點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Effect Hook 處理 Side Effect

Sam Xiao's Avatar 2019-10-01

在觀念上我們會希望在 DOM Render 完之後才處理 Side Effect,在 Class Component 我們須在 componentDidMount()componentDidUpdate() lifecycle 分別處理;但在 Function Component 只需使用 Effect Hook 統一處理即可。

Version

macOS Mojave 10.14.6
WebStorm 2019.2.3
Node 12.11.0
Yarn 1.19.0
create-react-app 3.1.2
React 16.10.1

Class Component

import React, { Component } from 'react';

export default class extends Component {
  state = {
    count: 0,
  };

  componentDidMount = () => {
    document.title = `You have clicked ${ this.state.count } times`;
  };

  componentDidUpdate = () => {
    document.title = `You have clicked ${ this.state.count } times`;
  };

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

  render() {
    return (
      <div>
        <button onClick={ this.addCount }>+</button>
        <div>{ this.state.count }</div>
      </div>
    );
  }
}

當 DOM render 完後,希望能同時根據目前 count state 更新 document.title,此為 side effect。

第 8 行

componentDidMount = () => {
  document.title = `You have clicked ${ this.state.count } times`;
}

當一開始 component 完成 mount 時,會執行 componentDidMound(),可在此對 document.title 執行 side effect。

12 行

componentDidUpdate = () => {
  document.title = `You have clicked ${ this.state.count } times`;
};

當每次 state 變動,DOM render 完時,會執行 componentDidUpdate(),可在此對 document.title 執行 side effect。

我們可發現在 componentDidMount()componentDidUpdate() 出現重複 code,雖然可將重複部分 extract 成新 method,但仍無法避免在 componentDidMound()componentDidUpdate() 都要呼叫新 method 的事實

Function Component

import React, { useState, useEffect } from 'react';

export default () => {
  let [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You have clicked ${ count } times`
  });

  let addCount = () => setCount(count + 1);

  return (
    <div>
      <button onClick={ addCount }>+</button>
      <div>{ count }</div>
    </div>
  );
};

第 1行

import React, { useState, useEffect } from 'react';

useEffect() import 進來。

第 6 行

useEffect(() => {
  document.title = `You have clicked ${ count } times`
});

useEffect() 傳入 callback,當 DOM render 完後會呼叫該 callback。

useEffect() 的 callback 在 componentDidMount()componentDidUpdate() cycle 都會被呼叫,因此只要寫一次即可。

此外,藉由 ECMAScript 的 closure 特性,會自動抓到 count state,因此不需要 this 存取 state。

因為每次 DOM render 完都會重新執行 useEffect() ,因此 callback 是重新傳入,此時剛好抓到 count state 的最新值,因此每次 state 更新,useEffect() 的 callback 都會顯示最新的 state。

Conclusion

  • Effect hook 其實背後黑魔法不少,但最少不必如 class component 要在 componentDidMount()componentDidUpdate() 各寫一次,只要觀念上是 DOM render 完執行 side effect 即可;且善用 closure 特性,不必使用 this 存取 state,可放心使用 arrow function

Reference

Reed Barger, Introducing the useEffect Hook
React, Using the Effect Hook