點燈坊

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

使用 State Hook 處理 State

Sam Xiao's Avatar 2019-09-29

以前只能在 Class Component 處理 State,現在也能在 Function Component 以 State Hook 處理 State。

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

App.js

import React, { Component } from 'react';

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

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

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

14 行

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

傳統 JSX 要寫在 render() 內,且必須使用 this 才能抓到 state 與 method。

第 4 行

state = {
  count: 0,
}

宣告 state object 儲存 count state。

第 8 行

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

定義 click 的 event handler:addCount() method,使用 setState() 寫入新的 state。

Class component 寫法遵循 OOP 風格:使用 side effect 更改 state,且大量使用 thismethod

Function Component

App.js

import React, { useState } from 'react';

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

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

App 由 class 變成 function。

第 1行

import React, { useState } from 'react';

useState() import 進來。

第 7 行

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

不再需要 render(),只需 return JSX 即可。

也由於 state 與 function 都宣告在 App() 內,連 this 也不需要了。

第 4 行

let [count, setCount] = useState(0);

React 提供了 useState() hook,傳進 state 的初始值,也就是 0,回傳兩個值,一個為 state,另外一個為 state 的 setter,習慣命名為 setXXX()

第 6 行

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

定義 click 的 event handler:addCount() function,使用 useState() hook 提供的 setCount() 寫入新的 state。

Function component 寫法遵循 FP 風格:不使用 side effect 更改 state,也沒有使用 thismethod ,全都是一進一出的 pure function,且程式碼更為精簡

Custom Hook

Q:若將來 component 太大要重構,或其他 component 要重複使用這段邏輯該怎麼辦 ?

counter.js

import { useState } from 'react';

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

  return [
    count,
    addCount,
  ];
};

我們將 count state 與 addCount() 抽成 useCount() hook,單獨放在 counter module 中。

由於 JSX 需要的只有 count state 與 addCount() function,最後再將這兩個以 array 方式傳回。

React 官方建議 custom hook 都以 use 開頭。

App.js

import React from 'react';
import useCount from './counter';

export default () => {
  let [count, addCount] = useCount();

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

App() 只要接收 useCount() hook 所傳回的 state 與 function 即可。

我們可以發現由於 function component 的 hook 寫法,因為都是沒有 side effect 的 pure function,所以特別容易重構,不用再擔心 this 問題,只需簡單的 extract function,然後搬到其他 module 即可

Conclusion

  • 有了 state hook,使得原本必須在 class component 才能有的 state,目前在 function component 也能實現
  • 由於 function component 都是 pure function,因此特別容易重構成 custom hook
  • Component 間牽涉 state 的邏輯若有重複,只要簡單抽成 custom hook 再 import 進來即可,不必再使用 render props 或 higher-order component

Reference

Reed Barger, Introducing the useState Hook
React, Hooks at a Glance
React, Using the State Hook
React, Building Your Own Hooks