# React Basice Hook Note ###### tags: `Hooks` `React` `useState` `useEffect` `useContext` `useReducer` `useMemo` `useCallback` - useState - useEffect - useContext - useReducer - useMemo - useCallback # useState 狀態管理 ``` const [state, setState] = useState(initState) ``` - `useState` 回傳一個 `Array` - `state` 為要設置的 state (命名隨意) - `setState` 為更新 state 的函式 (命名隨意) - `initState` 為初始 state,也可以是一個 `return function` - `useState Hook` 可以用不止一次 <br/> ## [ Init state 的 2 種方式 ] - 傳入 data,每次 component render 都會跑一次 - 傳入 return function,只跑第一次 component render 時 ``` // 每次 render 都會跑一次 const [clickCount, setClickCount] = useState(0); const [clickCount, setClickCount] = useState(init()); // 只跑第一次 render 時 const [clickCount, setClickCount] = useState(() => init()); ``` <br/> ## [ Update state 的 2 種方式 ] - 傳入 data - 傳入 return function,可以獲得最新的 state ``` // 傳入 data <button onClick={() => setClickCount(clickCount + 1)}>點擊</button> // 傳入 return function <button onClick={() => setClickCount((pre) => pre + 1)}>點擊</button> <button onClick={() => setUserData((pre) => { ...pre, name: 'Yellow'})}>點擊</button> ``` <br/> ## [ 延伸> 解決 useState 不及時問題 ] 先來亢亢這個 ``` const [updateCount, setUpdateCount] = useState(5); const updateCountHandle = async () => { await setUpdateCount(updateCount + 1); console.log('updateCount: ', updateCount); // 預期+1 後是 6,但結果一樣是5 }; const updateCountBtn = () => { return ( <div> <h3>最新的點擊次數是 {updateCount} 次</h3> <button onClick={() => updateCountHandle()}>更新點擊</button> </div> ); }; ``` React 文檔是說 > 为什么我会在我的函数中看到陈旧的 props 和 state ? > 组件内部的任何函数,包括事件处理函数和 effect,都是从它被创建的那次渲染中被「看到」的 > (說明 console 印出來的是這次 render 出來的值) 但如果中間在加入一個 return function 呢? ``` const [updateCount, setUpdateCount] = useState(5); const updateCountHandle = async () => { await setUpdateCount(updateCount + 1); await setUpdateCount((pre) => { console.log('preState: ', pre); // 印出 6 return pre +1; }); console.log('updateCount: ', updateCount); // 印出 5 XD }; const updateCountBtn = () => { return ( <div> <h3>最新的點擊次數是 {updateCount} 次</h3> // 印出 7 woooo <button onClick={() => updateCountHandle()}>更新點擊</button> </div> ); }; ``` 建議是不要使用以上方式這樣做判斷 > 而是使用 `useEffect` 偵測 `updateCount` 的變化並且執行要的動作 ``` const [updateCount, setUpdateCount] = useState(5); const updateCountHandle = async () => { await setUpdateCount(updateCount + 1); // await setUpdateCount((pre) => { // console.log('preState: ', pre); // return pre + 1; // }); console.log('updateCount: ', updateCount); // 印出 5 }; useEffect(() => { // do something console.log('useEffect: ', updateCount); // 印出 6,恭喜~~ }, [updateCount]); ``` <br/> <br/> # useEffect 副作用管理 任何會產生 side effect (資料獲取、訂閱、手動修改 React Dom 等) 的行為都應該使用 useEffect 處理。 整合了 `componentDidMount`, `componentDidUpdate`, `componentWillUnmount` ``` useEffect(callback, array) ``` - `callback` 函式,用於處理 side effect 邏輯 - `array` 根據不同的設定來決定要執行 callback 的時機 <br/> ## [ 第一個參數 callback,副作用邏輯 ] - **side effect logic** 副作用邏輯處理 - **clean up** 可以返回一個函式,用於清理工作,相當於 `componentWillUnmount` ``` useEffect(() => { // 處理 side effect return () => { // Cleanup whatever we did last time } }) ``` <br/> ## [ 第二個參數 array,用於控制執行 ] - **once** 如果是空 array `[]`,只在元件第一次 render 時執行,相當於 `componentDidMount` - **have change** 如果 array 有 state, 或 props 的值,當有改變時就會執行,相當於 `componentDidUpdate` - **after every render** 當沒有定義 array 時,他會在第一次及每次 render 都會執行 ``` useEffect(() => { // 只在初次 render 時執行 console.log('once') }, []) ``` ``` useEffect(() => { // 會在初次 render 時執行,並且當 array 內的 state 或 props 改變時也會執行 console.log('have change') }, [state, props]) ``` ``` useEffect(() => { // 他會在第一次及每次 render 都會執行 console.log('after every render') }) ``` <br/> ## [ Clean up 清理機制 ] 有兩種常見的副作用,一種是不需要清理的另一種是需要的。 - **不需要清理的** 資料請求,DOM 修改,log 紀錄等,useEffect 會自動處理 - **需要清理的** 訂閱和取消訂閱,事件監聽和取消監聽都是需要清理的 <br/> <br/> # useContext 方便使用 context 是 Context.Consumer 的語法糖 ## [ Context 資源共享 api ] 解決層層傳遞 props 的問題 ( props drilling ),主要通過 3 個步驟: 1. 通過 `React.createContext` 創建 Context Object。 2. 使用 `Context.Provider` 包裹父元件,傳遞資源使底下 child 可以取用。 3. 使用 `Context.Consumer` 包裹子元件,取得 context 資源。 ### 創建 ``` const testContext = createContext({ isLogging: flase }); // const { Provider, Consumer } = testContext; export default testContext; // 這裡需要將 context 匯出讓 child 取用 ``` ### 包裹父元件,傳遞資源 ``` import testContext from 'src/store/test-context' const logoutHandle = () => {} ... <testContext.Provider value={{ ...testContext, logoutHandle }}> <MyComponents /> </testContext.Provider> ``` ### 包裹子元件,取得資源 ``` import { testContext } form '../ParentsComponent'; // 匯入 context <testContext.Consumer> { ctx=> { // ctx.isLogging 是通過 provider 傳下來的資源 }} </testContext.Consumer> ``` <br/> ## [ useContext 取得資源 ] 使用 `useContext ` 代替 Consumer 取得 context 資源 ### 子元件取得資源 ``` import { testContext } form '../ParentsComponent'; // 匯入 context const ChildComponent = () => { const store = useContext(testContext) } ``` ## 注意!! 每當 useContext 更新時,底下的 child 元件都會重新 re-render,所以不常變動的值較適合使用 context 保管,或是使用 useMemo 節省效能。 <br/> #### 延伸閱讀:[The Problem with React's Context API](https://leewarrick.com/blog/the-problem-with-context/) <br/> <br/> # useReducer 複雜的狀態管理 進階版 useState,兩者都是用來儲存、更新資料。跟 Redux 很像!核心都是 store, action, reducer ![](https://i.imgur.com/j4GfM8E.png) ## [ 基本 useReducer ] ![](https://i.imgur.com/XznH7Gf.jpg) ### 參數: - `reducer` 是一個函式,用於處理 action 並更新 state。<br /> ``` (preState, action) => newSate ``` - `initState` 初始化 State。 - `initFn` 初始化 Function,useReducer 初次執行時被處理。 ### 回傳: - `state` 狀態 snapshot - `dispatch` 更新 state 的方法,action 作為參數。 <br/> ### 如何使用: ``` const { useState, useReducer } = React; const ACTIONS = { INCREMENT: 'increament', DECREMENT: 'decreament' } function reducer(state, action) { const type = { [ACTIONS.INCREMENT]: () => ({ count: state.count + 1}), [ACTIONS.DECREMENT]: () => ({ count: state.count - 1}) } return type[action.type] ? type[action.type]() : state } const Counter = () => { const [state, dispatch] = useReducer(reducer, {count: 256}) const increase = () => {dispatch({type: ACTIONS.INCREMENT}) }; const decrease = () => {dispatch({type: ACTIONS.DECREMENT }) }; return ( <div> <button onClick={decrease} /> <span>{state.count}</span> <button onClick={increase} /> </div> ) }; ``` <br /> <br /> # useMemo 記憶值 用於效能優化。無關於父元件,主要用在當元件重新渲染時,通過`記憶值` 避免在元件中複雜的程式重複執行,可以減少渲染的耗時。 <br/> 適合用於 > 需要複雜計算的場景,例如 `複雜的列表渲染`,深拷貝等等。 > React 官方特別提醒 > You may rely on useMemo as a performance optimization, not as a semantic guarantee. > 因此,不要什麼東西都丟到 useMemo 裡面,在需要優化效能時才引用,否則只是讓 React 處理更多事情,造成更大的負擔。 ## [ 基本用法 ] ``` const memoizedValue = useMemo(callback, array); ``` ### 參數: - `callback` 計算邏輯函示。 - `array` 當裡面賦予的值改變時 useMemo 才會執行。 ### 返回: - 返回 callback 的返回值。 ``` const obj1 = { id: '1', name: 'yellow' } const obj2 = { id: '2', name: 'kimi', age: 18 } const memoizedValue = useMemo(()=> Object.assign(obj1, obj2),[obj1, obj2]) // 使用 <div>{memoizedValue.age}</div> ``` <br /> <br /> # useCallback 記憶值 當父元件傳遞的 props 是 Object 時,父元件的狀態被改變觸發重新渲染,Object 的記憶體位址也會被重新分配,React.memo 會用 shallowly compare 比較 props 中 Object 的記憶體位址,這個比較方式會讓子元件被重新渲染。<br/> 因此,React 提供了 React.useCallback 這個方法讓 React 在元件重新渲染時,如果 dependencies array 中的值在沒有被修改的情況下,它會幫我們記住 Object,防止 Object 被重新分配記憶體位址。 ## [ 基本用法 ] ``` const memoizedCallback = useCallback(callback, array); ``` 當 useCallback 能夠記住 Object 的記憶體位址,就可以避免父元件重新渲染後,Object 被重新分配記憶體位址,造成 React.memo 的 shallowly compare 發現傳遞的 Object 記憶體位址不同。 ### 參數: - `callback` 計算邏輯函示。 - `array` 當裡面賦予的值改變時 useMemo 才會執行。 ### 返回: - 返回 callback 本身。 `useCallback(fn, arr)` 等於 `useMemo(()=>fun, arr)` ``` const obj1 = { id: '1', name: 'yellow' } const obj2 = { id: '2', name: 'kimi', age: 18 } const memoizedValue = useCallback(()=> Object.assign(obj1, obj2),[obj1, obj2]) // 使用 <div>{memoizedValue().age}</div> ```