到底React Hooks有何特別(二)?淺談useEffect及useReducer

Gordon Lau

Gordon Lau

2018-11-29

於本篇文章的上集,我們討論了useState如何令Stateful React component簡化良多,此篇主要討論的是如何使 用useEffectuseEffect可以簡化stateful logic,很多人都提到 React Hooks 有可能可以完全取代Redux作為 React State Management的標準,正因如此。

重溫

上集提到,使用useState可以將原本class based component變成簡單的 functional component

class Welcome extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            counter: 0
        }
        this.incrementCounter = this.incrementCounter.bind(this);
    }
    incrementCounter(){
        this.setState(state=>({
            counter : state.counter+1
        }));
    }
    render(){
        return (
            <h1 onClick={this.incrementCounter}>
            Hello, {this.props.name} 
            {this.state.counter} times
            </h1>
        )
    }
}

變成

function Welcome(props) {
    const [counter,setCounter] = useState(0);
    return <h1 onClick={()=>setCounter(counter=>counter+1)}>
            Hello, {props.name} 
            {counter} times
            </h1>;
}

UseEffect

此例子中並無諸如componentDidUpdatecomponentDidMountcomponentWillUnmount等方法,React Hooks到底如何有效於 function 中取代呢?答案就是運用 useEffect。 舉例加上componentDidMountcomponentWillUnmount,來初始化及重置counter。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
    this.incrementCounter = this.incrementCounter.bind(this);
  }
  componentDidMount() {
    this.setState({
      counter: 10
    });
  }

  componentWillUnmount() {
    this.setState({
      counter: 0
    });
  }
  incrementCounter() {
    this.setState(state => ({
      counter: state.counter + 1
    }));
  }
  render() {
    return (
      <h1 onClick={this.incrementCounter}>
        Hello, {this.props.name}
        {this.state.counter} times
      </h1>
    );
  }
}

componentDidMount算是component的setup logic,當component一載入完成就會開始運行。 componentWillUnmount算是component的teardown logic,當component臨缷載前開始運行。

useEffect方法寫,出乎意料簡潔:

function App(props) {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    setCounter(counter => 10);
    return () => {
      setCounter(counter => 0);
    };
  }, []);
  return (
    <h1 onClick={() => setCounter(counter => counter + 1)}>
      Hello, {props.name}
      {counter} times
    </h1>
  );
}

多出來的只是一段如下的代碼:

useEffect(()=>{
    // componentDidMount is here!
    setCounter(counter=> 10);
    return ()=>{
        // componentWillUnmount is here!
        setCounter(counter=>0)
    }
},[])

第一個setCounter(counter=>10)是隨我們Welcome一起載入運行,而在return的function則是此effect的 teardown logic,也就是為清理 資源而寫的。你可能會問,那麼這段邏輯取代了 componentDidMountcomponentDidUpdatecomponentWillUnmount 那個呢?

  • 原本應在componentDidMount 的是 setCounter(counter=>10)
  • 原本應在componentWillUnmount 的是 setCounter(counter=>0)

componentDidUpdate呢? 有趣的是,上面並沒有對應 componentDidUpdate的地方,因為在useEffect第二個參數,有一個empty array。第二個參數是此effect的dependency,React會儲起每次這個array的數值,如果在下一次update的時候發現這個array改變了,就代表了 這個effect需要重新運行。由於一個empty array永遠都是一樣,所以我們這個useEffect只會運行一次!如果沒有了第二個參數,那就變成了 componentDidUpdate +componentDidMount了。

useEffect(()=>{
    // componentDidMount is here!
    // componentDidUpdate is here!
    setCounter(counter=> 10);
    return ()=>{
        // componentWillUnmount is here!
        setCounter(counter=>0)
    }
});

有了dependency 這個參數,可以輕易做到相當reactive的UI,例如僅當props改變,這個effect才會跟著改變,我們可以輕易寫成這個樣子

useEffect(()=>{
    // componentDidMount is here!
    // componentDidUpdate is here!
    setCounter(counter=> 10);
    return ()=>{
        // componentWillUnmount is here!
        setCounter(counter=>0)
    }
},[props]);

請留意第二個參數需要一個array。 以下是一個在Github Pages的live Example:

UseReducer

React Hooks當然不只useStateuseEffect兩個方法,還有一個備受注目的,就是useReduceruseReducer是React團隊方便開 發者使用reducer pattern而加入。

要使用useReducer,寫法也是非常簡單。

function counterReducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { counter: state.counter + 1 };
    default:
      return { counter: 0 };
  }
}

function App(props) {
  const [state, dispatch] = useReducer(counterReducer, { counter: 10 });
  return (
    <h1 onClick={() => dispatch({ type: "INCREMENT" })}>
      Hello, {props.name}
      {state.counter} times
    </h1>
  );
}

上面 counterReducer是正常reducer的寫法,只有一個簡單action,就是INCREMENTuseReducer會return兩個數值,第一個就 是state,第二個就是對應reducer的dispatch function,所以當一運行dispatch({type:"INCREMENT"})時,就會把counter加了1。

此段代碼於上面CodeSandbox裏面的index-reducer.js之內。

總結

useEffectuseReducer 都大大簡化了React寫複雜代碼的難度。當然順帶一提的時, React Hooks還是處於實驗性質的功能,各位尚未可以 應用於現實世界的軟件上的。不過由於功能强大,有取代現有複雜框架之勢,相當可能廣泛使用。

Comments

Read More

好Programmer是怎樣煉成的?

軟件工程及軟件工藝

Web Technology為何征服世界?

到底React Hooks 有何特別?