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

    源码

  • 相关阅读:
    vue 采坑 Invalid default value for prop "slideItems": Props with type Object/Array must use a factory function to return the default value.
    vue-cli3 按需引入echarts
    vue-cli3 按需引入外部elment-ui UI插件
    vue-cli3 引入less全局变量
    css 文本溢出截断省略方案
    canvas画圆角头像
    css 加载效果
    css实例气泡效果
    css居中-水平居中,垂直居中,上下左右居中
    meta标签
  • 原文地址:https://www.cnblogs.com/lovevin/p/13528376.html
Copyright © 2020-2023  润新知