用 useReducer 代替 Redux


寫在前面

看本篇博客的前提需要了解 Redux 是什么,若不知請移步 Redux

自從 React Hooks 推出 useReducer Hook 來,在使用 useReducer Hook 的時候其實可以明顯感覺到就是和 Redux 是差不多的,都是以 reducer 和 action 兩個主要概念為主。

reducer 是一個 (state, action) => newState 的狀態產生機,action 是一個動作描述對象。

只不過對於 state 的讀寫接口的處理方式不同,Redux 是通過 createStore(reducer, initialState) 來創建一個 store 實例,該實例封裝了 state 的讀寫接口和監聽接口:getState 、dispatch、subscribe,各組件通過調用 store 實例提供的狀態操作接口來對狀態進行使用和操作。

但 useReducer Hook 是沒有使用 store 實例,而是遵循 Hook 總是返回讀寫接口的規則,直接通過 [state, dispatch] = useReducer(reducer, initialState) 的方式返回狀態的讀寫接口。在 Redux 中,store.dispatch 觸發事件動作時,Redux 並不會為我們主動重新渲染視圖,而是需要我們調用 store.subscribe 在監聽函數中手動 render 視圖。但 Hook 一般是在調用寫接口后就會自動重新 render 視圖。因此,useReducer Hook 就是這樣的,dispatch 寫接口調用后就幫我們自動重新 render 了。

那么如何讓創建 reducer 的讀寫 API 的組件將狀態的讀寫 API:state 和 dispatch 應用到其所有的后代組件呢?

像 Redux 中創建的 store 還可以通過 import store 的方式使用到,但是 useReducer 只能在函數組件內部使用得到應用狀態讀寫 API,更不可能導出去了。此時就用到了 useContext() 這個 Hook。

下面以用 useReducer 代替 Redux 做一個 todo-list demo,來講解 useReducer + useContext 是如何代替 Redux 的。

目錄結構如下:

但這種代替方式只適用於組件都是函數組件的情況

1. 使用 useReducer 創建狀態機

const [state, dispatch] = useReducer(reducer, {
    filter: filterOptions.SHOW_ALL,
    todoList: []
});

2. 使用 createContext 和 useContext 暴露狀態機接口

2.1 createContext

context.js(因為創建的 context 會在各個組件中使用 useContext 得到,因此需要單獨文件導出)

import {createContext} from 'react';

const Context = createContext(null);

export default Context

App.js(設置 context 的作用范圍)

function App() {
    const [state, dispatch] = useReducer(reducer, {
        filter: filterOptions.SHOW_ALL,
        todoList: []
    });
    return (
        <Context.Provider value={{ state, dispatch }}>
            <div className="App">
                我是 APP,要點:useReducer 的初始值不要傳 null,要初始化,否則使用 ajax fetch 不成功
                <AddTodo/>
                <TodoList/>
                <Filter/>
            </div>
        </Context.Provider>
    );
}

2.2 useContext

TodoList / index.js

const TodoList = () => {
    const {state, dispatch} = useContext(Context);
    useEffect(()=> {
        fetchTodoList(dispatch)
    },[])
    const getVisibleTodoList = (state, filter)=>{
        switch (filter) {
            case filterOptions.SHOW_ALL:
                return state.todoList
            case filterOptions.SHOW_COMPLETE:
                return state.todoList.filter(todo => todo.isComplete)
            case filterOptions.SHOW_UNCOMPLETE:
                return state.todoList.filter(todo => !todo.isComplete)
        }
    }
    return state.todoList.length > 0 ? (
        <ul>
            {getVisibleTodoList(state, state.filter).map((todo, index) => (
                <li key={index} onClick={() => dispatch(toggleTodo(index))}
                    style={{textDecoration: todo.isComplete ? 'line-through' : 'none'}}>{todo.text}</li>
            ))}
        </ul>
    ) : (<div>加載中...</div>);
};

3. 使用最原始的拆分方式代替 combineReducers

Redux 中有提供 combineReducers 合並 reducer 的方法,在 useReducer Hook 中,我們可以使用最原始的對象拆發的方法代替 combineReducers

reducers / todoList.js

import {ADD_TODO, INIT_TODOS, TOGGLE_TODO} from '../constants/actionTypes';

const todoList = (state, action)=>{
    switch (action.type) {
        case INIT_TODOS:
            return action.todoList
        case TOGGLE_TODO:
            return state.map((todo, index)=>{
                if(index === action.index)
                    return {...todo, isComplete: !todo.isComplete}
                return todo
            })
        case ADD_TODO:
            return [...state, { text: action.text,  isComplete: false}]
        default:
            return state
    }
}

export default todoList

reducers / filter.js

import {SET_FILTER} from '../constants/actionTypes';

const filter = (state, action)=>{
    switch (action.type) {
        case SET_FILTER:
            return action.filter
        default:
            return state
    }
}

export default filter

reducers / indes.js

import todoList from './todoList';
import filter from './filter';

const reducer = (state, action)=>{
    return {
        todoList: todoList(state.todoList, action),
        filter: filter(state.filter, action)
    }
}

export default reducer

源碼鏈接

以上內容只是在講如何使用 useReducer 和 useContext 代替 Redux,因此並沒有細細講 todo-list 的邏輯實現,具體實現可看源碼。

源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM