Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何使用useReducer Hook #102

Open
husky-dot opened this issue Aug 28, 2019 · 0 comments
Open

如何使用useReducer Hook #102

husky-dot opened this issue Aug 28, 2019 · 0 comments

Comments

@husky-dot
Copy link
Owner

作者:Dave Ceddia

译者:前端小智

来源:daveceddia

为了保证的可读性,本文采用意译而非直译。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

这个礼拜《大迁世界》有抽奖活动,奖品:专栏 《左耳听风》 x3, 技术书 x5,欢迎关注回复:抽奖

看到“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.typeadd 时,它返回一个包含所有旧元素的新数组,以及最后的新元素。

这里有一点需要注意的是,咱们使用数组的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

原文:

https://www.robinwieruch.de/react-usereducer-hook/

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant