useContext 与 useReducer 的组合使用

前言

在讲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 并触发组件的重新渲染。
jsx 复制代码
const [state, dispatch] = useReducer(reducer, initialArg, init?)

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

实现todoList

1.创建全局上下文

TodoContext.js 复制代码
export const TodoContext = createContext(null);

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

useTodos.js 复制代码
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 。

App.jsx 复制代码
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 属性(表示操作类型)和其他必要的数据。
todoReducer.js 复制代码
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函数,就是为了让组件内部更加干净,更好管理。

useTodoContext.js 复制代码
export function useTodoContext(){
    return useContext(TodoContext);
}

6.完成添加todo的功能

AddTodo.jsx 复制代码
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的功能

TodoList.jsx 复制代码
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 组合使用在全局应用级别状态管理中的作用。
相关推荐
笔COOL创始人3 分钟前
requestAnimationFrame 动画优化实践指南
前端·javascript·面试
sophie旭6 分钟前
性能监控之首屏性能监控小实践
前端·javascript·性能优化
北辰alk14 分钟前
React Consumer 找不到 Provider 的处理方案
react.js
Amumu1213820 分钟前
React 前端请求
前端·react.js·okhttp
UrbanJazzerati31 分钟前
统计学基础与数据可视化实战——基本图表(1)
面试
3824278271 小时前
JS表单提交:submit事件的关键技巧与注意事项
前端·javascript·okhttp
Kagol1 小时前
深入浅出 TinyEditor 富文本编辑器系列2:快速开始
前端·typescript·开源
小二·1 小时前
Python Web 开发进阶实战:Flask-Login 用户认证与权限管理 —— 构建多用户待办事项系统
前端·python·flask
浩瀚之水_csdn1 小时前
python字符串解析
前端·数据库·python