阅读 3282

【译】不要再问我React Hooks能否取代Redux了

原文地址:Stop Asking if React Hooks Replace Redux

许多同事一直问我一些类似的问题:

“如果我们在项目中使用hooks,我们是否还需要Redux?”

“React Hooks会不会使Redux太过时了?我能不能用Hooks来做所有Redux能做的事呢?”

在Google中搜索会发现,大家经常问这些问题。

“React Hooks是否会取代Redux?”,最简单的回答是“不一定”。

更细致但礼貌的答案是“嗯,那取决于你正在做的项目类型”。

我更倾向于告诉大家的答案是“我不确定你是否知道你在说什么”。有几个原因可以说明,为什么“React Hooks是否会取代Redux”是一个本质上有缺陷的问题。首先:

Redux一直是非强制性的

通过Dan Abramov(Redux的创造者之一)的一篇文章【You Might Not Need Redux】可以看出,如果你不需要使用它,则无需替换任何东西。

Redux是一个JavaScript库,并且如果你用的是React(另一个JavaScript库),那么为了使用Redux,你还需要在应用中加载一个React-Redux的JavaScript库。在项目中使用依赖库会增加打包体积,这会增加你应用的加载时间。基于这个原因,你不应该使用一些库,像jQuery、Redux、MobX(另一个状态管理库),甚至是React,除非你有明确的理由要使用它们。

当大家问到“是否hooks会替代Redux”,他们似乎经常觉得,他们的React应用需要使用其中一种。事实并非如此,如果你正在写的应用没有很多状态需要被储存,或者你的组件结构很简单,可以避免过度的prop传递,以及基于React本身提供的特性,你的状态已经足够可控了,不管有没有hooks,这些情况使用状态管理就没有多大意义了。

即使你确实有许多的状态,或者有像老树根一样扭曲分叉的React组件结构,你仍然不需要状态管理库。Prop传递可能很麻烦,但是React给了你许多状态管理选项,并且hooks绝对可以帮你很好地组织状态。Redux是一个轻量级的库,但是它的设置很复杂,增加了打包体积,并且很多地方需要权衡。有很多原因可以说明,为什么你应该选择不在项目中使用它,并且这些原因很有说服力。

你并不总是需要Redux,这也是在说,你依然有许多理由去使用它的。如果你的项目在一开始就使用了Redux,那么它可能是一个很好的理由,无论它是否做了这些:组织(应用状态的可预测性、单一的数据流,在复杂的应用中很有用)、中间件、Redux的强有力的开发工具和调试能力。如果你有使用Redux的理由,它不会因为React Hooks变得无效。如果你之前需要Redux,那么你现在仍然需要。这是因为:

React Hooks和Redux并没有试图解决同样的问题

Redux是一个状态管理库,Hooks是React最近更新的部分特性,让你的函数组件可以做类组件能做的事情。

所以不使用类组件来写React应用突然会让状态管理库变得过时了呢?

当然不会!

通过文档可以看出,React Hooks被开发出来主要是这三个理由:

  • 难以复用类组件之间的逻辑
  • 生命周期中经常包含一些莫名其妙的不相关逻辑
  • 类组件难以被机器和人理解

注意,没有一条理由的动机直接表明要做一些与状态管理相关的事情。

话说如此,React Hooks确实提供了一些选择去管理应用的状态。尤其是useStateuseReduceruseContext方法,提供来新的方式去维护你的状态,这被证明比先前React提供的选项更好、更有条理。

但是这些hooks并不是什么新东西或神奇的东西,并且它们也没有使状态管理过时,因为事实是:

React Hooks并没有让你的应用可以做一些以前做不到的事情

那就对了,你现在可以写函数组件来做一些以前只能用类组件来做的事情,但是这些函数组件并不能做一些类组件做不到的事情,除了可以更好地组织和复用代码的能力。它们不一定让你的应用更好,而是让开发者的体验更好。

useStateuseReducer只是管理组件状态的方法,并且它们的工作原理同类组件的this.statethis.setState是一样的,你仍然需要传递你的props。

useContext是大家认为在Redux板上钉钉的特性,因为它可以让你在组件之间共享应用的状态,而不需要通过prop传递,但是它也没有真正的做任何新的事情。context API现在是React的一部分,useContext仅仅是让你不用<Consumer>包裹也可以使用context。并且有一些开发这用context来管理整个应用的状态,这不是设计context的目的。通过文档可以看出:

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

Context是为了共享数据而被设计出来的,可以认为是React组件树的“全局”,比如当前已授权的用户、主题或者首选的语言。

换句话说,就是那些预计不会频繁更新的东西。

文档中也建议有节制地使用context,因为“它会使得组件难以复用”。他们也提醒开发者,如果开发者不小心,context很容易触发不必要的重复渲染。

我见过项目成功地使用React Context来管理应用状态,这是有可能的,也不失为一种选择。但是状态管理并不完全是context被设计出来去做的事情,而且Redux和其他状态管理库被设计出来就是为了处理这种特定的目的。

此外,React Hooks也绝不意味着Redux的消亡,因为如果你看一眼React-Redux最近更新的文档,你会明白:

React-Redux也有自己的hooks

没错,React Hooks正在帮助React-Redux恢复活力并移除来它的一些痛点,与“替代”的说法相差甚远。

我在另一篇文章中对React-Redux进行了深入研究,这里要说的重点。在hooks之前,你必须定义mapStateToPropsmapDispatchToProps两个函数,并且用connect包裹你的组件来创建一个高阶组件,它会传递dispatch方法和部分Redux贮存的状态,这些状态是你在mapping函数中指定作为props传递到组件中的。

让我们来看一个非常简单的计数器应用的例子(太简单甚至都不需要Redux,但是这里主要是为了展示一些信息)。假设我们已经定义了Redux store和incrementdecrement两个action creator(完整的源码在这里)。

import React from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions/actions';

class App extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const {count, increment, decrement} = this.props;

    return (
      <div>
        <h1>The count is {count}</h1>
        <button onClick={() => increment(count)}>+</button>
        <button onClick={() => decrement(count)}>-</button>
      </div>
    );
  }
}

const mapStateToProps = store => ({
  count: store.count
});

const mapDispatchToProps = dispatch => ({
  increment: count => dispatch(actions.increment(count)),
  decrement: count => dispatch(actions.decrement(count))
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
复制代码

太令人烦恼了!如果我们不必包裹组件到高阶组件中,就可以让组件取到Redux store的值,这样不是更友好吗?是的,这就是hooks出现的原因。Hooks就是为了复用代码和消除由于高阶组件产生的“嵌套地狱”。下面是一个相同的组件,使用React-Redux hooks转换成函数组件。

import React from 'react';
import * as actions from '../actions/actions';
import {useSelector, useDispatch} from 'react-redux';

const App = () => {
  const dispatch = useDispatch();
  const count = useSelector(store => store.count);

  return (
    <div>
      <h1>The count is {count}</h1>
      <button onClick={() => dispatch(actions.increment(count))}>+</button>
      <button onClick={() => dispatch(actions.decrement(count))}>-</button>
    </div>
  );
}

export default App;
复制代码

是不是很漂亮?简而言之,useSelector让你可以保存部分Redux store的值到你的组件。useDispatch更简单,它仅仅为你提供了一个dispatch函数,你可以用它来发送状态更新到Redux store。最棒的是,你不再需要写这些丑陋的mapping函数和用connect函数来包裹组件。现在,一切都很好地包含在你的组件中,它更简洁,因此更容易阅读,并且更有条理。重点是:

没有必要比较React Hooks和Redux孰优孰劣

毫无疑问,这两项技术可以很好地互补。React Hooks不会替代Redux,它们仅仅为你提供来新的、更好的方式去组织你的React应用。如果你最终决定使用Redux来管理状态,可以让你编写更好的连接组件。

所以,请不要再问“React Hooks是否会取代Redux?”。

相反,开始问自己“我正在制作什么样的应用?我需要什么样的状态管理?Redux可以用吗,还是有些过度使用呢?hooks可以用吗,还是应该用类组件?如果我决定使用Redux和React Hooks(或者MobX和React Hooks,或者Redux和jQuery,不用React——这些都是有效的选择,取决于你正在做的事情),那么我怎样可以使这些技术互补并且和谐共处呢?”。