useReducer的使用以及与useState、useImmerReducer的对比使用

前言

对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会影响代码的可读性。这种情况,可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数就是reducer。

使用

useReducer(reducer, initialArg, init?)

参数

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

返回值

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

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

注意事项

  • useReducer 是一个 Hook,所以只能在 组件的顶层作用域 或自定义 Hook 中调用,而不能在循环或条件语句中调用。如果你有这种需求,可以创建一个新的组件,并把 state 移入其中。
  • 严格模式下 React 会 调用两次 reducer 和初始化函数,这可以帮助你发现意外的副作用。这只是开发模式下的行为,并不会影响生产环境。只要 reducer 和初始化函数是纯函数(理应如此)就不会改变你的逻辑。其中一个调用结果会被忽略。

dispatch 函数

useReducer 返回的 dispatch 函数允许你更新 state 并触发组件的重新渲染。它需要传入一个 action 作为参数:

const [state, dispatch] = useReducer(reducer, { age: 42 });



function handleClick() {

  dispatch({ type: 'incremented_age' });

  // ...

React 会调用 reducer 函数以更新 state,reducer 函数的参数为当前的 state 与传递的 action。

参数
  • action:用户执行的操作。可以是任意类型的值。通常来说 action 是一个对象,其中 type 属性标识类型,其它属性携带额外信息。
返回值

dispatch 函数没有返回值。

注意
  • dispatch 函数 是为下一次渲染而更新 state。因此在调用 dispatch 函数后读取 state 并不会拿到更新后的值,也就是说只能获取到调用前的值。
  • 如果你提供的新值与当前的 state 相同(使用 Object.is 比较),React 会 跳过组件和子组件的重新渲染,这是一种优化手段。虽然在跳过重新渲染前 React 可能会调用你的组件,但是这不应该影响你的代码。
  • React 会批量更新 state。state 会在 所有事件函数执行完毕 并且已经调用过它的 set 函数后进行更新,这可以防止在一个事件中多次进行重新渲染。如果在访问 DOM 等极少数情况下需要强制 React 提前更新,可以使用 flushSync

创建Reducer

const [state, dispatch] = useReducer(reducer, initdata)

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

  1. 当前的 state,首次渲染时为你提供的 初始值
  2. dispatch 函数,让你可以根据交互修改 state

为了更新dom,使用一个表示用户操作的 action 来调用 dispatch 函数:

function handleClick() {
  dispatch({ type: 'incremented_age' })
}

React 会把当前的 state 和这个 action 一起作为参数传给 reducer 函数,然后 reducer 计算并返回新的 state,最后 React 保存新的 state,并使用它渲染组件和更新 UI。

实现reducer函数

function reducer(state, action) {
  // ...
}

处理 state 的逻辑一般会使用 switch 语句来完成。在 switch 语句中通过匹配 case 条件来计算并返回相应的 state。

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

action 可以是任意类型,不过通常至少是一个存在 type 属性的对象。也就是说它需要携带计算新的 state 值所必须的数据。

function Form() {
  const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }
  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    });
  }
  // ...

action 的 type 依赖于组件的实际情况。即使它会导致数据的多次更新,每个 action 都只描述一次交互。state 的类型也是任意的,不过一般会使用对象或数组

注意

state 是只读的。即使是对象或数组也不要尝试修改它。

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      //  不要像下面这样修改一个对象类型的 state:
      state.age = state.age + 1;
      return state;
    }
 	}
}

正确的做法是返回新的对象:

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // ✅ 正确的做法是返回新的对象
      return {
        ...state,
        age: state.age + 1
      };
    }
  }
}

如果想直接修改数据,可以使用Immer,如下实例中说明了Immer的使用。

实例

点击添加,新增一行

点击编辑,字符拼接"new"

点击删除,删除行

useReducer的使用以及与useState、useImmerReducer的对比使用_第1张图片

使用 useState 实现

使用 useState 实现,可以发现代码量偏多,如果单文件出现多种操作,代码可读性变差。

import { useState } from "react"

function App(){
  const [list,setList] = useState([
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  const addHandler = () => {
    setList([...list,{id:4,text:"ddd"}])
  }
  const editHandler = (id) => {
    setList(list.map(item=>{
      if(item.id === id){
        return {...item,text:"new" + item.text}
      }else{
        return item
      }
    }))
  }
  const delHandler = (id) => {
    setList(list.filter(item=>{
      if(item.id === id){
        return false
      }else{
        return true
      }
    }))
  }
  return (
  	<div>
      <button onClick={() => addHandler()}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button onClick={() => editHandler(item.id)}>编辑</button>
              <button onClick={() => delHandler(item.id)}>删除</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

使用 useReducer 实现

使用 useReducer 进行统一管理,可以发现代码可读性提高了。

import { useReducer } from "react"

// 由着reducer函数完成外部逻辑的统一处理
function listReducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, { id: +new Date(), text: "ddd" }]
    case "edit":
      return state.map((item) => {
        if (item.id === action.id) {
          return { ...item, text: "new" + item.text }
        } else {
          return item
        }
      })
    case "remove":
      return state.filter((item) => {
        if (item.id === action.id) {
          return false
        } else {
          return true
        }
      })
  }
}

function App(){
  const [list,listDispatch] = useReducer(listReducer,[
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  return (
  	<div>
      <button onClick={() => listDispatch({ type: "add" })}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => listDispatch({ type: "edit", id: item.id })}
              >
                编辑
              </button>
              <button
                onClick={() => listDispatch({ type: "remove", id: item.id })}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

使用 useImmerReducer 实现

使用 useImmerReducer,可以更加方便的处理复杂数据。

import { useImmerReducer } from "use-immer"

// 由着reducer函数完成外部逻辑的统一处理
function listReducer(draft, action) {
  switch (action.type) {
    case "add":
      draft.push({ id: +new Date(), text: "ddd" })
      break
    case "edit":
      const value = draft.find((item) => item.id === action.id)
      value.text = "new" + value.text
      break
    case "remove":
      const index = draft.findIndex((item) => item.id === action.id)
      draft.splice(index, 1)
      break
  }
}

function App(){
  const [list,listDispatch] = useImmerReducer(listReducer,[
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  return (
  	<div>
      <button onClick={() => listDispatch({ type: "add" })}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => listDispatch({ type: "edit", id: item.id })}
              >
                编辑
              </button>
              <button
                onClick={() => listDispatch({ type: "remove", id: item.id })}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

总结

当组件里有多个状态更新逻辑,并且有些是有一定关联性的,那么多个useState会降低代码可读性,为解决这个问题,我们可以将多个状态更新逻辑整合到一个外部函数,这个函数就是reducer。reducer的state是只读的,不可修改,如果想直接操作可以使用Immer。

你可能感兴趣的:(react,1024程序员节,javascript,前端,react.js)