從零開始的 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/