iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
0

經過前幾天的介紹後,我們知道了如何設計 component 以及設計 component 上的一些問題,今天我們要來了解 hook 到底是怎麼解決相關問題,以及怎麼提升效能的。

HOOK

HOOK 就像我們昨天說到的,是 React 在去年釋出的新 API ,讓 function 也可以使用生命週期函數來接取資料以及擁有 state,這是因為它使用了 useState 和 useEffect 兩種方法,這邊我們將同時說明並改造之前的 class component 。

useState

這個函數可以讓 function 也擁有 state,他同時創建 state 和專屬用來改變這個 state 的 function。

我們將前幾天的小練習改造成 hook 吧!

首先我們一次性將useEffect、useState import 進 app.js

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

接下來,先把 componentDidMount、componentWillUnmount、componentDidUpdate 先註解掉,等等才會用到他們

註: 註解快捷鍵 ctrl+/

建立兩個 useState

  const [count,setCount] = useState(0)
  const [memory,setMemory] = useState("infinity")

這樣我們的 state 就設定好了

註:const 是一種常用來宣告物件的方式,未來會針對宣告做詳細說明。

接下來將 class component 和 app 改為 function component,並刪除 constructor 與 render,要注意不要刪錯喔。

function App() {
    return (
      <div>
        <h1>Hello World</h1>
        <ElderBrother />
      </div>
    )
}

function ElderBrother() {…}

將 function 也改為 const 宣告,並使用 useState 自己的 function 更新資料。

    const addCount = () => {
      setCount(count+1)
    };
  
   const handleChange = () => {
      setMemory( "i'm gooooood")
      setCount(count+1)
    };

把 component 內的 props 和 state 改為我們設定的

<YoungerBrother paper={memory} handleChange={handleChange}/>
<h3>{count}</h3>

useState 的部分就大概完成了,畫面會長這樣。
https://ithelp.ithome.com.tw/upload/images/20200908/20123396DAXEdC2xJr.png
我們來試試看 change。
https://ithelp.ithome.com.tw/upload/images/20200908/201233966ntS0nxpsH.png
功能上沒甚麼問題。

useEffect

這個函數可以看成 componentDidMount、componentWillUnmount、componentDidUpdate 的綜合版,他跟 useState一樣,可以多次宣告,達到關注點分離,不過在我們這個例子會比較看不出效果XD,等後面寫系列專案就可以看出差別。

現在我們來試試看怎麼使用 useEffect,我們先用一個簡單的 console.log()測試一下

function ElderBrother() {
  const [count,setCount] = useState(0)
  const [memory,setMemory] = useState("infinity")
  
  useEffect(()=>console.log('useEffect'))
    
  const addCount = () => {...};
  
  const handleChange = () => {...};
  
      return (...);
    }

我們來看一下 devtool 的結果
https://ithelp.ithome.com.tw/upload/images/20200908/20123396RWpQnbYy7f.png
很清楚地出來了。

那你可能會問說,那我要怎麼分辨甚麼時候是 mount、updating、unmount。

在觸發一次 change 試試看。
https://ithelp.ithome.com.tw/upload/images/20200908/20123396X1okV2Ey15.png
這邊可以發現,當 updating 時也會觸發 useEffect。

那我們可以在 useEffect 抓取準確的 state 嗎?我們修改一下 console.log。

    useEffect(()=>console.log(`useEffect count:${count}`))

重新執行之後多案幾次 change 試試看。
https://ithelp.ithome.com.tw/upload/images/20200908/20123396HQI5tlc7XX.png
就算我們沒有綁定 change 的 callback function, 也能及時抓的到 count 的值。

這是因為,在每次更新 state 觸發 re-render 時,react 都會安排一個 different effect 來替代上一個,這根據 react 的 DOM 更新規則,讓 useEffect 的動作更像是 render 結果的一部分,所以它才能抓到當下更新之後的 count。

而每一個 useEffect 更新的結果都是獨立的,我們可以透過更新 count 跟 memory 來試試差別。

我們需要拆開 change 的功能 ,讓兩個 state 各自更新。

   const ChangeCount = () => {
      setCount(count+1)
    };
   const ChangeMemory =(e)=>{
      setMemory(e.target.value)
    }

這邊將 addCount 和 handleChange 統一命名,不做好這部分的話,你以後要看 code 會想垂死自己。

現在實做一下畫面,將 state 顯示在父 component,等等比較會直覺一點。

      <div>
        <h1>我是 Hook</h1> //原本是 "我是 class " 這邊也改一下才不會搞混。
        <YoungerBrother paper={memory} ChangeCount={ChangeCount} ChangeMemory={ChangeMemory}/>
        <h3>{count}</h3>
        <h3>{memory}</h3>
      </div>

子 component 只留 change 的實作。

    <div>
      <h1>我是 function</h1>
      <p>
        <button onClick={props.handleChange}>change</button>
      </p>
      <input value={props.paper} onChange={props.ChangeMemory} />
    </div>

最後,我們來更改 useEffect,並添加依賴( Dependency ),這樣的話就可以確保只有在依賴對像被更新時才觸發 useEffect,並且達到關注點分離的效果,白話一點,這樣做比較看得懂。

  useEffect(() => console.log(`useEffect count:${count}`),[count]);
  useEffect(() => console.log(`useEffect memory:${memory}`),[memory]);

註:Dependency,意思是當依賴的物件被更改時,會觸發被依賴的動作,會在架構設計時詳細介紹。

畫面會長這樣。

https://ithelp.ithome.com.tw/upload/images/20200908/20123396SxPpj7OAJe.png

觸發 ChangeCount。

https://ithelp.ithome.com.tw/upload/images/20200908/20123396ke3vhI7x3v.png

觸發 ChangeMemory

https://ithelp.ithome.com.tw/upload/images/20200908/20123396SKmE2bDDjw.png

可以發現 useEffect 可以處理各自的 state ,再也不用擔心會生命週期混雜著不同的邏輯了

當然,你也可以把 Dependency 設置為 "empty array" 就長這樣 => [],這樣的話 useEffect 就只會在 mount 被觸發。

而 unmount 可以在 useEffect 使用 return 觸發,這邊以 count 的 useEffect為例。

  useEffect(() => {
    console.log(`useEffect count:${count}`);
    return function unSubscript() {
      console.log("clean");
    };
  }, [count]);

我們添加一個 console.log測試,按下change。

https://ithelp.ithome.com.tw/upload/images/20200908/20123396NVD7JD853l.png

會發現 unmount 被觸發了 !?

回想一下我們剛剛講的,"在每次更新 state 觸發 re-render 時,react 都會安排一個 different effect 來替代上一個",所以就會觸發 unmount,大概流程會像下圖這樣。

https://ithelp.ithome.com.tw/upload/images/20200908/20123396P7uLoyKjmV.png

最後來比較一下 hook 和 class component

Class component:

class ElderBrothers extends Component {
  constructor(props) {
    super(props);
    this.state = {
      memory: "infinity",
      count:0
    };
  }
  componentDidMount() {
    console.log(
      "memory : " + this.state.memory + ", count : " + this.state.count
    );
  }
  componentWillUnmount() {
    this.unSubscript();
    console.log("component will unmount");
  }
  componentDidUpdate() {
    console.log("DidUpdate =>  memory : " + this.state.memory + ", count : " + this.state.count);
  }
  ChangeCount = () => {
    this.setState({count:this.state.count + 1});
  };
  ChangeMemory = (e) => {
    this.setState({memory:e.target.value});
  };
  unSubscript() {
    console.log("clean");
  };
  render() {
    return (
      <div>
        <h1>我是 class</h1>
        <YoungerBrother
          paper={this.state.memory}
          ChangeCount={this.ChangeCount}
          ChangeMemory={this.ChangeMemory}
        />
        <h3>{this.state.count}</h3>
        <h3>{this.state.memory}</h3>
      </div>
    );
  }
}

Hook:

function ElderBrother() {
  const [count, setCount] = useState(0);
  const [memory, setMemory] = useState("infinity");

//--- count---//
  const ChangeCount = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log(`useEffect count:${count}`);
    return function unSubscript() {
      console.log("clean");
    };
  }, [count]);
//------------------//

//---memory---//
  useEffect(() => console.log(`useEffect memory:${memory}`), [memory]);

  const ChangeMemory = (e) => {
    setMemory(e.target.value);
  };
//-------------------//

  return (
    <div>
      <h1>我是 class</h1>
      <YoungerBrother
        paper={memory}
        ChangeCount={ChangeCount}
        ChangeMemory={ChangeMemory}
      />
      <h3>{count}</h3>
      <h3>{memory}</h3>
    </div>
  );
}

應該可以看出兩者之間的差別,hook 的可讀性明顯比 class component 好很多,越大型的專案其中的差異會越來越明顯。

我是 Chris,我們今天簡單介紹了 useState 和 useEffect,但還有很多 hook api 我們今天沒有接觸到,我們會在接下來的系列專案中盡量帶到,明天我們將補一下之前沒講到的部分觀念。


上一篇
Day-9 循環的浪漫
下一篇
Day-11 作用域和變數宣告
系列文
先你一步的菜鳥 - 從 0 開始的前端網頁設計31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言