從零開始的 React 教學 Part 3 - Class Components

Last updated on  Oct 2, 2022  in  ReactJS 前端框架  by  Amo Chen  ‐ 3 min read

從零開始的 React 教學 Part 2 中,我們學會利用 Webpack 編譯含有 JSX 的 React 專案,讓我們能夠利用 JSX 語法提高開發 React 的效率。

本篇會將 從零開始的 React 教學 Part 1 中的範例改成 JSX 語法呈現,並且模組化部分組件(components) ,從而達到較高的可維護性。

最後再加上新的外掛 babel/plugin-proposal-class-properties ,讓我們能夠使用 Arrow function 更加簡化 React 程式碼。

本文環境

  • Chrome 81
  • React 16.13.1

Hello React 進化版

結合前 2 篇所學,我們就能夠將 src/app.jsx (詳見 從零開始的 React 教學 Part 2 部分程式改用 JSX 語法:

import React from 'react';
import ReactDOM from 'react-dom';

class HelloWorld extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
        count: 0,
      }
      this.addCount = this.addCount.bind(this)
  }

  addCount() {
      let count = this.state.count + 1
      this.setState({count: count})
  }

  render() {
    return (
    	<h1 onClick={this.addCount}>Hello World, {this.props.name} {this.state.count}
    	</h1>
    );
  }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(
	<HelloWorld name='React' />,
	domContainer
);

可以看到第 20 行與第 29 行改成以 JSX 語法實作,程式整體看起來也簡潔許多。

p.s. onClick 是 React 規定的寫法,與 Javascript 的 onclick 不一樣,如果想知道 React 支援什麼 event 可以查閱官方文件 Supported Events

模組化

目前為止我們都將所有的程式放在 src/app.jsx 中,對於小型專案而言,並沒有太大影響,然而隨著專案越來越大,模組化就會變成一個至關重要的課題。

得益於 Webpack 與 Babel ,我們能夠輕鬆地使用 ES6 的模組功能 達到模組化 React 專案的目標。

目前有 2 派模組化 Javascript 的做法:

ES6 的模組功能並非屬於上述任何一種,儘管它看起來十分像 CommonJS ,不過它其實是參考了前述 2 者作法而發展出來的。

本篇就不多對 ES6 的模組多做贅述,詳細可以參閱 JavaScript modules - JavaScript | MDN 學習模組的基礎。

接著,試著新增一個模組化的組件,用來為文字加上底線的效果,我們新增 1 個檔案 textDeco.jsx :

import React from 'react';

class BottomLine extends React.Component {
  render() {
    return (
      <div style={{borderStyle: "solid", borderWidth: "0 0 1px 0"}}>
        {this.props.children}
      </div>
    )
  }
}

export default BottomLine;

上述範例的重點在最後 export default BottomLine 的部分,代表我們允許其他人使用 BottomLine 這個組件。

另外值得注意的是 React inline CSS 的寫法,規定我們必須傳 map 型態的物件給 style 才行,這也是為何我們用 style=\{\{borderStyle: "solid", borderWidth: "0 0 1px 0"\}\} 設定 CSS。

p.s. React 的 inline CSS 屬性不需要有 - ,所以 border-style 可以改成 borderStyle, border-width 則改成 borderWidth

如果我們將 style 改成 style="border-style: solid; border-width: 0 0 1px 0;" 就會在瀏覽器的開發者工具出現以下錯誤,這時候只要將 style 改為前述寫法即可:

Uncaught Error: The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.

再來我們在 app.jsx 試著使用該組件:

import React from 'react';
import ReactDOM from 'react-dom';

import BottomLine from './textDeco';

class HelloWorld extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
        count: 0,
      }
      this.addCount = this.addCount.bind(this)
  }

  addCount() {
      let count = this.state.count + 1
      this.setState({count: count})
  }

  render() {
    return (
      <BottomLine>
        <h1 onClick={this.addCount}>Hello World, {this.props.name} {this.state.count}
        </h1>
      </BottomLine>
    );
  }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(
  <HelloWorld name='React' />,
  domContainer
);

上述範例第 4 行代表從 textDeco.jsx 匯入 BottomLine 組件,並且在第 22 及第 26 行使用它。

成功的話將會看到 Hello World, 0 多個底線。

以上就是用 Webpack 加上 ES6 進行模組化的過程。

Arrow function (箭頭函式)

本篇最後談談 @babel/plugin-proposal-class-properties · Babel 這個外掛。

目前為止,我們都會在組件的 constructor 方法內為綁定(bind)組件內的方法,例如前述範例 this.addCount = this.addCount.bind(this) 的部分。

但是很多時候我們都會忘記要綁定,導致類似以下的錯誤出現:

app.jsx:54 Uncaught TypeError: Cannot read property 'state' of undefined

這種錯誤總是讓人覺得煩躁,這時候只要安裝 @babel/plugin-proposal-class-properties · Babel 就能夠使用 Arrow function,利用 Arrow function 的特性,從此再也不需要每次都為 React 組件的方法進行綁定。

安裝方法:

$ npm install --save-dev @babel/plugin-proposal-class-properties

安裝完成之後,必須為 Webpack 的設定檔 webpack.config.js 加上新的設定:

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    app: "./src/app.jsx",
  },
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].js",
    publicPath: "/"
  },
  resolve: {
    extensions: [".jsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-react",
              "@babel/preset-env"
            ],
            plugins: ['@babel/plugin-proposal-class-properties'],
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: { presets: ["@babel/preset-env"] }
        }
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My React App',
      filename: 'index.html',
      template: "./src/index.html",
    })
  ],
  devServer: {
    static: {
        directory: path.join(__dirname, "dist"),
    },
    compress: false,
    port: 9000
  },
}

新增的設定為第 28 行 plugins: ['@babel/plugin-proposal-class-properties'],

最後讓我們將 app.jsx 的組件方法改為 Arrow function 並且拿掉綁定的部分吧:

import React from 'react';
import ReactDOM from 'react-dom';

import BottomLine from './textDeco';

class HelloWorld extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
        count: 0,
      }
  }

  addCount = () => {
      let count = this.state.count + 1
      this.setState({count: count})
  }

  render() {
    return (
      <BottomLine>
        <h1 onClick={this.addCount}>Hello World, {this.props.name} {this.state.count}
        </h1>
      </BottomLine>
    );
  }
}

const domContainer = document.querySelector('#app');
ReactDOM.render(
  <HelloWorld name='React' />,
  domContainer
);

上述範例中:

  addCount = () => {
      let count = this.state.count + 1
      this.setState({count: count})
  }

就是一個不需任何參數的 Arrow function ,另外也可以看到 constructor 不需要綁定也能正常運作。

偉哉 Webpack & ES6 。

以上, Happy Coding!

References

https://reactjs.org/

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

追蹤新知

看完這篇文章了嗎?還意猶未盡的話,追蹤粉絲專頁吧!

我們每天至少分享 1 篇文章/新聞或者實用的軟體/工具,讓你輕鬆增廣見聞提升專業能力!如果你喜歡我們的文章,或是想了解更多特定主題的教學,歡迎到我們的粉絲專頁按讚、留言讓我們知道。你的鼓勵,是我們的原力!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!