React useReducer()Hook的简易指南

165 阅读7分钟

React useReducer()钩的简易指南

如果你使用过useState() 钩子来管理非琐碎的状态,比如一个项目列表,你需要在状态中添加、更新和删除项目,你可能已经注意到,状态管理逻辑占据了组件主体的很大一部分。

这是一个问题,因为React组件本质上应该包含计算输出的逻辑。但是状态管理逻辑是一个不同的问题,应该在一个单独的地方管理。否则,你就会得到一个混合了状态管理和渲染逻辑的地方,这就很难阅读、维护和测试了。

为了帮助你分离这些关注点(渲染和状态管理),React提供了钩子useReducer() 。该钩子通过将状态管理从组件中提取出来来实现。

让我们看看useReducer() 这个钩子是如何工作的。作为一个很好的奖励,你会在帖子中发现一个真实的例子,它对理解还原器的工作原理有很大帮助。

目录

  • 1.useReducer()
  • 2.实现一个秒表
  • 3.减速器心理模型
  • 4.总结

1.useReducer()

useReducer(reducer, initialState) 钩子接受2个参数:还原器函数和初始状态。然后,该钩子返回一个包含2个项目的数组:当前状态和分配函数。

jsx

import { useReducer } from 'react';
function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const action = {
    type: 'ActionType'
  };
  return (
    <button onClick={() => dispatch(action)}>
      Click me
    </button>
  );
}

现在,让我们来解读一下初始状态动作对象调度还原器这些术语的含义。

A.初始状态

初始状态是指状态被初始化时的值。

例如,在计数器状态的情况下,初始值可以是:

javascript

// initial state
const initialState = { 
  counter: 0 
};

B.行动对象

动作对象是一个描述如何更新状态的对象。

通常情况下,动作对象会有一个属性type - 一个字符串,描述还原器必须做什么样的状态更新。

例如,一个增加计数器的动作对象可以如下所示:

javascript

const action = {
  type: 'increase'
};

如果动作对象必须携带一些有用的信息(又称有效载荷)被还原器使用,那么你可以给动作对象添加额外的属性。

例如,这里有一个动作对象,将一个新的用户添加到用户数组的状态中。

javascript

const action = {
  type: 'add',
  user: { 
    name: 'John Smith',
    email: 'jsmith@mail.com'
  }
};

user 是一个持有要添加的用户信息的属性。

C.调度函数

Dispatch是一个特殊的函数,用于调度一个动作对象。

调度函数是由useReducer() 钩子为你创建的。

javascript

const [state, dispatch] = useReducer(reducer, initialState);   

每当你想更新状态时(通常来自事件处理程序或在完成一个获取请求后),你只需用合适的动作对象调用调度函数:dispatch(actionObject)

D.减速器函数

还原器是一个纯函数,接受2个参数:当前状态和一个动作对象。根据动作对象,还原器函数必须以不可改变的方式更新状态,并返回新的状态。

下面的reducer函数支持增加和减少一个计数器的状态。

javascript

function reducer(state, action) {
  let newState;
  switch (action.type) {
    case 'increase':
      newState = { counter: state.counter + 1 };
      break;
    case 'descrease':
      newState = { counter: state.counter - 1 };
      break;
    default:
      throw new Error();
  }
  return newState;
}

上面的reducer并不直接修改state 变量中的当前状态,而是创建一个新的状态对象存储在newState ,然后返回。

React会检查新状态和当前状态的差异,以确定状态是否被更新,所以不要直接突变当前状态。

E.连接一切

将所有这些术语连接起来,下面是使用还原器进行状态更新的工作原理。

React useReducer()

作为一个事件处理程序的结果,或者在完成一个获取请求后,你用动作对象调用调度函数。

然后React将动作对象和当前状态值重定向到reducer函数。

reducer函数使用动作对象并执行状态更新,返回新的状态。

然后React检查新的状态是否与之前的状态不同。如果状态被更新了,React会重新渲染组件,useReducer() ,并返回新的状态值:[newState, ...] = useReducer(...)

请注意,useReducer() 设计是基于Flux架构的。

如果所有这些术语听起来太抽象,那么你的感觉是正确的让我们在一个有趣的例子中看看useReducer() 如何工作。

2.实现一个秒表

我们的任务是实现一个秒表。这个秒表有3个按钮。开始、停止和复位,并有一个数字显示所过的秒数。

现在让我们来考虑如何构建秒表的状态。

有两个重要的状态属性:一个布尔值表示秒表是否运行(让我们把它命名为isRunning ),一个数字表示经过的秒数(让我们把它命名为time )。因此,这里是初始状态的样子。

javascript

const initialState = {
  isRunning: false,
  time: 0
};

初始状态表明,秒表开始时是不活动的,而且是在0 秒。

然后让我们考虑秒表应该有哪些动作对象。我们很容易发现,我们需要4种动作:启动、停止和重置秒表的运行过程,以及每一秒的时间。

javascript

// The start action object
{ type: 'start' }
// The stop action object
{ type: 'stop' }
// The reset action object
{ type: 'reset' }
// The tick action object
{ type: 'tick' }

有了状态结构,以及可能的动作,让我们使用还原函数来定义动作对象如何更新状态。

javascript

function reducer(state, action) {
  switch (action.type) {
    case 'start':
      return { ...state, isRunning: true };
    case 'stop':
      return { ...state, isRunning: false };
    case 'reset':
      return { isRunning: false, time: 0 };
    case 'tick':
      return { ...state, time: state.time + 1 };
    default:
      throw new Error();
  }
}

最后,这里是组件Stopwatch ,它通过调用useReducer() 钩子将所有东西连接在一起。

jsx

import { useReducer, useEffect, useRef } from 'react';
function Stopwatch() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const idRef = useRef(0);
  useEffect(() => {
    if (!state.isRunning) { 
      return; 
    }
    idRef.current = setInterval(() => dispatch({type: 'tick'}), 1000);
    return () => {
      clearInterval(idRef.current);
      idRef.current = 0;
    };
  }, [state.isRunning]);
  
  return (
    <div>
      {state.time}s
      <button onClick={() => dispatch({ type: 'start' })}>
        Start
      </button>
      <button onClick={() => dispatch({ type: 'stop' })}>
        Stop
      </button>
      <button onClick={() => dispatch({ type: 'reset' })}>
        Reset
      </button>
    </div>
  );
}

开始、停止和重置按钮的点击事件处理程序相应地使用dispatch() 函数来调度必要的动作对象。

useEffect() 回调里面,如果state.isRunningtruesetInterval() 定时器函数每秒钟都会派发tick动作对象dispatch({type: 'tick'})

每次reducer() 函数更新状态时,组件都会因此而重新渲染并接收新的状态。

3.还原器心理模型

为了进一步巩固你的知识,让我们看看一个与还原器工作类似的真实世界的例子。

想象一下,你是20世纪上半叶一艘船的船长。

Engine Order Telegraph

船长的舰桥有一个特殊的通讯设备,叫做发动机指令电报机(见上图)。这种通信工具用于将命令从舰桥传送到机房。典型的命令是缓慢后退功率前进停止等。

你在舰桥上,船在全速前进。你(船长)想让船以全速前进。你会走到发动机命令电报机前,把手柄调到前进全速。机房里的工程师,拥有相同的设备,看到前方全速命令,并将发动机设置为相应的状态。

发动机命令电报机调度功能,命令行动对象机房的工程师减速功能,而发动机制度状态

发动机命令电报有助于将舰桥与发动机室分开。同样地,useReducer() 钩子也有助于将渲染与状态管理逻辑分开。

4.结论

React中的useReducer() 钩子让你把状态管理和组件的渲染逻辑分开。

const [state, dispatch] = useReducer(reducer, initialState) 它接受2个参数:还原器函数和初始状态。同时,reducer返回一个包含2个项目的数组:当前状态和dispatch函数。

当你想更新状态时,只需用适当的动作对象调用dispatch(action) 。然后该动作对象被转发到更新状态的reducer() 函数。如果状态已经被reducer更新了,那么组件就会重新渲染,而[state, ...] = useReducer(...) 钩子就会返回新的状态值。

useReducer() 适合于相对复杂的状态更新(至少需要2-3个更新动作)。对于简单的状态管理,只需使用 。useState()

挑战:编写一个自定义钩子myUseState() ,其工作原理与useState() ,只是它使用useReducer() 钩子来管理状态。请在下面的评论中写出你的解决方案!

喜欢这篇文章吗?请分享!