前端应用的状态管理日益复杂。随着大前端时代的到来,前端越来越注重处理逻辑,而不仅是专一 UI 层面的改进,而以 React 为表明的前端框架的出现,大大简化了咱们编写 UI 界面的复杂度。虽然 React 提供了 State 机制实现状态管理,也有诸如“状态提高”等开发约定,可是这些方案只适用于小型应用,当你的前端应用有多达 10 个以上页面时,如何让应用状态可控、让协做开发高效成为了亟待解决的问题,而 Redux 的出现正是为了解决这些问题而生的!Redux 提出的“数据的惟一真相来源”、单向数据流、“纯函数 Reducers” 大大简化了前端逻辑,使得咱们可以以高效、便于协做的方式编写任意复杂的前端应用。本篇教程致力于用简短的文字讲透 Redux,在实战中掌握 Redux 的概念和精髓。前端
此教程属于React 前端工程师学习路线的一部分,欢迎来 Star 一波,鼓励咱们继续创做出更好的教程,持续更新中~。node
Redux 官方文档对 Redux 的定义是:一个可预测的 JavaScript 应用状态管理容器。react
这就意味着,Redux 是没法单独运做的,它须要与一个具体的 View 层的前端框架相结合才能发挥出它的威力,这里的 View 层包括但不限于 React、Vue 或者 Angular 等。这里咱们将使用 React 做为绑定视图层,由于 Redux 最初诞生于 React 社区,为解决 React 的状态管理问题而设计和开发的一个库。这篇教程将让你直观地感觉 React 的“状态危机”,以及 Redux 是如何解决这一危机的,从而可以更好地学习 Redux,并理解它的源起,以及它将走向什么样的远方。git
近来 React Hooks 确实很火,展示出惊人的潜力,甚至有人声称能够抛弃 Redux 了。其实笔者以为这种说法不彻底正确,Redux 的成功其实不只仅是由于这个框架自己,还由于围绕其构建起来的生态系统,好比 Enhancers、Middlewares、还有诸如 redux-form,redux-immutable 等,甚至还有基于 Redux 的上层框架,如 Dva 还有 Rematch,这些都为 Redux 巩固了在 React 社区的王者地位。github
而有趣的是,咱们注意到 Redux 的 React 绑定库 react-redux 如今正在用 React Hooks 重构,以求让代码更加精炼和高效,因此笔者以为 React Hooks 首先还处于萌芽阶段,一小部分尝鲜者在视图使用它来构建更好的 React 项目或者框架,React Hooks 可让以前的这些项目和框架变得更好,以更好的辅助 Redux 生态的继续繁荣,因此咱们有理由相信,React Hooks 的出现,会让 React 社区变得更加高效和专业,也会帮助 Redux 工具链变得更加轻量,最终做为 React 的一个优秀的特性将 React 和其生态带向更好的远方。数据库
本篇教程是关于 Redux 的快速入门教程,并致力于讲解与 React 绑定时的使用,而了解和掌握 Redux 对于一个 React 开发者来讲属于较为进阶的内容,因此咱们假设在阅读本篇教程以前,你须要拥有如下的知识储备:npm
const
、对象解构、函数默认参数等概念有良好的了解,固然若是你了解过函数式编程,如纯函数、不变性等就更好了在本篇教程中,咱们将首先给出了一个使用 React 实现的待办事项小应用(比上篇教程中完成的版本多了筛选的功能),它将是咱们学习 Redux 的起点,当你熟悉了这份初始代码,并了解了它的功能以后,你就能够关闭它,而后开始咱们教程的学习啦!编程
咱们将基于这个纯 React 写成的模板,分析 React 在处理状态时存在的问题,以及用 Redux 重构带来的优点。接着咱们将经过实战的方式学习如何将一个纯 React 应用一步步地重构成一个 Redux 应用,最终实现一个升级版的待办事项小应用。redux
本教程所实现的源代码都托管在 Github 上:后端
你能够经过 CodeSandbox 查看代码最终的效果:
无论外界把 Redux 吹得如何天花乱坠,实际上它能够用一张图来归纳,这张图也有利于帮助你思考前端的本质是什么:
咱们先来详解一下这张图,而且在教程以后的内容中,你会屡次看到这张图以不一样的形式出现。咱们但愿学完本篇教程以后,每当你想起 Redux 时,脑海里就是上面这张图。
首先咱们来看 View ,在前端开发中,咱们称这个为视图层,就是展现给最终用户的效果,在本篇教程的学习中,咱们的 View 就是 React。
随着前端应用要完成的工做愈来愈丰富,咱们对前端也提出了要保持 “状态” 的要求。在 React 中,这个 “状态” 将保存在 this.state
。在 Redux 中,这个状态将保存在 Store。
这个 Store 从抽象意义上来讲能够看作一个前端的 “数据库”,它保存着前端的状态(state),而且分发这些状态给 View,使得 View 根据这些状态渲染不一样的内容。
注意到,Redux 是一个可预测的 JavaScript 应用状态管理容器,这个状态容器就是这里的 Store。
咱们平常生活中看到的网页,它不是一成不变的,而是会响应用户的 “动做”,不管是页面跳转仍是登录注册,这些动做会改变当前应用的状态。
在 Redux 框架中,Reducers 的做用就是响应不一样的动做。更精确地说,Reducers 是负责更新 Store 中状态的 JavaScript 函数。
当咱们对这三个核心概念有了粗略的认知以后,就能够开始 Redux 的学习了。
将初始 React 代码模板 Clone 到本地,进入仓库,并切换到 initial-code 分支(初始代码模板):
git clone https://github.com/pftom/redux-quickstart-tutorial.git
cd redux-quickstart-tutorial
git checkout initial-code
复制代码
安装项目依赖,并打开开发服务器:
npm install
npm start
复制代码
接着 React 开发服务器会打开浏览器,若是你看到下面的效果,而且能够进行操做,那么表明代码准备完成:
提示
因为咱们使用 Create React App 脚手架,它使用 Webpack Development Server(WDS)做为开发服务器,所以在后面编辑代码的时候只需保存文件,咱们的 React 应用就会自动刷新,很是方便。
咱们完成的这个待办事项小应用比上篇教程中实现的要高级一点,以下面这个动图所示:
咱们但愿展现一个 todo 列表,当一个 todo 被点击时,它将被加上删除线表示此 todo 已经完成,咱们还加上了一个输入框,使得用户能够增长新的 todo。在底部,咱们展现了三个按钮,能够切换展现 todo 的类型。
整份 React 代码组件设计以下(首先是组件,而后是组件所拥有的属性):
TodoList
用来展现 todo 列表:
todos: Array
是一个 todo 数组,它其中的每一个元素的样子相似 { id, text, completed }
。toggleTodo(id: number)
是当一个 todo 被点击时会调用的回调函数。Todo
是单一 todo 组件:
text: string
是这个 todo 将显示的内容。completed: boolean
用来表示是否完成,若是完成,那么样式上就会给这个元素划上删除线。onClick()
是当这个 todo 被点击时将调用的回调函数。Link
是一个展现过滤的按钮:
active: boolean
表明此时被选中,那么此按钮将不能被点击onClick()
表示这个 link 被点击时将调用的回调函数。children: ReactComponent
展现子组件Footer
用于展现三个过滤按钮:
filter: string
表明此时的被选中的过滤器字符串,它是 [SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE]
其中之一。setVisibilityFilter()
表明 Link 被点击时将设置对应被点击的 filter
的回调函数。App
是 React 根组件,最终组合其余组件并使用 ReactDOM 对其进行编译渲染,咱们在它的 state
上定义了上面的几个组件会用到的属性,同时定义了其余组件会用到的方法,还有 nextTodoId
,VisibilityFilters
,getVisibleTodos
等一些辅助函数。咱们知道 Redux 能够与多种视图层开发框架如 React,Vue 和 Angular 等搭配使用,而 Redux 只是一个状态管理容器,因此为了在 React 中使用 Redux,咱们还须要安装一下对应的依赖。
npm install redux
npm install react-redux
复制代码
作得好!如今一切已经准备就绪,相信你已经火烧眉毛的想要编写一点 Redux 相关的代码了,别担忧,在下一节中,咱们将引出 Redux Store 的详细概念,而且经过代码讲解它将替换 React 的哪一个部分。
咱们前面提到了 Store 在 Redux 中的做用是用来保存状态的,至关于咱们在前端创建了一个简单的 ”数据库“。在目前的富状态前端应用中,若是每一次状态的修改(例如点击一个按钮)都须要与后端通讯,那么整个网站的平均响应时间将变得难以接受,用户体验将糟糕透顶。
根据不彻底统计:”一个网站能留住一名用户的时间只有 8S,若是你在 8S 内不能吸引住用户,或者网站出现了问题,那么你将完全地丢失这名用户!”
因此为了适应用户的访问需求,聪明的前端拓荒者们开始将后端的 “数据库” 理念引入到前端中,这样大多数的前端状态能够直接在前端搞定,彻底不须要后端的介入。
在 React 中,咱们将状态存在每一个组件的 this.state
中,每一个组件的 state
为组件所私有,若是要在一个组件中操做另一个组件,实现起来是至关繁琐的。
咱们将用下面这张图来演示一下为何繁琐:
组件 A 是组件 B 和 C 的父组件。若是组件 B 想要操做组件 C,那么它首先须要调用父组件 A 传给它的 handleClick
方法,而后经过这个方法修改父组件A的 state
,进而经过 React 的自动从新渲染机制,触发组件 C 的变化。
如今组件 B 和组件 C 是处于平级的,你可能还感受不到这种跨组件改变有什么问题,让咱们再来看一张图:
咱们看到上面这张图,组件 B 和组件 C 相差了不少级,图中的 n 可能为 10,也可能更多。这个时候若是再想在组件 B 中修改组件 C,那就要把这个 handleClick
方法一层一层地往下传。每次要修改的时候,都要进行调用,这已经至关繁琐了。
若是组件 C 离组件 A 还有很深的层级,状况就更复杂了:
这时候,不只要把 handleClick
方法经过很深的层级传给组件 B,当组件 B 调用 handleClick
方法时,修改组件 A 的 state
,再反过来传递给组件 C 时,组件 A 到组件 C 之间的全部组件都会触发从新渲染,这带来了巨额的渲染开销,当咱们的应用愈来愈复杂,这种开销显然是承受不起的。
React 诞生的初衷就是为了更好、更高效率地编写用户界面 ,它不该该也不须要来承担状态管理的职责。
因而备受折磨的前端拓荒者们构想出了伟大的 Store。咱们彻底不须要让每一个组件单独保持状态,直接抽离全部组件的状态,类比 React 组件树,构造一个中心化的状态树,这棵状态树与 React 组件树一一对应,至关于对 React 组件树进行了状态化建模:
能够看到,咱们将组件的 state 去掉,取而代之的是一棵状态树,它是一个普通的 JavaScript 对象。经过对象的嵌套来类比组件的嵌套组合,这棵由 JavaScript 对象建模的状态树就是 Redux 中的 Store。
当咱们将组件的状态抽离出去以后,咱们在使用组件 B 操做组件 C 就变得至关简单且高效。
咱们在组件 B 中发起一个更新状态 C 的动做,此动做对应的更新函数更新 Store 状态树,以后将更新后的状态 C 传递给组件 C,触发组件 C 的从新渲染。
能够看到,当咱们引入这种机制以后,组件 B 与组件 C 之间的交互就可以单独进行,不会影响 React 组件树中的其余组件,也不须要传递很深层级的 handleClick
函数了,不再须要把更新后的 state
一层一层地传给组件 C,性能有了质的飞跃。
有了 Redux Store 以后,全部 React 应用中的状态修改都是对这棵 JavaScript 对象树的修改,全部状态的获取都是从这棵 JavaScript 对象树获取,这棵 JavaScript 对象表明的状态树成了整个应用的 “数据的惟一真相来源”。
了解了 Redux Store 之于 React 的做用以后,咱们立刻在 React 中应用 Redux ,看看神奇的 Store 是如何介入并产生如此大的变化的。
咱们修改初始代码模板中的 src/index.js
,修改后的代码以下:
import React from "react";
import ReactDOM from "react-dom";
import App, { VisibilityFilters } from "./components/App";
import { createStore } from "redux";
import { Provider } from "react-redux";
const initialState = {
todos: [
{
id: 1,
text: "你好, 图雀",
completed: false
},
{
id: 2,
text: "我是一只小小小小图雀",
completed: false
},
{
id: 3,
text: "小若燕雀,亦可一展宏图!",
completed: false
}
],
filter: VisibilityFilters.SHOW_ALL
};
const rootReducer = (state, action) => {
return state;
};
const store = createStore(rootReducer, initialState);
ReactDOM.render(
<Provider store={store}> <App /> </Provider>,
document.getElementById("root")
);
复制代码
能够看到,上面的代码作了下面几项工做:
redux
中导出了 createStore
,从 react-redux
导出了 Provider
,从 src/components/App.js
中导出了 VisibilityFilters
。initialState
对象,这将做为咱们以后建立 Store 的初始状态数据,也是咱们以前提到的那棵 JavaScript 对象树的初始值。rootReducer
函数,它是一个箭头函数,接收 state
和 action
而后返回 state
,这个函数目前尚未完成任何工做,可是它是建立 Store 所必须的参数之一,咱们将在以后的 Reducers 中详细讲解它。createStore
函数,传入定义的 rootReducer
和 initialState
,生成了咱们本节的主角:store!App
组件的最外层使用 Provider
包裹,并接收咱们上一步建立的 store
做为参数,这确保以后咱们能够在子组件中访问到 store 中的状态。Provider
是 react-redux
提供的 API,是 Redux 在 React 使用的绑定库,它搭建起 Redux 和 React 交流的桥梁。如今咱们已经建立了 Store,并使用了 React 与 Redux 的绑定库 react-redux
提供的 Provider
组件将 Store 与 React 组件组合在了一块儿。咱们立刻来看一下整合 Store 与 React 以后的效果。
打开 src/components/App.js
,修改代码以下:
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
// 省略了 VisibilityFilters 和 getVisibleTodos 函数...
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTodo = this.toggleTodo.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.setVisibilityFilter = this.setVisibilityFilter.bind(this);
}
// 省略中间其余方法...
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo onSubmit={this.onSubmit} />
<TodoList
todos={getVisibleTodos(todos, filter)}
toggleTodo={this.toggleTodo}
/>
<Footer
filter={filter}
setVisibilityFilter={this.setVisibilityFilter}
/>
</div>
);
}
}
const mapStateToProps = (state, props) => ({
todos: state.todos,
filter: state.filter
});
export default connect(mapStateToProps)(App);
复制代码
能够看到,上面的代码作了这几项工做:
react-redux
绑定库里面导出了 connect
函数。mapStateToProps
箭头函数,它接收 state
和 props
,这个 state
就是咱们那棵 Store 里面保存的 JavaScript 对象状态树,目前就是咱们在上一个文件中定义的 initialState
内容;这个 props
就是咱们熟悉的原 React 组件的 props
,它对于 mapStateToProps
是一个可选参数。 mapStateToProps
函数就是能够同时操做组件的原 props
和 Store 的状态,而后合并成最终的组件 props,(固然这里咱们并无使用原组件 props 内容)并经过 connect
函数传递给 App
组件。connect
函数接收 mapStateProps
函数,获取 mapStateProps
返回的最终组合后的状态,而后将其注入到 App
组件中,返回一个新的组件,而后交给 export default
导出。App
组件中就能够取到经过 mapStateToProps
返回的 { todos, filter }
内容了,咱们经过对象解构,从 this.props
拿到 todos
和 filter
属性。constructor
中的 this.state
内容。注意
connect
实际上是一个高阶函数,高阶函数就是指能够接收参数调用并返回另一个函数的函数。这里connect
经过接收mapStateToProps
而后调用返回一个新函数,接着这个新函数再接收App
组件做为参数,经过mapStateToProps
注入todos
和filter
属性,最后返回注入后的App
组件。
提示
这里之因此咱们能在
App
组件中经过mapStateToProps
拿到 Store 中保存的 JavaScript 对象状态树,是由于咱们在以前经过Provider
包裹了App
组件,并将store
做为属性传递给了Provider
。
如今再来看一看咱们在第一步骤中提到的环形图,咱们如今处于这个流程的第一步,即将 Store 里面的状态传递到 View 中,具体咱们是经过 React 的 Redux 绑定库 react-redux
中的 connect
实现的。
保存改变的内容,若是你的 React 开发服务器打开着,那么你应该能够在浏览器中看到以下内容:
恭喜你!你已经成功编写了 Redux 的 Store,完成将 Redux 整合进 React 工做的 1/3。 经过在 React 中接入 Store,你成功的将 Redux 和 React 之间的数据打通,并删除了 this.state
,使用 Store 的状态来取代 this.state
。
可是!当你此时点击 Add Todo
按钮,你的浏览器应该会显示出红色的错误,由于咱们已经删除了 this.state
的内容,因此在 onSubmit
方法中读取 this.state.todos
就会报错。别担忧,咱们将在下一节中: Action
中讲解如何解决这些错误。
欢迎来到 Redux Action 环节,让咱们再一次引用上一节提到的图:
在上一节中,咱们就在组件 B 中完成某种动做来修改组件 C 中的内容,详细剖析了彻底基于 React 实现的弊端,并经过引出 Redux Store 的概念,讲解了咱们只须要建一个全局 JavaScript 对象状态树,而后全部的状态的改变都是经过修改这一状态树,进而将修改后的新状态传给相应的组件并触发从新渲染来完成咱们的目的。而且咱们讲解了如何将 Store 里面的状态传给 React 组件使用。
这一节咱们就来说一讲,如何修改 Redux Store 中保存的状态。让咱们再抛出熟悉的 Redux 状态环形图:
修改 Store 中保存的状态就是上面这张图的第二个部分,即咱们已经建立好了 Store,并在里面存储了一棵 JavaScript 对象状态树,咱们经过 “发起更新动做” 来修改 Store 中保存的状态。
在 Redux 的概念术语中,更新 Store 的状态有且仅有一种方式:那就是调用 dispatch
函数,传递一个 action
给这个函数 。
一个 Action 就是一个简单的 JavaScript 对象:
{ type: 'ADD_TODO', text: '我是一只小小小图雀' }
复制代码
咱们能够看到一个 action
包含动做的类型,以及更新状态须要的数据,其中 type
是必须的,其它内容都是可选的,这里咱们除了 type
,还额外添加了一个 text
,表明咱们发起 type
为 ADD_TODO
的动做是,额外传递了一个 text
内容。
因此若是咱们须要更新 Store 状态,那么就须要相似下面的函数调用:
dispatch({ type: 'ADD_TODO', text: '我是一只小小小图雀' })
复制代码
由于咱们在建立 Action 的时候,有时候有些内容是固定了,好比咱们的待办事项添加教程的 Action,有三个字段,分别是 type
,text
,id
,咱们可能会要在多个地方能够 dispatch
这个 Action,那么咱们每次都须要写下面长长的一串 :
{ type: 'ADD_TODO', text: '我是一只小小小图雀' , id: 0}
{ type: 'ADD_TODO', text: '小若燕雀,亦可一展宏图' , id: 1}
...
{ type: 'ADD_TODO', text: '欢迎你加入图雀社区!' , id: 10}
复制代码
对 JavaScript 函数比较熟悉的同窗可能就知道该如何解决这种问题。是的,咱们只须要定义一个函数,而后传入须要变化的参数就能够了。
let nextTodoId = 0;
const addTodo = text => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});
复制代码
这种接收一些须要修改的参数,返回一个 Action 的函数在 Redux 中被称为 Action Creators(动做建立器)。
当咱们使用 Action Creators 来建立 Action 以后,咱们再想要修改 Store 的状态就变成了下面这样:
dispatch(addTodo('我是一只小小小图雀'))
复制代码
能够看到,咱们的逻辑大大简化了,每次发起一个新的 "ADD_TODO"
action,都只须要传入对应的 text。
了解了 Action 的基础概念以后,咱们立刻来尝试一下如何在 React 中发起更新动做。
首先,咱们在 src
文件夹下面建立 actions
文件夹,而后在 actions
文件夹下建立 index.js
文件,并在里面添加下面的 Action Creators:
let nextTodoId = 0;
export const addTodo = text => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});
复制代码
由于在使用 Redux 的 React 应用中,咱们将须要建立大量的 Action 或者 Action Creators,因此 Redux 社区的最佳实践推荐咱们建立一个独立的 actions
文件夹,并在这个文件夹里面编写特定的 Action 逻辑。
能够看到,咱们加入了一个 addTodo
Action Creator,它接收 text
参数,并每次自增一个 id
,而后返回带有 id
和 text
,而且类型为 "ADD_TODO"
的 Action。
接着咱们修改 src/components/AddTodo.js
文件,将以前的 onSubmit
替换成以 dispatch(action)
的形式来修改 Store 的状态:
import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../actions";
const AddTodo = ({ dispatch }) => {
let input;
return (
<div> <form onSubmit={e => { e.preventDefault(); if (!input.value.trim()) { return; } dispatch(addTodo(input.value)); input.value = ""; }} > <input ref={node => (input = node)} /> <button type="submit">Add Todo</button> </form> </div> ); }; export default connect()(AddTodo); 复制代码
能够看到,上面的代码作了这几项改变:
react-redux
中导出了 connect
函数,它负责将 Store 中的状态注入组件的同时,还给组件传递了一个额外的方法:dispatch
,这样咱们就能够在组件的 props
中获取这个方法。注意到咱们在 AddTodo 函数式组件中使用了对象解构来获取 dispatch
方法。addTodo
Action Creators。addTodo
接收 input.value
输入值,建立一个类型为 "ADD_TODO"
的 Action,并使用 dispatch
函数将这个 Action 发送给 Redux,请求更新 Store 的内容,更新 Store 的状态须要 Reducers 来进行操做,咱们将在 Reducer 中详细讲解它。由于咱们已经将直接修改 this.state
的 onSubmit
换成了 dispatch
一个 Action,因此咱们删除 src/components/App.js
相应的代码,由于咱们如今已经不须要它们了:
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
// 省略 VisibilityFilters 和 getVisibleTodos ...
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTodo = this.toggleTodo.bind(this);
this.setVisibilityFilter = this.setVisibilityFilter.bind(this);
}
toggleTodo(id) {
const { todos } = this.state;
this.setState({
todos: todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
});
}
setVisibilityFilter(filter) {
this.setState({
filter: filter
});
}
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo />
<TodoList
todos={getVisibleTodos(todos, filter)}
toggleTodo={this.toggleTodo}
/>
<Footer
filter={filter}
setVisibilityFilter={this.setVisibilityFilter}
/>
</div>
);
}
}
// 后面没有变化 ...
复制代码
能够看到咱们删除了 nextTodoId
,由于咱们已经在 src/actions/index.js
中从新定义了它;接着咱们删除了 onSubmit
方法;最后咱们删除了传递给 AddTodo
组件的 onSubmit
方法。
保存修改的内容,咱们在待办事项小应用的输入框里面输入点内容,而后点击 Add Todo
按钮,咱们发现,以前的错误没有再次出现。
在这一节中,咱们完成了 Redux 状态环形图的第二个部分,即发起更新动做,咱们首先讲解了什么是 Action 和 Action Creators,而后经过 dispatch(action)
的方式来发起一个更新 Store 中状态的动做。
当咱们使用了 dispatch(action)
以后,传递给子组件,用来修改父组件 State 的方法就不须要了,因此咱们在代码中删除了它们。在咱们的 AddTodo
中,这个方法就是 onSubmit
。
可是有一点遗憾就是,咱们虽然删除了 onSubmit
方法,可是咱们这一节中讲到和实现的 dispatch(action)
还只能完成以前 onSubmit
方法的一半功能,即发起修改动做,可是咱们目前还没法修改 Store 中的状态。为了修改 Store 中的 State,咱们须要定义 Reducers,用于响应咱们 dispatch
的 Action,并根据 Action 的要求修改 Store 中对应的数据。
在这一节中,咱们立刻来告终上一节中留下的遗憾,即咱们好像放了一声空炮,dispatch
了一个 Action,可是没有收获任何效果。
首先祭出咱们万能的 Redux 状态循环图:
咱们已经完成了前两步了,离 Redux 整合进 React 只剩下最后一个步骤,即响应从组件中 dispatch
出来 Action,并更新 Store 中的状态,这在 Redux 的概念中被称之为 Reducers。
reducer
是一个普通的 JavaScript 函数,它接收两个参数:state
和 action
,前者为 Store 中存储的那棵 JavaScript 对象状态树,后者即为咱们在组件中 dispatch
的那个 Action。
reducer(state, action) {
// 对 state 进行操做
return newState;
}
复制代码
reducer 根据 action 的指示,对 state 进行对应的操做,而后返回操做后的 state,Redux Store 会自动保存这份新的 state。
注意
Redux 官方社区对 reducer 的约定是一个纯函数,即咱们不能直接修改
state
,而是可使用{...}
等对象解构手段返回一个被修改后的新state
。好比咱们对
state = { a: 1, b: 2 }
进行修改,将a
替换成 3,咱们应该这么作:newState = { ...state, a: 3 }
,而不该该state.a = 3
。 这种不直接修改原对象,而是返回一个新对象的修改,咱们称之为 “纯化” 的修改。
当了解了 Reducer 的概念以后,咱们立刻在应用中响应咱们以前 dispatch
的 Action,来弥补咱们在上一节中留下的遗憾。
打开 src/index.js
,对 rootReducer
做出以下修改:
// ...
const rootReducer = (state, action) => {
switch (action.type) {
case "ADD_TODO": {
const { todos } = state;
return {
...state,
todos: [
...todos,
{
id: action.id,
text: action.text,
completed: false
}
]
};
}
default:
return state;
}
};
// ...
复制代码
上面的代码作了这么几项工做:
rootReducer
进行改进,从单纯地返回原来的 state
,变成了一个 switch
语句,在 switch
语句中对 action 的 type
进行判断,而后作出对应的处理。action.type
的类型为 "ADD_TODO"
时,咱们从 state
中取出了 todos
,而后使用 {...}
语法给 todos
添加一个新的元素对象,并设置 completed
属性为 false
表明此 todo 未完成,最后再经过一层 {...}
语法将新的 todos
合并进老的 state
中,返回这个新的 state
。action.type
没有匹配 switch
的任何条件时,咱们返回默认的 state
,表示 state
没有任何更新。当咱们对 rootReducer
函数作了上述的改动以后,Redux 经过 Reducer 函数就能够响应从组件中 dispatch
出来的 action
了,目前咱们还只能够响应 action.type
为 "ADD_TODO"
的 action,它表示新增一个 todo。
保存修改的代码,打开浏览器,在输入框里面输入点内容,而后点击 Add Todo
按钮,如今网页应该能够正确响应你的操做了,咱们又能够愉快地添加新的待办事项了。
在这一小节中,咱们实现了第一个能够响应组件 dispatch
出来的 Action 的 Reducer,它判断 action.type
的类型,并根据这些类型对 state
进行 “纯化” 的修改,当 action.type
没有匹配 Reducer 中任何类型时,咱们返回原来的 state
。
当了解了 Redux 三大概念:Store,Action,Reducers 以后,咱们再来看一张图:
这张图咱们以前看过相似的,只不过这一次咱们在这张图上加了点东西,分别标出了 dispatch
、 reducers
和 connect
所完成的工做。
dispatch(action)
用来在 React 组件中发出修改 Store 中保存状态的指令。在咱们须要新加一个待办事项时,它取代了以前定义在组件中的 onSubmit
方法。reducer(state, action)
用来根据这一指令修改 Store 中保存状态对应的部分。在咱们须要新加一个待办事项时,它取代了以前定义在组件中的 this.setState
操做。connect(mapStateToProps)
用来将更新好的数据传给组件,而后触发 React 从新渲染,显示最新的状态。它架设起 Redux 和 React 之间的数据通讯桥梁。如今,Redux 的核心概念你已经所有学完了,而且咱们的应用已经彻底整合了 Redux。可是,咱们还有一点工做没有完成,那就是将整个应用彻底使用 Redux 重构。在下一篇教程中,咱们将使用咱们在上面三节学到的知识,一步一步将咱们的待办事项应用的其余部分重构成 Redux,敬请期待~
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。