最近有一个问题比较火,是在有了react hooks之后,我们还需要redux这种状态管理工具吗?个人的观点是:仍然需要。react hooks帮助我们更好的复用代码逻辑,但是对于复杂应用而言,仍然需要redux来管理数据流,并且redux跟hooks的配合能够更好解耦组件的逻辑。最近发布的[email protected]提供了对react hooks的支持,使用hooks将会改变过去在React应用程序中编写redux的方式。这边文章将会介绍: 为什么redux要花费很长时间才能发布对hooks的支持,它有哪些API,以及最后对迁移到hooks的一些看法。
虽然已经有第三方库提供了在hooks中使用redux的支持,但我们一直在等待redux官方支持hooks这个功能,因为现在的所有解决方案都没有比较好的性能。到目前为止,类似于useRedux或useConnect之类的库,都无法解决与性能相关的关键问题,这会导致使用hooks的组件在任何state更改时,都会重新渲染,无论我们的组件是否依赖了某个state。
主要原因是6.x.x版本的react-redux试图充分利用新的React Context API,遗憾的是它不能减少不必要的更新。这意味着,只要Context.Provider更新,那么每个Context.Consumer也必须更新。因此,一旦state更新,使用useRedux或useConnect hooks的组件将始终更新,无论这个state是否在hooks组件中用到。因此react-redux必须发布一个新的主要版本(即7.xx),在这个版本中重写了相关逻辑,将其自身与先前的Context API实现分离,并且官方提供了对hooks的支持!
最新的redux将不需要使用connect来连接组件,而是在组件中直接使用redux提供的hooks。这对于使用redux的大型应用来说是一种非常好的提升,因为现在可以实际创建自定义的可重用hooks,而无需额外的container组件。到目前为止,我们无法创建一个hooks来从redux中读取state或者dispatch actions。因此,借助自定义hooks封装redux相关的逻辑,react将不需要直接引用redux。
并且redux提供的hooks让我们不再需要使用mapStateToProps,mapDispatchToProps和connect来维护单独的container组件和UI组件,可以立即在function组件内部读取redux中的state。此外,还可以将任何现有的自定义hooks与redux集成,而不是将通过hooks创建的state,作为参数传递给其他hooks,相当于把这块的逻辑完全抽取出去了,让我们编写的组件更纯净。
尽管现在的redux对hooks支持还是alpha版本,但是我们可以在非生产环境上试试。
useSelector用于从Redux存储的state中提取值并订阅该state。这基本上类似于在hooks中实现的mapStateToProps函数,但有一些小的差异:
首先,不再提供ownProps API,并且应该使用useCallback或useMemo来通过自定义逻辑获取它们。
其次,useSelector()第二个参数也是依赖数组,跟useEffect一样。如果不提供第二个参数,每次组件更新时都会重新计算;如果提供了依赖数组,只有依赖数组对应的值变更了之后,才会触发重新计算。
接下来我们看一个实际的例子:
// 以前
import React from 'react';
import { connect } from 'react-redux';
const Component = props => {props.content};
export default connect(state => ({
title: state.title,
content: state.content
}))(Component)
// 使用redux hooks
import React from 'react';
import { useSelector } from 'react-redux';
const Component = props => {
const { title, content } = useSelector(state => ({
title: state.title,
content: state.content
}));
return {content};
除此之外,redux以前的性能优化逻辑同样保留了下来,如果当前的props跟老的props相同,则组件将不会重新渲染。
由于React Redux中使用的批处理更新的逻辑,导致同一组件中的多个useSelector()重新计算出state,只会让组件重新渲染一次。因此,我们可以自由的在组件中useSelector(),而不用担心重复渲染的情况。在上面的例子中,我们可以将单个useSelector()分成两个独立的(一个读取title,另一个读取content)useSelector(),他们在性能和渲染数量方面完全相同。
除了读取store中的state,还要能dispatch actions来更新store中的state,useDispatch就是这样一个API。只要组件需要触发redux action,那么这个钩子就是你需要的。不幸的是,mapDispatchToProps 被废弃掉了,所以每当你想要dispatch actions时,你需要使用dispatch(actionCreator())来调用它的action creator。如果我们初次使用这种方式,会显得有点不太习惯,因为以前都是通过connect HOC来调用被包装成prop的dispatch函数,但hooks的方式会为代码带来更多的清晰度。
遗憾的是,如果我们想要在事件处理函数里面dispatch actions,必须创建一个匿名函数,如:() => dispatch(actionCreator)。由于匿名函数的性质,这将在每次重新渲染时获得新的引用。因此,如果将这个匿名函数作为props传递给子组件组件,那么子组件将每次都重新渲染。为了优化性能,必须使该函数具有相同的引用,解决方案是在useCallback中创建这个匿名函数。
import React from 'react';
import { useCallback, useDispatch } from 'react-redux';
import { increaseCounterAction } from './actions';
import ExpensiveComponent from './ExpensiveComponent';
// 常用的方式
const Component = props => {
const dispatch = useDispatch();
return (
)
}
// 使用useCallback优化性能
const Component = props => {
const dispatch = useDispatch();
const handleIncreaseCounter = useCallback(
() => dispatch(increaseCounterAction()),
[dispatch]
);
return
}
useStore用于获取创建的store实例。在任何需要访问store的应用中,都可以通过usestore来获取。如果出于某种原因,比如说单元测试时,想要获取不同的store,我们可以将store通过新的contextAPI传递进组件树中,就像下面这样:
import React from 'react';
import { useStore } from 'react-redux';
import OtherProvider from './OtherProvider';
const Component = props => {
const store = useStore();
return {props.children}
}
上面谈到了使用hooks的方式来使用redux的好处,那我们应该将我们的connect() HOC转换为hooks吗?这里有几个需要权衡的点:
目前,从redux HOC到redux hooks没有一条简单的迁移方案。不能简单地将单个connect() HOC替换为单个“useConnect()” hooks,因为redux并没有提供useConnect API来让我们简单的替换掉connect。在最初的alpha版本中曾经有一个useRedux() hooks,但它很快被废弃,因为其没有提供让我们缓存action creator的能力,这就会带来性能问题。
因此,现有代码中的每一个 connect(mapStateToProps,mapDispatchToProps)HOC都需要被2个分离的hooks替换,如果其中使用ownProps,则需要对mapStateToProps进行额外的重构。通过上面的内容,我们知道,要想迁移到redux的hooks上,重构成本还是蛮大的。
无论如何,我们仍然会采取将UI 组件和container组件分离的方式。这使我们既可以重复使用各个组件,又可以通过将这两个不同类型的组件作为不同的实体来测试。另一方面,Hooks尝试将这两者合并在一起,实质上是不在区分UI组件和container组件。这是一个新趋势,始于2018年发布hooks。原因在于:大多数情况下,UI组件仅与特定container组件相关,并且组件的可重用性基本上不是问题;另一方面,测试发生了变化,以解决这个问题,很多人正在慢慢地从单元和implements测试(Enzyme)迁移到集成和用户输出测试(React测试库)
出于这些原因,我们会根据实际应用的架构,来决定是否将组件拆分成UI组件和container组件。虽然hooks可以提高可读性(不再需要检查2个位置)和可维护性(关于文件组织和结构),但是一些成熟的代码库可能希望保持现有的组件分离模式,因为它对它们更有效。
原文https://zhuanlan.zhihu.com/p/70380695?utm_source=tuicool&utm_medium=referral