文章目录
1. 知识铺垫
1.1 理解JavaScript纯函数
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
- 在 react 开发中纯函数是被多次提及的;
- 比如 react 中组件就被要求像是一个纯函数(为什么用像,因为还有 class 组件),redux 中有一个 reducer 的概念,也是要求必须是一个纯函数;
- 所以掌握纯函数对于理解很多框架的设计是非常有帮助的;
纯函数的维基百科定义:
- 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
- 此函数在相同的输入值时,需产生相同的输出。
- 函数的输出与输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
当然上面的定义会过于的晦涩,简单总结一下:
- 确定的输入,一定会产生确定的输出;
- 函数在执行过程中,不能产生副作用;
1.2 副作用概念的理解
那么上面又有一个概念,叫做副作用
- 在计算机科学中,副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响
- 比如修改了全局变量,修改参数或者改变外部的存储;
纯函数在执行的过程中就是不能产生这样的副作用:副作用往往是产生bug的 “温床”。
1.3 纯函数的案例
来看一个对数组操作的两个函数:
- slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组;
- splice:splice截取数组, 会返回一个新的数组, 也会对原数组进行修改;
slice 就是一个纯函数,不会修改数组本身,而 splice 函数不是一个纯函数;
1.4 纯函数的作用和优势
为什么纯函数在函数式编程中非常重要
- 因为你可以安心的编写和安心的使用;
- 你在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改;
- 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;
React 中就要求我们无论是函数还是 class 声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改:
在接下来学习 redux 中,reducer 也被要求是一个纯函数。
2. 为什么需要redux
JavaScript 开发的应用程序,已经变得越来越复杂了:
- JavaScript需要管理的状态越来越多,越来越复杂;
- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;
管理不断变化的 state 是非常困难的:
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View 页面也有可能会引起状态的变化;
- 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
React 是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:
- 无论是组件定义自己的 state,还是组件之间的通信通过 props 进行传递;也包括通过 Context 进行数据之间的共享;
- React 主要负责帮助我们管理视图,state 如何维护最终还是我们自己来决定;
Redux 就是一个帮助我们管理State的容器:Redux 是 JavaScript 的状态容器,提供了可预测的状态管理;
Redux 除了和 React 一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)
3. Redux 的核心概念
3.1 store
Redux 的核心理念非常简单。
- 比如我们有一个朋友列表需要管理:
- 如果我们没有定义统一的规范来操作这段数据,那么整个数据的变化就是无法跟踪的;
- 比如页面的某处通过 products.push 的方式增加了一条数据;
- 比如另一个页面通过 products[0].age = 25 修改了一条数据;
整个应用程序错综复杂,当出现 bug 时,很难跟踪到底哪里发生的变化
const initialState ={
friends:[
{
name:'foo',age:18},
{
name:'coo',age:19},
{
name:'hoo',age:20}
]
}
3.2 action
Redux 要求我们通过 action 来更新数据:
- 所有数据的变化,必须通过派发(dispatch)action来更新;
- action是一个普通的JavaScript对象,用来描述这次更新的 type 和 content;
比如下面就是几个更新 friends 的 action:
- 强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可追踪、可预测的;
- 如:下面的 action 是固定的对象;真实应用中,我们会通过函数来定义,返回一个action;
const action1 = {
type: 'ADD_FRIEND', info: {
name:'licy',age:21} }
const action2 = {
type: 'INC_AGE', index:0 }
const action3 = {
type: 'CHANGE_NAME', playload: {
index:0,newName:'lulu'} }
3.3 reducer
reducer 就是将 state 和 action 联系在一起的
- reducer 是一个纯函数;
- reducer 做的事情就是将传入的state和action结合起来生成一个新的state
function reducer(state = initialState, action) {
switch (action.type) {
case "ADD_FRIEND": // 添加一个朋友
return {
...state, friends: [...state.friend, action.info] }
case 'INC_AGE': // 给某个朋友的 age + 1
return {
...state, friends: state.friends.map((item, index) => {
if (index == action.index) {
return {
...item,age:item.age+1}
}
return item;
})
}
case 'CHANGE_NAME':
return {
...state, friends: state.friends.map((item, index) => {
if (index === action.index) {
return {
...item,name:action.newName}
}
return item;
})
}
default:
return state;
}
}
3.4 Redux 的三大原则
单一数据源
- 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中:
- Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;
- 单一的数据源可以让整个应用程序的 state 变得方便维护、追踪、修改;
State是只读的
- 唯一修改 State 的方法一定是触发 action,不要试图在其他地方通过任何的方式来修改State:
- 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;
- 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
使用纯函数来执行修改
- 通过 reducer 将 旧state和 actions联系在一起,并且返回一个新的State:
- 随着应用程序的复杂度增加,我们可以将 reducer 拆分成多个小的 reducers,分别操作不同 state tree 的一部分;
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用
3.5 Redux的使用过程
- 创建一个对象,作为我们要保存的初始状态:
- 创建 Store 来存储这个 state
- 创建 store 时必须创建 reducer;
- 我们可以通过
store.getState
来获取当前的 state;
- 通过 action 来修改 state
- 通过
dispatch
来派发 action; - 通常 action 中都会有 type 属性,也可以携带其他的数据;
- 通过
- 修改reducer中的处理代码
- 这里一定要记住,reducer是一个纯函数,不能直接修改state;
如果我们将所有的逻辑代码写到一起,那么当 redux 变得复杂时,代码就难以维护。
- 接下来,对代码进行拆分,将 store、reducer、action、constants 拆分成一个个文件。
- 创建
store/index.js
文件:创建 store - 创建
store/reducer.js
文件:定义 reducer - 创建
store/actionCreators.js
文件:将所有 action 函数统一定义于此文件 - 创建
store/constants.js
文件:管理 派发 action 时 标注(type)的名称
- 创建
【示例:在 App.jsx 文件中点击修改 name 和 age 按钮,能修改 store 中的数据】
1、store/index.js
文件
import {
createStore } from "redux"
import {
reducer} from './reducer'
// 创建 store
export const store = createStore(reducer);
// 订阅 subscribe; 在订阅了的地方,就能在 state 更新的之后 触发回调
const unsubscribe = store.subscribe(() => {
console.log("订阅数据发生了变化", store.getState());
});
// 调用 unsubscribe 即可取消订阅
// unsubscribe()
2、store/reducer.js
文件
import {
CHANGE_AGE, CHANGE_NAME } from "./constants";
// 初始化的数据
const initialState = {
name: "zs",
age: 20,
};
// 定义 reducer 函数: 纯函数
// 接收 2 个 参数
// 参数一: store中 目前保存的 state
// 参数二:本次需要更新的action(dispatch传入的action)
// 返回值: 返回值会作为 store之后存储的 state
export function reducer(state = initialState, action) {
console.log("reducer:", state, action);
// 如果有新数据更新,那么返回一个 新的 state
switch (action.type) {
case CHANGE_NAME:
return {
...state, name: action.name };
case CHANGE_AGE:
return {
...state, age: action.age };
default:
return state;
}
// 没有新数据更新, 那么返回之前的 state
return state;
}
3、store/actionCreators.js
文件
import {
CHANGE_AGE,CHANGE_NAME} from './constants'
export const changeNameAction = (name) => ({
type: CHANGE_NAME,
name,
});
export const changeAgeAction = (age) => ({
type: CHANGE_AGE,
age,
});
4、store/constants.js
文件
// 定义常量的文件
export const CHANGE_NAME = 'change_name'
export const CHANGE_AGE = 'change_age'
5、App.jsx
文件
import React, {
PureComponent } from 'react'
import {
store } from "./store";
import {
changeAgeAction,changeNameAction} from "./store/actionCreator"
export default class App extends PureComponent {
constructor() {
super()
this.state ={
name:store.getState().name}
}
updateName() {
// 修改Store中的数据:必须用 action
// store.dispatch({ type: 'change_name', name: 'foo' }); // 和下面一行的效果一样
store.dispatch(changeNameAction('foo'));
}
updateAge() {
const data = store.getState();
// store.dispatch({type:'change_age',age:data.age + 1})
store.dispatch(changeAgeAction(data.age + 1));
}
componentDidMount() {
// 在组件挂载的时候去订阅 store;store的数据一更新,就会通知所有订阅者更新
this.unsubscribe = store.subscribe(() => {
const state = store.getState()
this.setState({
name:state.name // 把需要的数据更新到组件自身上
})
})
};
componentWillUnmount() {
this.unsubscribe();// 记得在组件卸载时,取消订阅
}
render() {
return (
<div>
<button onClick={
(e) => this.updateName()}>修改name</button>
<button onClick={
(e) => this.updateAge()}>修改age</button>
</div>
);
}
}
但是如上所示,仅仅在一个组件中,使用 state 的 数据 ,就已经很繁琐了,更别说 组件可能需要很多 state 中的数据,甚至其他组件也要使用;下文中会用 react-redux库 简化流程
3.6 Redux 使用流程
认识一下 redux 在实际开发中的流程:
4. react-redux
4.1 react-redux基本使用
redux 和 react 没有直接的关系,你完全可以在 React, Angular, Ember, jQuery, or vanilla JavaScript 中使用 Redux。
尽管如此,redux 依然是和 React 库结合的更好,因为他们是通过state函数来描述界面的状态,Redux 可以发送状态的更新,让他们作出相应的更新。
redux 官方提供了 react-redux 的库,提供了 connect、Provider 等 API, 帮助我们连接 react 和 redux,实现的逻辑会更加的严谨和高效。
使用步骤:
一:下载 react-redux
库