Skip to main content

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

· 5 min read
戈頓
Developer & Co-founder of Tecky Academy

於本篇文章的上集,我們討論了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還是處於實驗性質的功能,各位尚未可以 應用於現實世界的軟件上的。不過由於功能强大,有取代現有複雜框架之勢,相當可能廣泛使用。