iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
0
自我挑戰組

React 30 天學習歷程系列 第 26

【Day 26】React Hook(三): useReducer

  • 分享至 

  • xImage
  •  

useReduceruseState 的一個替代方案,用於處理比較複雜的狀態,或是裡面有多個屬性的 state,類似於 redux 中的 reducer,不同的地方在於它多了兩個參數 reducerinitialAction

const [state, setState] = useState(initialState)

const [state, dispatch] = useReducer(reducer, initialState, initialAction)
  • state 就是資料的狀態。
  • dispatch 是更新 state 的方法,跟 setState 的差別在於它接受 action 作為參數。
  • reducer 是一個函式,用於處理 action 的描述,並以此更新 state。它接受兩個參數, state(舊state) 和 action,並返回一個新的 state,如下面的結構
(state, action) => newState
  • action 是一個物件,用於描述 state 更新的方式,它通常有一個 type 屬性,也可以攜帶其他參數,下面是一個 action 的範例,action 只能透過 dispatch 來調用,dispatch 會將 action 傳給 reducer
const action = {
    type: 'increment', // increment 表示 state 的值要增加
    payload: {
        other: 'value' // 其他的參數
    }
}
  • initialState 就是初始的 state
  • initialActionuseReducer 第一次被執行時的 action

用 useReducer 更新 state

下面是一個簡易的 reducer 使用範例,我們利用三個 button 來使用不同的 action type,這樣當 dispatch 調用 action 時,觸發 reducer,就能起到不同的效果。

import { useReducer } from 'react';

//初始值為 0
const initialState = { count: 0 }

// 接受 state, action 參數
const reducer = (state, action) => {
    // 根據 action 的 type 來更新 state
    switch (action.type) {
        // type 為 reset,就重新轉為初始值
        case 'reset':
            return initialState
        // type 為 increment,就 +1
        case 'increment':
            return {count: state.count + 1}
        // type 為 decrement,就 -1
        case 'decrement':
            return {count: state.count - 1}
        // 當 type 不屬於上面任何值,就返回當前的 state
        default:
            return state
    }
}

const TestComponent = () => {
    const [state, dispatch] = useReducer(reducer, initialState)
    // 透過 dispatch 調用 action,來判斷更新的方式
    return <div>
        <p>current count {state.count}</p>
        <div><button onClick={() => dispatch({type: 'reset'})}>reset</button></div>
        <div><button onClick={() => dispatch({type: 'increment'})}>+1</button></div>
        <div><button onClick={() => dispatch({type: 'decrement'})}>-1</button></div>
    </div>
}

搭配 useContext 使用

上一篇有提到我們可以利用 useContext 做到 state 的全域調用,我們也可以搭配 useContext 來使用 useReducer 以達到較為複雜的 state 管理及更新。下面我們先建立初始值和 Context

const userInitState = {
  name: 'Leo',
  age: 29
}

export const UserContext = React.createContext({
  userInitState
});

接著我們建立更新 state 用的 reducer

const userReducer = (state, action) => {
    switch (action.type) {
        case 'reset':
            return userInitState
        case 'changeName':
            return {name: 'Jack', age: userInitState.age}
        case 'ChangeAge':
            return {name: userInitState.name ,age: 25}
        default:
            return state
    }
}

我們接著在 UserProfile 中去調用 useReducer,並將 userStateuserDispatch 透過 provider 傳送到 TestComponent1TestComponent2元件

const UserProfile = () => {
    const [userState, userDispatch] = useReducer(userReducer, userInitState);

    return <div>
        <UserContext.Provider
            value={{
                userState,
                userDispatch
            }}
        >
            <TestComponent1 />
            <TestComponent2 />
        </UserContext.Provider>
    </div>
}

這樣我們就能在 TestComponent1TestComponent2元件 中調用 userDispatch 並同步更新 userState,達到管理全域狀態並更新的效果。

const TestComponent1 = (props) => {
    const { userState, userDispatch } =  React.useContext(UserContext);
    // 調用 userDispatch
    return <div>
        <button onClick={() => userDispatch({type: 'changeName'})}>change Name</button>
        <button onClick={() => userDispatch({type: 'ChangeAge'})}>change Age</button>
        <button onClick={() => userDispatch({type: 'reset'})}>reset data</button>
    </div>
}
const TestComponent2 = (props) => {
    const { userState, userDispatch } =  React.useContext(UserContext);
    // 同步更新 userState
    return <div>
        <div>name: {userState.name}</div>
        <div>age: {userState.age}</div>
    </div>
}

上一篇
【Day 25】React Hook(二): Context 和 useContext
下一篇
【Day 27】React Hook(四): 其他 React Hook 的語法
系列文
React 30 天學習歷程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言