先前的文章中都是以 class 形式製作 React 組件(component),但對於一些不需要狀態(state)的組建而言,使用 class 製作 React 組件不免有點多餘。所幸對於這種單純的組件,可以使用 function 進行開發。

本文環境

  • Chrome 86
  • React 16.13.1
  • nodejs 12.16.1
  • npm 6.13.4
  • webpack 3.3.8

Functional Components

使用 function 開發組件,大致類似以下形式的程式碼:

function 組件名(props) {
    return (    // 等同於 class 組件中的 render()
    )
}

因此 React 教學 - 從零開始的 React 生活 Part 3 中的 BottomLine 可以改成以下:

import React from 'react';

function BottomLine(props) {
  return (
    <div style={{borderStyle: "solid", borderWidth: "0 0 1px 0"}}>
      {props.children}
    </div>
  )
}

export default BottomLine;

改成 functional component 後,比 class 組件更加簡潔!

當然, functional component 也能夠擁有 state ,不過需要使用 useState() 達成,例如以下範例的 Switch 組件,使用 const [value, setValue] = useState(false) 將 value 的預設值設定為 false, 並取得 1 個函式 setValue 用以後續更新 value 的值,該函式相當於 class component 中的 setState 。因此當我們觸發 onClick 時,透過呼叫 setValue 就能夠變更 value 的值,進而讓組件改變顯示的字串。

p.s. setValue 可以換成任何想要的名稱

import React, { useState } from 'react';

function Switch(props) {
  const [value, setValue] = useState(false)

  return (
    <div onClick={() => setValue(!value)}>
      switch: {value?'on':'off'}
    </div>
  )
}

上述範例可以放進 React 教學 - 從零開始的 React 生活 Part 3 的 app.jsx 中,就能夠試玩,例如:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

import BottomLine from './textDeco';

function Switch(props) {
  const [value, setValue] = useState(false)

  return (
    <div onClick={() => setValue(!value)}>
      switch: {value?'on':'off'}
    </div>
  )
}

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>
        <Switch />
        <h1 onClick={this.addCount}>Hello World, {this.props.name} {this.state.count}
        </h1>
        <Switch />
      </BottomLine>
    );
  }
}

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

useEffect

除了 state 之外, functional component 也能夠使用 useEffect() 在組件渲染完成之後做些額外的事情,例如變更網頁標題:

function Character(props) {
  const value = 'Luke'

  useEffect(() => {
    console.log('after render')
    document.title = 'You are looking at ' + value
  })

  console.log('before render')
  return <div>{value}</div>
}

上述範例執行後,可以在開發者工具的 console 看到類似以下的輸出,足見 useEffect 確實會在組件渲染完成後才執行:

before render
after render

此外, useEffect 每次都會在渲染完成後執行。

The function passed to useEffect will run after the render is committed to the screen.

所以在 useEffect 中又讓組件重新渲染的話,譬如在 useEffect 更新 state 的值,將會再次觸發 useEffect , 然後又重新更新了 state 的值,導致組件再次重新渲染,進而使得組建沒有停止新渲染的一天,例如以下範例:

function Character(props) {
  const [value, setValue] = useState(0)

  useEffect(() => {
    console.log('after render');
    setValue(value + 1)
  })

  console.log('before render');
  return <div>{value}</div>
}

上述範例除 value 會無限遞增之外,也會在 console 中看到類似以下的錯誤訊息,該訊息也提及此錯誤可能由於我們在 useEffect 改變 state 所引起(state 的改變會造成 React 將組件重新渲染):

react_devtools_backend.js:2430 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
    in Character (created by HelloWorld)
    in div (created by BottomLine)
    in BottomLine (created by HelloWorld)
    in HelloWorld

此種不斷重新渲染的情況,將會造成效能問題,必須注意。

以上就是如何使用 React functional component 的介紹,如果有興趣的話,可以詳閱 Using the Effect Hook 一文,裡面還有很多本文未提及的部分喔!

Happy coding!

References

https://reactjs.org/docs/components-and-props.html

https://reactjs.org/docs/state-and-lifecycle.html