为什么需要redux
原有组件间通信方式错综复杂,逐层传递,不明了,方便组件间通信
用store管理所有组件的状态
redux 特性
single source of truth:状态来源唯一;
可预测性:state +action = new state
纯函数更新Store:输出取决于输入,内部不会依赖任何除参数以外外部元素产生副作用
store 三个函数
getState()
dispatch(action)
subscribe(listener)
action 描述行为的对象集合
reducer 更新state的具体行为
几个工具函数
combineReducers 把多个reducer 合成一个新的reducer,并定义每个reducer在store中的对应节点
bindActionCreators 封装actionCreator和store.dispatch调用过程1
2
3
4
5
6
7
8
9
10function actionCreator(){
return {
type:'addItem',
text:'......'
}
}
store.dispatch(actionCreator())
===>
actionCreator = bindActionCreators(actionCreator,store.dispatch)
actionCreator()
connect 创建高阶组件,对store 特定属性进行监听和绑定,从而在这些属性变化时自动刷新组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72import React from "react";
import { bindActionCreators, createStore } from "redux";
import { Provider, connect } from "react-redux";
// Store initial state
const initialState = { count: 0 };
// reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE":
return { count: state.count + 1 };
case "MINUS_ONE":
return { count: state.count - 1 };
case "CUSTOM_COUNT":
return { count: state.count + action.payload.count };
default:
break;
}
return state;
};
// Create store
const store = createStore(counter);
// Action creator
function plusOne() {
// action
return { type: "PLUS_ONE" };
}
function minusOne() {
return { type: "MINUS_ONE" };
}
export class Counter extends React.Component {
render() {
const { count, plusOne, minusOne } = this.props;//这三者通过connect关联store后获得
return (
<div className="counter">
<button onClick={minusOne}>-</button>
<span style={{ display: "inline-block", margin: "0 10px" }}>
{count}
</span>
<button onClick={plusOne}>+</button>
</div>
);
}
}
//组件需要数据,state是store.getState()获得的数据集合根节点
//性能问题:如果将store中的整个state绑定到组件上,那state上任何一个属性发生变化都会引起组件更新,所以向组件传递数据时,将数据绑定到尽可能的最小范围,实现只有绑定的数据变化时才引起组件更新
function mapStateToProps(state) {
return {
count: state.count
};
}
//组件中需要用到的触发函数
function mapDispatchToProps(dispatch) {
return bindActionCreators({ plusOne, minusOne }, dispatch);
}
//使用connect将redux和react 组件结合起来,形成高阶组件
const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
export default class CounterSample extends React.Component {
render() {
return (
<Provider store={store}> //根节点定义Provider,挂上store,其所有子节点均可以访问到
<ConnectedCounter />
</Provider>
);
}
}
主要流程
1.定义redux store中需要的东西:reducer,actionCreator
2.使用createStore 根据reducer创建store
3.定义组件,确定需要哪些数据和触发函数
4.根据组件需要,创建数据获取函数,封装触发函数相应actionCreator
5.使用connect结合redux和组件形成高阶组件在Provider 中使用
异步Action
异步 action不是特殊action而是多个同步 action的组合使用
一个AJAX的Action发出后,到达中间件,进行预处理,根据预处理的结果dispatch不同的action出去,给reducer,然后更新store
异步Action不是redux的一个概念而是action的一种设计模式
不同的action还是同步的action,只是异步action把这些同步的action放在了不同阶段去dispath
中间件
1.截获某种特定类型的action,进行特殊处理
2.发出action
如何组织action和reducer
‘标准’形式redux action的问题
1.所有Action放一个文件,会无限扩展
2.Action,Reducer分开,实现业务逻辑时需要来回切换
3.系统中有哪些Action不够直观
新的方式
单个action和reducer放在同一个文件,一个文件一个action
然后将所有action导入同一文件,所有reducer导入同一文件
1.易于开发:不用在action和reducer文件间来回切换
2.易于维护:每个action文件都很小,容易理解
3.易于测试:每个业务逻辑只需要对应一个测试文件
4.易于理解:文件名就是action名字,文件列表就是action列表
a sample
问题:拆分这么细的话,文件会多很多,对包的大小也会有影响
答:webpack 确实会为每个文件生成一小段固定的元代码,同样的代码行数,文件越多 bundle 确实会越大。但这个并不会成为性能瓶颈,两个原因:1. 拆分后代码数量不会显著增多,增加的元代码部分相比整个包的大小基本可以忽略;2. 生产环境普遍会启用 gzip,对于重复的元代码信息会被有效压缩,对比拆分前,gzip 后的包大小基本没有差异
不可变数据
redux在修改state时采用新对象替换老对象的思路,不在原来对象上做修改,在新对象中复制老对象,包含要修改的部分,通过对比新老对象的引用不同,引起节点更新—-redux运行基础
为何需要不可变数据
1.性能优化(通过对比新旧state不是同一个引用即可判断store发生了变化,从而不用进行深层遍历,比较具体的值,redux中的store都是不可变数据,每个节点都是不可变数据,当一个组件绑定在一个节点上,只需判断前后状态的引用是否相等,从而判断store是否发生变化,进而决定是否要更新组件)
2.易于调试和跟踪(store变化时可以看到前后状态,便于调试)
3.易于推测(任何时刻可以知道是什么引起store发生变化,根据action前后的状态判断,action是否被进行了正确处理)
如何操作不可变数据
1.原生写法{…},Object.assign() 性能最好1
2
3
4
5
6const state = {
filter:'completed',
todos:['learn react']
}
const newState1 = {...state,todos:[...state.todos,'learn redux']}
const newState = Object.assign({},state,{todos:[...state.todos,'learn redux']})
2.immutability-helper 适合需要深层次节点处理,需要引入类库,熟悉相应语法1
2
3
4
5
6import update from 'immutability-helper'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = update(state,{todos:{$push:['learn redux']})
3.immer 可以像操作原对象一样生成新对象,性能最差(当节点层数多,结构复杂时,需要为每个属性建立代理),适合应用程序小,场景简单的情况1
2
3
4
5
6
7
8import produce from 'immer'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = produce(state,draftState =>{
draftState.todos.push('learn redux')
})