如何使用useReducer Hook

2019-09-05

JiangRen Mr

看到“reducer”这个词,容易让人联想到Redux,但是在本文中,不必先理解Redux才能阅读这篇文章。咱们将一起讨论“reducer”实际上是什么,以及如何利用useReducer来管理组件中的复杂状态,以及这个新钩子对Redux意味着什么?

Reducer 是什么鬼

如果你熟悉Redux或数组上中的reduce方法,你大概就知道“reducer”是什么。 如果不熟悉,“reducer”大概是一个带有2个值并返回1个值的函数这么个意思。

如果你有一系列的东西,并且想将这些东西组合成一个单独的物体。“函数式编程”中就是使用Arrayreduce函数。 例如,如果你有一个数字数组并且想得到数组中所有数字的总和,咱们就可以写一个reducer函数并将它传递给reduce,如下所示:

let numbers = [1, 2, 3];
let sum = numbers.reduce((total, number) => {
    return total + number;
},0)

如果你以前没用过这个,它可能看起来有点神秘。它所做的是为数组的每个元素调用函数,传入前一个total和当前元素 number。无论你返回什么,都会成为新的totalreduce的第二个参数(在本例中为0)是total的初始值。在本例中,reduce函数会被调用3次:

  • 调用 (0, 1) 返回 1
  • 调用 (1, 2) 返回 3
  • 调用 (3, 4) 返回 6

reduce返回6,它保存在sum中。

使用useReducer又会是什么样的?

各位花了在半篇幅来解释Arrayreduce函数,因为 useReducer 参数与 reduce 相同,并且工作方式基本一样。 useReducer接收 (state, action) => newState,并且返回了一个与当前state成对的dispatch的方法。 咱们使用 useReducer 来编写上面的求和例子。

useReducer((state, acton) => {
  return state + action
}, 0)

useReducer返回一个包含2个元素的数组,类似于useState hook。 第一个是当前状态,第二个是dispatch方法,如下所示:

const [sum, dispatch] = useReducer((state, action) => {
  return state + action
}, 0)

注意state可以是任何值,它不一定是一个对象,可以是一个数字,一个数组,或者其他任何类型。

尽管 useReducer 是扩展的 hook, 而 useState 是基本的 hook,但 useState 实际上执行的也是一个 useReducer。这意味着 useReducer 是更原生的,你能在任何使用 useState 的地方都替换成使用 useReducer

import React, { useReducer } from 'react';

function Counter() {
  // First render will create the state, and it will
  // persist through future renders
  const [sum, dispatch] = useReducer((state, action) => {
    return state + action;
  }, 0);

  return (
    <>
      {sum}

      <button onClick={() => dispatch(1)}>
        Add 1
      </button>
    </>
  );
}

点击按钮dispatch一个值为1action,该action将被添加到当前状态,然后组件使用新的状态重新渲染。

这里故意展示了,派发action没有遵循Redux的典型模式{type: "INCREMENT BY"、value: 1}或其他类似的东西。hook 的世界是一个新的世界:值得考虑的是,你是否发现旧的模式有价值并希望保留它们,或者你是否愿意更改它们。

一些更复杂的例子

再来看看更接近典型Redux reducer 的例子。创建一个组件来管理购物列表,这里看还会使用另外一个 hook:useRef

首先,导入两个 hook

import React, { useReducer, useRef } from 'react';

然后创建一个设置refreducer的组件。 ref保存对表单的引用,以便咱们可以提取其值。

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      // do something with the action
    }
  }, []);

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
          </li>
        ))}
      </ul>
    </>
  );
}

请注意,在这种情况下,咱们的“state”是一个数组。 咱们通过useReducer第二个参数将它初始化为一个空数组,并从reducer函数返回一个数组。

useRef Hook

useRef hook为DOM节点创建持久引用。 调用useRef会创建一个空的节点。它返回的对象具有current属性,因此在上面的示例中,咱们可以使用inputRef.current访问输入的DOM节点。 如果你熟悉React.createRef(),则其工作原理非常相似。

但是,useRef返回的对象不仅仅是一种保存DOM引用的方法。 它可以保存特定于此组件实例的任何值,并且它在渲染之间保持不变。

useRef可用于创建通用实例变量,就像使用具有this.whatever = valueReact类组件一样。 唯一的问题是,写入它会被视为“副作用”,因此咱们无法在渲染过程中更改它,需要在useEffect hook 中才能修改。

回到useReducer示例

我们用表单来处理用户的输入,按回车提交表彰。 现在来编写handleSubmit函数,该函数主要做的是将一个项添加到列表中,以及处理reducer中的 action

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        return [
          ...state,
          {
            id: state.length,
            name: action.name
          }
        ];
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: 'add',
      name: inputRef.current.value
    });
    inputRef.current.value = '';
  }

  return (
    // ... same ...
  );
}

reducer函数中主要判断两种情况:一种用于action.type==='add'的情况,还有就是默认下的情况。

action.type为 add 时,它返回一个包含所有旧元素的新数组,以及最后的新元素。

这里有一点需要注意的是,咱们使用数组的length作为一种自动递增的 ID 方便演示,但是对于一个真正的应用程序来说这是不可靠,因为它可能导致重复的ID和bug。(最好使用uuid这样的库,或者让服务器生成一个惟一的ID!)

当用户在输入框中按Enter键时会调用handleSubmit函数,因此咱们需要调用preventDefault以避免在发生这种情况时重新加载整页。 然后dispatch派发一个 action

删除项

现在来看看如何从列表中删除项的。

在项目中添加一个删除<button>,点击该按钮派发 它将发送一个 action type === "remove"的操作,以及要删除的项的索引。

然后咱们只需要在reducer中处理该action

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        // ... same as before ...
      case 'remove':
        // keep every item except the one we want to remove
        return state.filter((_, index) => index != action.index);
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) { /*...*/ }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
            <button
              onClick={() => dispatch({ type: 'remove', index })}
            >
              X
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}

练习:清空列表

试着添加一个功能:添加一个清空列表的按钮。

<ul>上方插入一个按钮,并为其提供一个onClick prop,派发一个action ,type 为“clear”的动作,并在 reducer 方法执行清空列表的动作。

可以在前面 CodeSandbox的基础上完成。

Redux 会死吗

大部分人看到useReducer hook, React 现在已经内置了reducer ,它有Context传递数据,所以可能会想到 Redux 会不会因此就死了,以下是原作者给出的一些看法。

作者不认为useReducer会杀死Redux。React Hook 扩展了React在状态管理方面的能力,所以会让使用 Redux的情况减少。

Redux仍然比Context + useReducer的组合做得更多,它具有Redux DevTools 用于调试,可定制中间件、,以及整个相关库生态系统,当然 Redu x在很多地方都被过度使用了,但它仍然具有强大的功能。

Redux提供了一个全局存储,可以在其中集中保存应用程序数据。useReducer本地化到特定组件。但是,没有什么可以阻止咱们使用useReduceruseContext构建自己的迷你redux 。如果你想这么做,而且符合你的需要,那就去做吧!

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

 

本文章转载自SegmentFault。

近期开课hot
logo

Follow Us

linkedinfacebooktwitterinstagramweiboyoutubebilibilitiktokxigua

We Accept

/image/layout/pay-paypal.png/image/layout/pay-visa.png/image/layout/pay-master-card.png/image/layout/pay-stripe.png/image/layout/pay-alipay.png

地址

Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)
Level 8, 11 York st, Wynyard, Sydney CBD
Business Hub, 155 Waymouth St, Adelaide SA 5000

Disclaimer

footer-disclaimerfooter-disclaimer

JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.

匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。 JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议

© 2017-2024 JR Academy Pty Ltd. All rights reserved.

ABN 26621887572