[译] React Hooks 越来越火了,它会取代传统的 Redux 吗?

5,454 阅读6分钟

前言

React Hooks 自推出之后,收到了不少追捧, 很多问题也随之而来。

本文就其中的一个话题展开讨论:React Hooks 是否会取代传统的Redux ?

我认为: 不会.

在我看来,相比于传统的Class Component, Hooks 并没有提供什么新的状态功能,只不过是对原有的 API 做了增强。

相比之前,Hoos 更加简洁,也提升了原生 API 的可用性,适用的场景也越来越多。

为了阐明我的观点, 我们先做一些回顾。


Redux 是什么

Redux 是一个 可预测的状态管理工具,可以轻松集成在 React 应用中。 它有很多优点, 比如:

  • 单一数据源
  • 数据共享
  • 事务状态
  • 将数据状态I/O和副作用相隔离
  • 状态回朔, 又称 时光机
  • 一系列辅助工具带来的强大调试能力

总的来说, Redux 提供了应对大型应用的代码组织和调试能力,在程序出错时, 能帮你快速定位问题。

Hooks 是什么

在下的这边文章科普了Hooks的一些基础功能, 感兴趣的可以看一下。

[全面了解 React 新功能: Suspense 和 Hooks ][segmentfault.com/a/119000001…]

Hooks 的主要优点:

  • 可以在函数式组件中定义数据状态,也能通过一些Hooks来模拟生命周期方法。
  • 逻辑复用;可以把公共逻辑抽象成一个个单独的Hook, 从传统的面向生命周期编程 转变为面向业务逻辑编程。
  • 共享公共行为,类似Render Props.

两家各有所长,好在现在有 react-redux Hooks(react-redux.js.org/next/api/ho…) 和 Reducer Hook (reactjs.org/docs/hooks-…) 这样的工具,我们就没有必要纠结两者之间如何选择,雨露均沾, 美滋滋。

Hooks 带来了那些变化

  • 改变了我们编写组件的方式, 有了更多选择,你可以抛弃生命周期方法, 拥抱Hooks。
  • Render Props 这种模式也有了更好的归宿

Hooks 不能取代哪些技术

  • Redux
  • HOC
  • 容器组件视图组件之间的隔离, 分离纯逻辑和视觉效果, 更易于测试。

何时使用Hooks

任何时候你都可以使用Redux 来管理状态,只要你喜欢。 但是如果你的应用足够简单,只包含单个视图,需要一个地方临时保存状态,不需要和其他组件共享数据,或者甚至都没有异步I/O都没有(有也无所谓)。 这时候就到Hooks大显身手了,这些情景下用Redux, 当然也可以,不过这种做法叫用牛刀杀鸡鸡。

来看个例子:

import React, { useState } from 'react';
import t from 'prop-types';
import TextField, { Input } from '@material/react-text-field';

const noop = () => {};

const Holder = ({
  itemPrice = 175,
  name = '',
  email = '',
  id = '',
  removeHolder = noop,
  showRemoveButton = false,
}) => {
  const [nameInput, setName] = useState(name);
  const [emailInput, setEmail] = useState(email);
const setter = set => e => {
    const { target } = e;
    const { value } = target;
    set(value);
  };
return (
    <div className="row">
      <div className="holder">
        <div className="holder-name">
          <TextField label="Name">
            <Input value={nameInput} onChange={setter(setName)} required />
          </TextField>
        </div>
        <div className="holder-email">
          <TextField label="Email">
            <Input
              value={emailInput}
              onChange={setter(setEmail)}
              type="email"
              required
            />
          </TextField>
        </div>
        {showRemoveButton && (
          <button
            className="remove-holder"
            aria-label="Remove membership"
            onClick={e => {
              e.preventDefault();
              removeHolder(id);
            }}
          >
            &times;
          </button>
        )}
      </div>
      <div className="line-item-price">${itemPrice}</div>
      <style jsx>{cssHere}</style>
    </div>
  );
};

export default Holder;

上面的例子 使用 useState 来跟踪表单中的 nameemail

const [nameInput, setName] = useState(name);
const [emailInput, setEmail] = useState(email);

你可能会注意到还有一个 removeHolder ,这个action creator 来自 Redux 。这种模式下,各种方法都可以混合搭配。

在 Hooks 出现之前, 可以使用 local state 保存状态和更新状态 用以应对这种情况。如果是我写这个的话, 我更倾向于把它塞到Redux中, 再通过Prop去取状态吧。

反正到现在, 我做过的所有React应用, 都用到了Redux,原则也很简单:

组件状态用组件状态,应用状态用 Redux。 各司其职, 相得益彰。

何时使用Redux

另一个常见的疑问是, 我应该把所有的数据和状态都放在Redux吗? 如果不这么做的话, 是不是就无法使用时间旅行?

答案是不会的。

因为应用中有很多状态是临时的,种种局限不足以为日志遥测或者时间旅行提供足够多的信息。 除非你在做的是一个具有协同能力的文本编辑器(比如我之前参与的Lark Docs, 还有腾讯文档等),可以把用户的每一个操作,每一个changeSet 的数据都保存起来, 包括光标位置等信息。

每次你往Redux 里添加数据的时候, 随之而来的是一层抽象和额外的复杂度。

换言之, 每次你要用Redux的时候, 要知道自己为啥需要用到它。 如果你的应用有如下需求, 那就可以考虑使用Redux:

  • 需要保存或者加载状态
  • 跨组件共享状态
  • 需要与其他组件共享业务逻辑或数据处理过程

还是那个例子:

访问链接: tddday.com/

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { compose } from 'ramda';
import page from '../../hocs/page.js';
import Purchase from './purchase-component.js';
import { addHolder, removeHolder, getHolders } from './purchase-reducer.js';
const PurchasePage = () => {
  // You can use these instead of
  // mapStateToProps and mapDispatchToProps
  const dispatch = useDispatch();
  const holders = useSelector(getHolders);
const props = {
    // Use function composition to compose action creators
    // with dispatch. See "Composing Software" for details.
    addHolder: compose(
      dispatch,
      addHolder
    ),
    removeHolder: compose(
      dispatch,
      removeHolder
    ),
    holders,
  };
return <Purchase {...props} />;
};
// `page` is a Higher Order Component composed of many
// other higher order components using function composition.
export default page(PurchasePage);

这个组件并不处理任何DOM, 是一个纯展示型的组件,并用 React-Redux hooks API 连接到 Redux 上。

之所以用到 Redux,是因为其他部分需要这个表单的数据。它的状态不是本地化到单个组件中,而是在组件之间共享;

Redux 允许我们干净地将副作用与其他组件逻辑分离开来,不需要我们模拟 I/O 服务。

相比 redux-thunk,我更喜欢使用 redux-saga 的原因就是后者的隔离功能)。

为了在这个用例上追赶 Redux 的脚步,React 的 API 需要提供副作用隔离功能。

Redux 是一种架构

Redux 与状态管理库有着很大区别。但本质上,也是 Flux 架构 的一个子集。

与库相比,Redux 向来更接近一种架构和非强制性的约定(convention)。

事实上,Redux 的基本实现只需要几十行代码。

这也是 Redux 的一大好处。如果你想多用一些本地组件状态和 hook API,不想把所有内容都塞到 Redux 里,那也完全没问题。

React 提供了一个 useReducer hook,可以用它接入你的 Redux 风格的 Reducer。这对不常见的状态逻辑、依赖状态等内容非常有用。

如果你的用例是要将临时状态装入单个组件,也可以使用 Redux 架构,但要用 useReducer hook 取代 Redux 来管理状态。

如果你后面需要维持或共享这个状态, 就需要把它保存到Redux里了。

结语

Hooks 并不会替代传统的 Redux, 它的出现为我们编写组件提供了更多的灵活性, 更多的可能, 希望各位FEer 都能及时拥抱这个新特性, 找到自己最喜欢的开发姿势~

行文粗浅, 若有疏漏, 还请指正。

附原文链接: medium.com/javascript-…