许多同事一直问我一些类似的问题:
“如果我们在项目中使用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确实提供了一些选择去管理应用的状态。尤其是useState
、useReducer
和useContext
方法,提供来新的方式去维护你的状态,这被证明比先前React提供的选项更好、更有条理。
但是这些hooks并不是什么新东西或神奇的东西,并且它们也没有使状态管理过时,因为事实是:
React Hooks并没有让你的应用可以做一些以前做不到的事情
那就对了,你现在可以写函数组件来做一些以前只能用类组件来做的事情,但是这些函数组件并不能做一些类组件做不到的事情,除了可以更好地组织和复用代码的能力。它们不一定让你的应用更好,而是让开发者的体验更好。
useState
和useReducer
只是管理组件状态的方法,并且它们的工作原理同类组件的this.state
和this.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之前,你必须定义mapStateToProps
和mapDispatchToProps
两个函数,并且用connect
包裹你的组件来创建一个高阶组件,它会传递dispatch方法和部分Redux贮存的状态,这些状态是你在mapping函数中指定作为props传递到组件中的。
让我们来看一个非常简单的计数器应用的例子(太简单甚至都不需要Redux,但是这里主要是为了展示一些信息)。假设我们已经定义了Redux store和increment
、decrement
两个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——这些都是有效的选择,取决于你正在做的事情),那么我怎样可以使这些技术互补并且和谐共处呢?”。