2018 React Redux 入门教程
本文最后更新于:2020年12月30日 凌晨
吾辈已转 mobx,放弃使用 redux 了。
译文链接, 原文链接
老实说学习 Redux 真的很有挫败感,虽然 Redux 的源码很小(< 2kb),然而其文档却庞大无比,老实说让人害怕!即便吾辈看了阮一峰写的 Redux 入门教程,然而还是在第二篇就 GG 了。纵然了解了概念,然而却不知如何使用,便是如此了。。。这篇翻译过来的教程吾辈感觉还不错,所以也便是转发一下好啦
吾辈也跟着教程写了一个 Redux Demo,仅供参考。
我希望这是学习 React Redux 最简单的入门教程。
我一开始学习 Redux 的时候我想找到最浅显易懂的入门教程。
尽管有大量的资源,但是我对于 Redux 的一些概念依旧搞不清楚。
我知道什么是 state ,但是 actions, action creators 和 reducers 又是什么鬼?我被搞晕掉了。
然后我也搞不明白 React 和 Redux 是怎样结合起来的。
前端时间开始着手写 React Redux 教程的过程中我理解了许多。
我通过写这篇教程自学 Redux 的基本概念。我也希望这可以帮助所有正在学习 React 和 Redux 的人。
适用人群
如果满足下列条件,那么本教程正是你要找的:
- 良好的 Javascript, ES6, React 基础
- 你希望以最简单的方式学习 Redux
你可以学到什么
通过这篇教程你可以学到:
- Redux 是什么
- 怎样结合 React 使用 Redux
搭建 React 开发环境
看这篇教程你需要有扎实的 Javascript, ES6, React 基础。
开始之前,让我们来简单搭建一个 React 开发环境。
你可以选择 webpack 3 或者 Parcel。
如果选用 Parcel 你可以参考这个链接 Setting up React with Parcel ,或者从我的 Github 上克隆仓库:
git clone [email protected]:valentinogagliardi/minimal-react-parcel.git
如果你想用 webpack 3 可以参考这个教程 How to set up React, Webpack 3, and Babel ,或者从我的 Github 上克隆仓库:
git clone [email protected]:valentinogagliardi/minimal-react-webpack.git
什么是 state ?
理解 Redux 之前你首先需要理解什么是 state 。
如果你曾经写过 React 的 state , 应该不足为奇。
我猜你应该写过如下的 React 状态组件:
1 |
|
上述组件是基于 Javascript ES6 class 的。
每个 React 状态组件持有自己的 state 。在 React 中 state 持有数据。组件渲染这组数据展示给用户。
React 提供了一个 setState 方法来更新组件内部的 state 。
我们通常这么用:
- 从组件内部持有的 state 中获取数据渲染
- 通过 React 的 setState 方法更新 state
Redux 解决了什么问题 ?
应用规模比较小的时候,在 React 组件内部维护 state 很方便。
但在复杂一点的业务场景下这么做就不太合适了。组件中会填满各种管理更新 state 的方法,臃肿不堪。前端不应该知道业务逻辑。
那么,有没有其它管理 state 的方式呢?Redux 就是其中的一个解决方案。
Redux 解决了一开始我们可能还不太清楚的问题:它为每一个 React 组件明确提供它所需要的 state 。
Redux 统一在一个地方维护 state 。
通过 Redux ,获取和管理 state 的逻辑也和 React 隔离开来。
这中方式的好处可能还不太明显。当你用上它后就会发现它的威力。
下一节内容我们聊一下为什么以及什么时候我们需要用 Redux。
我要学 Redux 么 ?
想学习 Redux 但是没搞定?
Redux 的确吓坏了许多初学者。但是不必因此而感到担心。
Redux 没有那么难。关键是:不要随波逐流地去使用它。
你开始学习 Redux 应该是出于自发的动机和热情。
我学习 Redux 是因为:
我 100% 地渴望知道 Redux 是怎么工作的
我想要提升我的 React 技能
我想成为更牛 X 的 React 开发者
React/Redux 当下是黄金组合
Redux 是一种思想,它和框架无关。学习了它可以在任何地方(比如 Vue JS,Angular 中)使用。
我要用 Redux 么 ?
没有 Redux 构建复杂的 React 应用也是可行的。只是有一些成本。
用 Redux 也是有一定成本的:它加入了另外一个抽象层。但是我我认为是利大于弊的。
另一个困惑初学者的问题是:我怎么判断什么时候我需要使用 Redux 呢?
如果你没有经验判定是否需要用 Redux 来管理 state ,我有如下建议使用的场景:
多个 React 组件需要访问同一个 state 但是又没有父子关系
需要通过 props 向下向多个组件传递 state
如果还是找不到感觉,别怕,我也一样。
Dan Abramov 说过『 Flux 就像眼镜:你需要的时候你自然会知道 』。
事实上对于我而言也正是如此。
Dan Abramov 写了篇很棒的文章来帮助我们理解: Redux 的使用场景
dev.to 上也有一篇 Dan 关于这一问题的 讨论
看一下 Mark Erikson 整理的 Redux 学习资源
顺便说下,Mark 的博客被视为 Redux 的最佳实践
Dave Ceddia 也分享过关于 Redux 干了什么?以及它的使用场景 的话题。
在深入学习之前,花些时间去搞明白 Redux 解决了什么问题。然后再决定要不要学。
要清楚在小型的应用中使用 Redux 没什么好处。只有在大型应用下才能发挥它的威力。但不管如此,学习 Redux 解决问题的思路总不是什么坏事。
下一节,我们介绍一下内容:
Redux 的基本原则
Redux 和 React 怎么结合
了解 Redux store
Actions, Reducers 我都知道。但有件事我搞不明白:这些动态的片段是怎么结合到一起的?
有小黄人相助不成?
是 store 把所有流动的片段协调地结合到 Redux 中。在 Redux 中 store 就像人类的大脑:这是种魔法。
Redux store 是一切的基础:整个应用的 state 都在 store 中维护。
如果我们把 Redux 的工作模式想象成人类的大脑。 state 就相当于存在于大脑(store)中的一段回忆。
所以开始使用 Redux 的时候我们需要创建一个 store 来存放 state。
切换到你的开发目录,安装 Redux:
1 |
|
创建一个目录来存放 store 相关逻辑:
1 |
|
在 src/js/store 中创建 index.js 并初始化:
1 |
|
createStore 就是创建 Redux store 的方法。
createStore 接收一个 reducer 作为第一个参数,就是代码中的 rootReducer。
你也可以传递一个初始 state 进 createStore。 但是大多数时候你不必这么做。传递初始 state 在服务端渲染时比较有用。当然,state 来自 reducers。
现在我们知道 reducer 做了啥了。reducer 生成了 state 。 state 并不是手动创建的。
带着这些概念我们继续我们第一个 Redux reducer。
了解 Redux reducers
初始 state 在 服务端渲染 中很有用,它必须完全从 reducers 中产生。
reducer 是个什么鬼?
reducer 就是个 javascript 函数。它接收两个参数:当前的 state 和 action 。
Redux 的第三条原则中强调:state 必须是不可变的。这也是为什么 reducer 必须是一个纯函数。纯函数就是指给有明确的输入输出的函数。
在传统的 React 应用中我们通过 setState 方法来改变 state 。 在 Redux 中不可以这么做。
创建一个 reducer 不难,就是个包含两个参数的 javascript 普通函数。
在我们的例子中,我们将创建一个简单的 reducer, 并向他传递初始 state 作为第一个参数。第二个参数我们将提供一个 action 。到目前为止 reducer 除了返回初始状态什么都不会做。
创建 reducer 根目录
1 |
|
然后在 src/js/reducers 创建一个名为 index.js 的新文件:
1 |
|
我承诺这是篇尽可能简明的教程。所以我们仅有一个 reducer :它除了返回初始 state 什么都不处理。
注意下初始 state 是怎样作为 默认参数 传递的。
下一节我们将结合一个 action 来让这个应用更加有趣。
了解 Redux actions
reducers 无疑是 Redux 中最重要的概念。reducers 用来生产应用的 state 。
但是 reducers 怎么知道什么时候去生产下一个 state 呢?
Redux 的第二条原则中说:改变 state 的唯一方式就是向 store 发送一个信号 。 这个 信号 指的就是 action 。
怎么改变这个不可更改的状态呢?你不能。返回的 state 是当前 state 的副本结合新数据后的一个全新的 state 。这点必须要明确。
比较欣慰的是 actions 就是简单的 javascript 对象。它长这样:
1 |
|
每一个 action 都要有一个 type 属性来描述要对 state 做怎样的改变。
你也可以指定一个叫 payload 的属性。在上面的例子中, payload 指代一篇新的文章。reducer 接下来将把这篇新文章加到 state 中去。
最佳实践是将每一个 action 都通过函数包裹起来。这个函数就是 action creator。
让我们来把这些都串起来创建一个简单的 action 。
创建 actions 目录:
1 |
|
在 src/js/actions 目录中创建名为 index.js 的文件:
1 |
|
type 属性就是个简单的字符串。reducer 将根据这个字符串决定怎么处理生成下一个 state 。
由于字符串很容易重复产生冲突,所以最好在常量中统一定义 action types 。
这个方法可以避免一些很难排查的错误。
创建一个新目录:
1 |
|
然后在 src/js/constants 目录下创建名为 action-types.js 的文件:
1 |
|
然后打开 src/js/actions/index.js 用常量来替换字符串:
1 |
|
我们进一步完成了一个可运行的 Redux 应用。 接下来让我们重构下我们的 reducer 吧!
重构 reducer
进入下一步之前,让我们总结一下 Redux 的主要概念:
Redux store 就像大脑:它负责将所有流动的片段有机地整合进 Redux 中
应用中的 state 在 store 中以唯一且不可变对象的形式存在
一旦 store 接收到一个 action 他就会触发一个 reducer
reducer 返回下一个 state
reducer 是怎样构成的?
reducer 是一个 javascript 函数,它接收两个参数:state 和 action。
reducer 一般会包含一个 switch 语句(傻一点的话也可以用:if /else)。
reducer 根据 action type 产生下一个 state 。此外,当没有匹配到 action type 时它至少也要返回初始 state 。
当 action type 匹配到一个 case 分支的时候, reducer 将计算下一个 state 并返回一个全新的对象。下面是个例子:
1 |
|
我们之前创建的 reducer 只返回了初始 state 什么都没做。让我们做点什么。
打开 src/js/reducers/index.js 按如下例子更新 reducer:
1 |
|
发现了什么?
尽管代码逻辑没有错,但这违背了 Redux 的主要原则: 不可变 。
Array.prototype.push
不是一个纯函数,它改变了原来的数组。
解决的方式很简单。用 Array.prototype.concat
替换 Array.prototype.push
来保持原始数组是不可变的。
1 |
|
还不够!可以用 扩展运算符 优化一下:
1 |
|
上述例子中初始 state 完全没受干扰。
最初的文章数组并没有改变。
初始 state 对象也没有改变。返回的 state 是初始 state 的一个副本。
在 Redux 中有两个关键点来防止类似的改变。
针对数组可以用 concat (), slice (), 和 … 操作符
针对对象可以用 Object.assign () and … 操作符
扩展运算符 在 webpack 3 中还是个实验性功能。需要安装一个 babel 插件来防止语法错误:
1 |
|
打开 .babelrc 更改配置:
1 |
|
小贴士: reducer 随着应用的增长会变得臃肿。你可以拆分 reducer 进不同的函数,然后通过 combineReducers 将他们结合起来。
下一节我们将通过 console 控制台把玩一下 Redux 。搞起!
Redux store 中的方法
这节内容将会很快过完,我保证。
我想借助浏览器的控制台让你快速理解下 Redux 是怎样工作的。
Redux 本身是一个很小的类库 (2KB)。它暴露了 一些简单的 API 让我们来管理 state 。其中最重要的方法就是:
- getState 用于获取应用的 state
- dispatch 用于触发一个 action
- subscribe 用于监听 state 的变化
我们将借助浏览器的管理控制台试验上述方法。
我们需要创建全局变量将我们先前创建的 store 和 action 暴露出来。
打开 src/js/index.js 按如下所示更新代码:
1 |
|
然后运行一下:
1 |
|
在浏览器中打开 http://localhost:8080/ 并按 F12 打开管理控制台。
由于我们全局暴露了 store , 所以我们可以进入它的方法。试一下!
首先访问当前 state:
1 |
|
输出:
1 |
|
没有文章。事实上我们还没有更新初始 state 。
让这变得更有趣些我们可以通过 subscribe 方法监听 state 的变化。
subscribe 方法接收一个回调函数,当 action 触发的时候该回调函数就会执行。触发 action 就意味着通知 store 我们想改变 state 。
通过如下操作注册回调函数:
1 |
|
要想改变 state 我们需要触发一个 action 。要触发一个 action 我们就需要调用 dispatch 方法 。
我们已经有了一个 action :addArticle 用来向 state 中新增文章。
让我们触发一下这个 action :
1 |
|
执行上述代码后你将看到:
1 |
|
验证一下 state 有没有变:
1 |
|
输出会是:
1 |
|
这就是个最简单的 Redux 原型。
难么?
稍微花点时间练习一下 Redux 的三个方法。在控制台中玩一把:
- getState 用于获取应用的 state
- dispatch 用于触发一个 action
- subscribe 用于监听 state 的变化
这就是开始入坑所需要知道的全部内容。
有信心进入下一步了么?让我们把 React 和 Redux 串起来吧。
把 React 和 Redux 结合起来
学习了 Redux 后我发现它并没有想象中那么复杂。
我知道我可以通过 getState 方法获取当前 state ;通过 dispatch 触发一个 action ; 通过 subscribe 监听 state 的变化。
目前我还不知道怎么将 React 和 Redux 组合起来使用。
我问自己:我需要在 React component 中调用 getState 方法么? 在 React component 我怎么去触发 action ? 等等一些列问题。
Redux 是框架无关的。你可以在纯 Javascript 中使用它。或者结合 Angular,Redux 一起使用。 有许多第三方库可以实现把 Redux 和任何你喜欢的框架结合起来使用。
对于 React 而言, react-redux 就是这么一个第三方库。
首先让我们通过下面的命令安装一下:
1 |
|
为了演示 React 和 Redux 是如何协同工作的,我们将构建一个超级简单的应用。这个应用由如下组件组成:
- 一个 App 组件
- 一个展示文章的 List 组件
- 一个新增文章的 Form 组件
(这是个玩具应用,仅仅用来展示文章列表,新增文章条目。但是可以算一个学习 Redux 不错的开端。)
react-redux
react-redux 是一个 React 和 Redux 绑定库。这个类库将 React 和 Redux 高效地连接起来。
react-redux 的 connect 方法做了什么呢? 毫无疑问是将 React 的组件和 Redux 的 store 连接起来了。
你可以根据需要向 connect 方法传递两个或者三个参数。需要知道的最基本的东西就是:
- mapStateToProps 函数
- mapDispatchToProps 函数
react-redux 中 mapStateToProps 做了什么呢?就像它的名字一样:它将部分 Redux state 和 React 组件中的 Props 连接了起来。通过这层连接,React 组件就可以从 store 中获取它所需要的 state 了。
react-redux 中 mapDispatchToProps 又做了什么呢?和 mapStateToProps 类似,只不过它是连接的 actions 。它将 部分 Redux action 和 React 组件中的 Props 连接了起来。通过这层连接,React 组件就可以从触发 actions 了。
都清楚了么?如果没有,停下来重读一遍。我知道许多内容需要时间消化。我要着急,早晚会搞明白的。
接下来的部分我们就要大显生手了!
App 组件 和 Redux store
我们知道 mapStateToProps 将部分 Redux state 和 React 组件中的 Props 连接了起来。你可能想问:连接 React 和 Redux 这么做就够了吗?不,还不够。
要连接 React 和 Redux 我们还要用到 Provider。
Provider 是 react-redux 提供的一个高阶组件。
直白地说,就是 Provider 将整个 React 应用封装起来,是它能够感知到整个 Redux store 的存在。
为什么这么做?我们知道在 Redux 中 store 管理着一切。React 必须通过 store 来访问 state , 触发 actions 。
了解了以上这些,就打开 src/js/index.js ,修改如下:
1 |
|
看到没有?Provider 将整个应用包裹起来。并将 store 作为属性传入。
现在让我们创建一个 App 组件。没什么特别的:App 组件导入一个 List 组件并渲染。
创建一个目录来存放组件:
1 |
|
在 src/js/components 目录下创建名为 App.js 的文件。
1 |
|
花时间看下组件的构成:
1 |
|
然后继续创建 List 组件。
List 组件 和 Redux state
目前为止我们没做特殊的操作。
但是 List 组件需要和 Redux store 产生交互。
简单总结一下:连接 React 组件 和 Redux 的关键就是 connect 方法。
Connect 方法接收至少一个参数。
由于我们想让 List 获取文章列表,因此我们需要让 state.articles 和组件连接起来。怎么做呢?用 mapStateToProps 参数来实现。
在 src/js/components 目录下创建名为 List.js 的文件。写入如下内容:
1 |
|
List 组件接收一个名为 articles 的属性, 它是一个 articles 数组的副本。这个数组寄生于我们之前创建的 Redux state 内。它来自 reducer :
1 |
|
然后我们就可以用这个属性在 JSX 中生成一个文章列表了:
1 |
|
小提示 可以用 React PropTypes 对属性进行校验。
最后组件被导出为 List。List 是无状态组件和 Redux store 连接之后的产物。
无状态组件没有内部 state 。数据是通过属性传入的。
依旧很困惑?我也是。理解 connect 的工作原理需要花点时间。别害怕,慢慢来。
我建议你停下来花点时间看下 connect 和 mapStateToProps 。
搞懂了我们就进入下一章的学习。
Form 组件 和 Redux actions
Form 组件比 List 组件稍微复杂一点。它是用于创建文章的表单。
另外它是一个状态组件。
状态组件是指在组件内部维护自身 state 的组件 。
状态组件?我们一直说要用 Redux 来管理 state 。 为什么现在又说要让 Form 自己去维护 state ?
即使在使用 Redux 的时候,使用有状态的组件也是完全可以的,两者并不冲突。
并不是所有的 state 都必须在 Redux 中管理。
在这个例子中我不想其他任何组件知道 Form 组件内部的 state 。
这么做很好。
这个组件做什么事呢?
该组件包含了一些在表单提交时更新本地 state 的逻辑。
另外它接收一个 Redux action 作为属性传入其中。用这种方式它可以通过触发 addArticle action 来更新全局的 state。
在 src/js/components 下建一个名为 Form.js 的文件。它看起来像下面这样:
1 |
|
怎么描述这个组件呢?除了 mapDispatchToProps 和 connect 它就是标准的 React 的那套东西。
mapDispatchToProps 将 Redux actions 和 React 属性 连接起来。这样组件就可以向外发送 action 了。
你可以看下 action 是怎样在 handleSubmit 方法中被触发的:
1 |
|
最后组件被导出为 Form 。 Form 就是组件和 Redux store 连接之后的产物。
注意:当 mapStateToProps 不存在的时候,connect 的第一个参数必须为 null,就像例子中的 Form 一样。 否则,你会得到 TypeError: dispatch is not a function. 的警告。
我们的组件部分就都结束了。
更新下 App , 把 Form 组件加进来:
1 |
|
运行一下:
1 |
|
你将看到如下界面:
没什么特别的,但可以展示 React 和 Redux 是如何工作的。
左边的 List 组件和 Redux store 相连。它将在你新建文章的时候刷新渲染。
哈哈!
你可以在 这里 查看源码。
总结
我希望你可以从这篇教程中学到一些东西。我尽力把例子写的足够简单。我希望可以在下面的评论中倾听你的反馈。
Redux 有许多模板和移动部件。别灰心。拿起来把玩,花点时间去吸收它所有的概念。
我逐步从零开始慢慢地理解了 Redux 。相信你也可以做到。
当然,也请花点时间想清楚为什么需要在你的项目中引用 Redux。
无论怎样:学习 Redux 都是 100% 值得的。
Redux 并不是状态管理的唯一方式。Mobx 是另一个有趣的可选方案。我也在关注 Apollo Client。谁将胜出?只有时间知道答案。
函数式编程的一些资源
Redux 使大多数初学者感到害怕,是因为它是围绕函数式编程和纯函数展开的。
我只能是推荐一些资源给大家,因为函数式编程超出了本指南的范围。 下面是一些函数式编程和纯函数相关的资源:
Redux 中的异步 actions
我不确定谈论异步 actions 是否合适。
大多数 Redux 初学者都很难仅学习纯粹的 Redux。 在 Redux 中处理复杂的异步 action 还是比较费劲的。
当你了解了 Redux 的核心概念后可以去读一下 Matt Stow 写的 A Dummy’s Guide to Redux and Thunk in React。这是篇关于 Redux 怎样用 redux-thunk 处理 API 请求的非常不错的介绍。
谢谢阅读,码得愉快~