React王炸组合(Reducer-Context)

148 阅读6分钟

引言

在前面的文章小编也是花了大量时间来学习React当中的路由,差点走火入魔哈,好在最近读了一本书《走向光明》。有感兴趣的小伙伴可以去看看,对自己最近的专注学习有着相当大的提升,也是这本书籍,让小编今天来攻克React当中的难点,如何理解Reducer和Context的结合使用。接下来这篇文章耗费小编一个月功力完成,让我们赶快进入Reducer结合Context的学习吧!!!

useReducer的核心

useReducer 是 React 提供的状态管理 Hook,适用于复杂状态逻辑包含多个子值的状态对象。它通过 reducer 函数(接收 stateaction,返回新状态)来更新状态,逻辑集中且可预测。

useContext的核心

useContext是 React 提供的 Hook,用于跨层级共享状态。通过Context.Provide向组件树传递数据,子组件无需逐层传递 props 即可访问共享状态。

useContext 和 useReducer(结合开发管理)

redux的前身,跨层级全局应用将 useReduceruseContext 结合,可以构建一个轻量级全局状态管理方案,常用于替代 Redux 的简单场景。

创建两个组件结合开发项目

1.全局创建TodoContext上下文对象

import {
  createContext,
} from 'react'

// 创建了上下文
export const TodoContext = createContext(null)

在这里我们需要向全局创建全局的状态的应用管理

import{
    createContext,
    useContext
} from 'react';
export const TodoContext = createContext(null);
// 自定义hooks
export function useTodoContext(){
    return useContext(TodoContext)
}

在这里有个非常重要的一点,封装了 useContext(TodoContext)在组件中使用时,不需要每次都导入 useContext 和 TodoContext,只需调用 useTodoContext()

用法原始写法封装后写法
引入依赖import { useContext } + import { TodoContext }只需 import { useTodoContext }
使用方式useContext(TodoContext)useTodoContext()
可维护性较低更高
可读性一般更清晰
错误提示需手动检查可封装检查逻辑

在这里便需要将自己的hooks封装到src下的hooks目录当中

import{
    useContext
} from 'react'
import{
    TodoContext
}

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

全局状态当中的代码就应该将封装的函数部分删去

import {
  createContext,
} from 'react'

// 上下文
export const TodoContext = createContext(null)

2.全局获取数据

import { useState } from 'react'
import './App.css'
import {
  TodoContext
} from '@/TodoContext'

function App() {
  // const todosHook = useTodos()

  return (
    // App 状态管理
    <TodoContext.Provider value="">

    </TodoContext.Provider>
  )
}

export default App

在这边使用主函数当中的TodoContext函数为子组件当中的全局上下文当中传递数据,而useTodoContext则为在TodoContext.Provider的子组件当中使用提供的全局数据。在浏览器当中可以查看此时的组件树数据:

image.png

3.创建reducers

image.png 当我们自定义Hook的时候,实现了组件和状态的分离,再使用useContext可以使定义的Hooks里面的状态和方法在任何组件层级当中可以使用。如果需要更大规模的使用这个Hooks,即应用管理和状态管理好,我们就需要使用到useReducer来使用对它负责useTodos

// 重要作用负责状态的正确,全局复用
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
  }
}

export default todoReducer

4.创建useTodos

import {
  useReducer,
} from 'react'
import todoReducer from '@/reducers/todoReducer'

// 参数的默认值
// 解构 []=[] {} = {}
export function useTodos(initial = []) {
  return {
    todos,
    addTodo,
    toggleTodo,
    removeTodo
  }
}

返回最初的方法,原始的配方,但是这里用料变了。在这里需要使用到useReducer方法,现在需要对下面的方法当中使用函数来调用。

const [todos, dispatch] = useReducer(todoReducer,initial)

image.png

initial = []是什么?

1. initial
  • 这是一个参数名,表示“初始值”。
  • 调用这个 Hook 时可以传入一个初始的 todo 列表,比如:
    深色版本
    useTodos([
      { id: 1, text: '学习React', completed: false }
    ])
2. = []
  • 这是 默认参数值(default parameter value)
  • 如果调用时没有传入参数,initial 就会默认是一个空数组 []
  • 这样即使没有提供初始值,代码也不会出错。

5.Hooks引入

将封装好的useTodos引入根组件当中,并向value当中提供tooksHook让我们看到打印的组件效果。

import { useState } from 'react'
import './App.css'
import {
  TodoContext
} from '@/TodoContext'
import { useTodos } from './hooks/useTodos'
import AddTodo from './components/AddTodo'
import TodoList from './components/TodoList'

function App() {
  const todosHook = useTodos()


  return (
    // App 状态管理
    <TodoContext.Provider value={todosHook}>
      <h1>Todo List</h1>
      <AddTodo />
      <TodoList />

    </TodoContext.Provider>
  )
}

export default App


image.png

可以看到在TodoContext当中的value有了todosHook当中的函数方法。

6.AddTodo中的难点

import {
  useState   // 私有
} from 'react'
import { useTodoContext } from '@/hooks/useTodoContext'
const AddTodo = () => {
  const [text, setText] = useState('')
  const { addTodo } = useTodoContext() // 跨层级
  const handleSubmit = (e) => {
    e.preventDefault()
    // 全局管理

    if (text.trim()) {
      addTodo(text.trim())
      setText('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button type="submit">Add</button>
    </form>
  )
}

export default AddTodo  

如何理解这段代码当中的下面代码是这段代码当中的关键问题!!!

import { useTodoContext } from '@/hooks/useTodoContext'
  const { addTodo } = useTodoContext() // 跨层级

理解了这段代码就是理解useContext的关键问题,在根组件当中的value当中我们向全局提供了可以拿到的value数据,当我们需要在其他组件当中用到该数据时将其组件引入。 而第二段代码当中我们需要从根组件当中自己所拿到的value值当中从中解构出咱们所需要的值,并且定义表单提交时的处理逻辑,实现添加Todo的功能。

7.TodoList简单

import {
  useTodoContext
} from '@/hooks/useTodoContext'

const TodoList = () => {
  const {
    todos,
    toggleTodo,
    removeTodo
  } = useTodoContext()

  if (todos.length === 0) {
    return <p>No todos</p>
  }

  return (
    <ul>
      {
        todos.map(todo => (
          <li key={todo.id}>
            <span
              onClick={() => toggleTodo(todo.id)}
              style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
            >
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>Remove</button>
          </li>
        ))
      }
    </ul>
  )
}

export default TodoList 

image.png

useReducer.gif 在这里就可以实现跨组件拿到数据并对数据进行修改,可以发现数据的内容全都是响应式的。通过上述useReducer和useContext的实际案例的学习可以让我们更快地掌握它两结合使用的技巧。如果掌握好了React当中的这两大Hooks,可以发现写起项目来将会得心应手。

总结

  • 在写项目当中为了方便代码的简洁和复用性,需要在全局创建TodoContext上下文对象,并且封装好Hooks函数使其可以在全局当中进行引用,方便代码的复用性和简洁性
  • 将我们的根组件当中引用TodoContext组件并向全层级组件提供数据
  • 当我们在写useTodos方法时,需要使用到useReducer函数来对自定义Hooks进行应用管理和状态管理,然后在useTodos当中通过useReducer来调用创建的方法:
const [todos, dispatch] = useReducer(todoReducer, initValue)
  • 完成上述功能完善之后,需要将封装好的useTodos在根组件当中引用,并且在根组件当中向全层级组件提供数据
  • 当我们在其他子组件当中需要获取数据的时候,使用useContext来获取数据并且解构出我们所需要的值,写好逻辑函数完成功能
  • 最后就是表单的渲染,需要将我们写好的方法拿到TodoList表单当中使用,这样整体页面的渲染就算是完成了,就可以看到页面的简单实现效果了

如果大家对于useReducer和useContext还不熟悉的话,可以移步到小编写的另外两篇文章。 初识React状态管理:useReducer
React"隔空取物"-useContext 用起来就这么简单