使用React-hooks 和 context api重写todomvc

简介

本文演示了如何使用React hooks和context 实现简单简单的data store和状态管理. 假设你对以下内容有基本了解(如果没有请google):

  • react-hooks
  • react context
  • todomvc
  • redux

状态管理

当提到状态管理我们首先都会想到Redux,在react项目中这几乎已经成为事实标准。Redux的优点无需多说,然而很多时候像Redux的作者说的一样,你可能不需要Redux。 在某些场景中可能context api就可以满足你的需求。在实际使用中,你可能发现很多地方和Redux的设计不谋而合,实际上Redux底层也使用了Context Api.

闲话少说,开始上代码!

代码结构

├── TodoContext.jsx // context
├── TodoProvider.js // context provider
├── components // react components
├── index.jsx
├── stores
│   ├── reducer.js  // data store reducer
│   └── util.js
└── useTodo.js  // customer hooks

React Context API

为了实现状态管理,我们创建一个context来保存todo数据的状态:

  • TodoContext
export const TodoContext = React.createContext(undefined);

这样我们就可以在我们的React组件中使用它,这里假设我们有一个todoState对象,下面会讲到如何生成它:

const App = {children} => (
  <TodoContext.Provider value={todoState}>
    {children}
  </TodoContext.Provider>
);

现在我们创建了Context,在组件中也引入了,那么如何在子组件中使用context,以及如何传入初始状态,和修改状态呢? 我们来看一下如何结合React Hooks的方法来使用context。

React Hooks

首先我们来封装一个Provider对象,这个provider对象接受一个useReducer的执行结果,也就是上文提到的todoState对象。可以理解为一个[data, dispatcher].这里的state和dispatcher的概念和Redux中非常相似。

  • TodoProvider
export const TodoProvider = ({ reducer, initialState, children }) => (
    <TodoContext.Provider value={useReducer(reducer, initialState)}>
        {children}
    </TodoContext.Provider>
);
  • useReducer

看到这里越来越熟悉,这不就是redux中的reducer嘛 ? 没错,reducer的作用就是根据不同的action和payload的组合,更新state中的数据。

export const reducer = (state, action) => {
    const { id, text } = action.payload || { id: undefined, text: undefined };
    const { todos, visibilityFilter } = state;
    switch (action.type) {
        case "ADD_TODO":
            return {
                todos: [
                    {
                        id: Math.random()
                            .toString(16)
                            .substring(2),
                        completed: false,
                        text
                    },
                    ...todos
                ],
                visibilityFilter
            };
        case "DELETE_TODO":
            return {
                todos: todos.filter(todo => todo.id !== id),
                visibilityFilter
            };
        // ....... 略

有了TodoProvider,现在App.tsx中引用方式变成如下,同时我们在这里传入了initState。

const initialState = {
    todos: [
        {
            text: "React Hooks",
            completed: false,
            id: 0
        },
        {
            text: "Context",
            completed: true,
            id: 1
        }
    ],
    visibilityFilter: "All"
};
const App = () => (
    <TodoProvider initialState={initialState} reducer={reducer}>
        <div>
            <Header />
            <MainSection />
        </div>
    </TodoProvider>
);
  • useTodo

有了state和reducer方法,怎么在组件中使用他们呢? 换句redux的话说如何把state和actionDispatcher和组件connect起来? 答案是useContext!这里我们创建了一个custom hooks,任何使用我们想使用todoState的时候,可以直接使用useTodo。

export const useTodo = () => useContext(TodoContext);

在组件中的用法:

// 引入    
    const [{ todos, visibilityFilter }, dispatch] = useTodo();

    // 创建新TODO
    dispatch({
        type: "ADD_TODO",
        payload: { text }
    });

相关推荐