useContext 与 useReducer 的组合使用

0 阅读4分钟

前言

在讲useReducer之前需要介绍一下useState这个hooks函数。useState用于数据管理,响应式数据管理,允许我们在函数组件中存储状态。

随着应用逐渐复杂,我们经常发现useState在管理复杂的状态逻辑时显得有些力不从心。这时,React为我们提供的另一个更为强大的hook——useReducer——可以帮助我们优雅地处理复杂状态。

useReducer允许我们使用 action 和 reducer 的方式来组织复杂的状态逻辑,使其变得更加清晰和模块化,弥补了useState的局限性。

今天让我们用useReducer+useContext来实现一个todosList功能。

了解一下什么是useReducer?

useReducer

useState相似,useReducer也是 React 的 Hook,而且也只能放在组件最顶层使用。与前者不同的地方在于,它是通过 action 来更新状态的,使状态更新逻辑更具可读性。

useReducer接受三个参数:

  • reducer:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
  • initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
  • 可选参数 init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg
什么是纯函数?

纯函数(Pure Function)是函数式编程中的一个核心概念,它指的是那些在同样的输入下总是产生相同输出的函数,并且不会产生任何副作用。具体来说,纯函数具有以下两个主要特性:

  1. 确定性:给定相同的输入参数,纯函数总是返回相同的结果。这意味着纯函数的输出仅取决于它的输入参数,不受外部状态(比如全局变量、数据库查询或网络请求等)的影响。
  2. 无副作用:纯函数不会修改任何外部状态或数据,也不会进行任何形式的I/O操作,例如文件系统操作、网络请求或打印到控制台等。它们仅仅根据输入参数计算并返回结果。

useReducer返回两个参数:

useReducer 返回一个由两个值组成的数组:

  1. 当前的 state。初次渲染时,它是 init(initialArg) 或 initialArg (如果没有 init 函数)。
  2. dispatch 函数。用于更新 state 并触发组件的重新渲染。
const [state, dispatch] = useReducer(reducer, initialArg, init?)

当你使用 useReducer 时,useReducer 返回一个数组,第一个元素是当前的状态 (state),第二个元素是一个可以用来触发状态更新的 dispatch 函数。通过解构赋值,我们可以方便地将这两个值分别赋给 statedispatch 变量。

实现todoList

1.创建全局上下文

export const TodoContext = createContext(null);

2.创建自定义hooks函数useTodos ,其主要作用是封装待办事项(todos)的状态管理和操作逻辑,方便在不同组件中复用。

const initialTodos =[
    {
        id:1,
        text:'学习React',
        done:false
    }
]
export function useTodos(initial=initialTodos) {
    const [todos,dispatch] = useReducer(todoReducer,initial)

    const addTodo = text => dispatch({type: 'ADD_TODO',text})
    const toggleTodo =(id)=> dispatch({type: 'TOGGLE_TODO',id})
    const removeTodo =(id)=> dispatch({type: 'REMOVE_TODO',id})

    return {
        todos,
        addTodo,
        toggleTodo,
        removeTodo
    }
}

3.在App.jsx中,用创建好的上下文包裹子组件,这样就不需要传递数据了,value 属性指定了要共享的数据,这里共享的是 todosHook 对象。todosHook 包含当前的待办事项列表 todos 和三个操作函数 addTodo 、 toggleTodo 、 removeTodo 。

function App() {
  const todosHook=useTodos();
  return (
      <TodoContext.Provider value={todosHook}>
         <h1>Todo App</h1>
         <AddTodo />
         <TodoList />
      </TodoContext.Provider>
  )
}

4.创建一个纯函数,用来出来todoReducer用于根据不同的 action 来更新待办事项的状态。reducer 接收两个参数:

  • state :当前的待办事项状态,通常是一个数组。
  • action :一个对象,包含 type 属性(表示操作类型)和其他必要的数据。
function todoReducer(state,action){
    switch(action.type){
        case 'ADD_TODO':
            return [...state,{
                    id:Date.now(),
                    text:action.text,
                    done:false
                }]
        case 'TOGGLE_TODO':
            return state.map(todo =>
                todo.id === action.id ?{...todo,done:!todo.done}:todo
            );
        case 'REMOVE_TODO':
            return state.filter(todo => todo.id !== action.id);
        default:
            return state;
    }
}

5.创建一个自定义hooksuseTodoContext,使用全局上下文.创建这么多自定义hooks函数,就是为了让组件内部更加干净,更好管理。

export function useTodoContext(){
    return useContext(TodoContext);
}

6.完成添加todo的功能

const AddTodo =()=>{
    const [text,setText]=useState('');
    const { addTodo }=useTodoContext();// 跨层级
    const handleSubmit=(e)=>{
        e.preventDefault(); // 阻止表单默认行为
        if(text.trim()){ // 去空格
            addTodo(text.trim()); // 调用addTodo方法添加任务
            setText(''); // 清空输入框
        }
    }
    return (
        <form onSubmit={handleSubmit}>
            <input 
            type="text" 
            value={text}
            onChange={(e)=>{
                setText(e.target.value)
            }}
            />
            <button type="submit">Add</button>
        </form>
    )
}

7.完成展示todoslist的功能

const TodoList = () => {
    const {
        todos,
        toggleTodo,
        removeTodo,
    } = useTodoContext();
    
    return (
        <ul>
            {
                todos.map((todo)=>(
                    <li key={todo.id}>
                        <span
                          onClick={()=>toggleTodo(todo.id)}
                          style={{textDecoration:todo.done ? 'line-through':'none'}}
                        >
                        {todo.text}
                        {/* {localStorage.getItem('text')} */}
                        </span>
                        <button onClick={()=>removeTodo(todo.id)}>remove</button>
                    </li>
                ))
            }
        </ul>
    )
}

总结

  • useReducer 核心 :包含响应式状态管理、使用纯函数 reducer 规定状态改变规则、初始值 initValue 以及通过 dispatch 派发 action 对象。
  • useContext 作用 :用于跨层次共享状态,借助 createContext 创建上下文, Context.Provider 提供状态, useContext 消费状态。
  • 组合使用 :将 useContext 和 useReducer 结合,可实现跨层级的全局状态管理,应用场景包括主题、登录状态、待办事项等。
  • 自定义 Hook :介绍了组件渲染与 Hook 状态管理的关系,以及 hook 分别与 useContext 、 useReducer 组合使用在全局应用级别状态管理中的作用。