用 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 的逻辑实现,具体实现可看源码。

源码

原文地址:https://www.cnblogs.com/lovevin/p/13528376.html