在之前的文章里,我提到了React
理念好掌握,但是只看书不知道到底实践中代码怎么组织,感谢刘架构提供了一个视频React16+Redux 实战企业级大众点评Web App
,这一个星期都在看这个视频,一个星期看了前边四部分,终于把Redux
以及connect
给整明白了,赶快趁热打铁用费曼学习法输出一下。
Redux
的基本概念
Redux
是Flux
模式的一种实现,这个模式由四部分组成:
动作->分配器->store
->渲染视图
对于Redux
来说,动作是一个JS
对象,其中有一个属性必须为type
,一般是一个常量,用于表示这是一个什么动作,在动作中,还会携带这个动作所需要的额外信息。
分配器指的是dispatch
,可以把动作理解为一个指令,而分配器就是把这个指令发送到store
中,用于改变state
。
store
就是集中存放这个应用所需的全部state
的地方,有了Redux
之后,我们就很少在应用的各个组件中存放state
,而是进行统一管理。store
接收到动作之后,使用创建store
时候交给store
的reducer
函数来根据动作返回一个新的state
,这个state
会(部分)替代原来的state
。reducer
必须是一个纯函数,返回的state
必须是一个全新的对象。这里可见Redux
的设计思想也继承了React
函数式编程的思想。
在更新了state
之后,就会更新对应的视图,不过这需要把Redux
和React
连接起来,这里涉及到高阶组件的概念,有一些很细节很精妙的过程。
action
动作
动作是一个对象,对象必定有一个属性为type
,用于检测是什么类别的动作,类别一般用常量定义。要更新store
中的state
的话,动作附带的数据是唯一来源,不可以将数据放在其他地方。通过store.dispatch(action)
就可以将动作传递到store
。
比如一个最简单的自增或者自减1的动作,可以定义type
如下:
const actionTypes = {
SELF_INCREMENT: "SELF_INCREMENT",
SELF_DECREMENT: "SELF_DECREMENT",
}
export default actionTypes;
然后可以定义两个创建action
的函数:
import actionTypes from "./actionType";
export const selfIncrement = (number = 1) => {
return {
type: actionTypes.SELF_INCREMENT,
number,
}
}
export const selfDecrement = (number = 1) => {
return {
type: actionTypes.SELF_DECREMENT,
number,
}
}
注意,action
创建函数和action
不是同一个东西,在大部分情况下,需要使用额外的数据和函数来创建一个附带有载荷的动作。这里创建了两个默认值函数,如果不传参数,则默认number
是1
,如果传入了就用传入的参数,来表示自增或者自减多少。
store
和reducer
store
和reducer
这两个东西是redux
的精髓,几乎所有的概念都离不开这两个东西。
store
的概念
一般store
,需要用一个初始的state
和reducer
来创建,等于要让store
知道一个初始的状态,以及如何去根据动作创建新的状态,创建store
的函数是Redux
提供的createStore
。
createStore
的标准函数签名如下:
createStore(reducer, [preloadedState], [enhancer])
其中reducer
必须传入,后边两个是可选的。第二个参数是初始的state
,如果采用了组合reducer
方式,则这个state
的各个属性名必须和combineReducers
的属性名一直。第三个参数是一个增强器,后边专门介绍。
对于简单的应用来说,先传入前两个参数,对于自增自减应用,很显然只需要有一个值保存即可。接下来就要看reducer
。
reducer
reducer
是一个纯函数,接受两个参数,签名是type Reducer<S, A> = (state: S, action: A) => S
,第一个参数是state
(这个state
是当前reducer
对应的state
,也就是说如果使用了combineReducers
,这个参数是各个子reducer
自己负责的那一部分state
,而不是全局state
)。第二个参数是接受的动作,返回值在前边已经说了,是一个与原来state
没有关联的纯对象,官网的最佳实践中说的很清楚,reducer
不能有副作用。
我们来写这个自增自减的reducer
:
import actionTypes from "../action/actionType";
const changeReducer = (state, action) => {
switch (action.type) {
case actionTypes.SELF_INCREMENT:
return {
value: state.value + action.number,
};
case actionTypes.SELF_DECREMENT:
return {
value: state.value - action.number,
};
default:
return {
...state
};
}
};
export default changeReducer;
这个reducer
判断动作的种类,如果是自增,就用当前状态中的value
值加上动作载荷中的值,如果是自减,就用当前状态中的value
值减去动作载荷中的值,如果不是上述两种动作,就不进行任何操作。注意这里我们的返回值,全部都是一个全新组装的对象,与原来state
的引用没有任何关系。
创建store
有了reducer
之后,我们也知道了state
中的属性名称叫做value
,那么就可以创建store
了,一般会在index.js
中创建store
,并用Provider
来包裹App
组件,这是后话了。
来创建store
如下:
const store = createStore(changeReducer, {value: 1});
这就创建好了store
,store
常用的两个方法是getState()
和dispatch(action)
。
我们现在已经知道,dispatch
内部实际上就是调用我们写的reducer
,计算出新的状态之后去更新state
,而getState()
就是从store
中获取最新的状态。
dispatch
我们来试着创建一个动作,然后去发送给store
,这些代码都写在index.js
中,启动程序的时候就可以得到执行:
const action1 = selfIncrement(10);
store.dispatch(action1);
console.log(store.getState());
const action2 = selfDecrement(6)
store.dispatch(action2);
console.log(store.getState());
这两个运行之后,先打印出11=1+10
,然后打印出5=11-6
。
这样我们就完成了最前边的动作->分配器->store
这个流程,现在还剩下一个流程就是把结果渲染出来,这个要怎么做呢?
React
与Redux
关于渲染,可能会想当然的写下边的代码:
import React from 'react';
import './App.css';
import {store} from "./index";
import {selfIncrement} from "./action/actionBuilder";
class App extends React.Component{
handleIncrement = (e)=>{
e.preventDefault();
store.dispatch(selfIncrement(1));
}
render() {
return (
<div className="App">
<button onClick={this.handleIncrement}>+1</button>
<button>-1</button>
<h2>{store.getState().value}</h2>
</div>
);
}
}
export default App;
初看似乎很美好,点击按钮之后,发送一个自增1的动作到store
中,然后通过{store.getState().value}
取出值渲染。但实际执行就会发现,点击按钮什么也没有发生,动作发送了,store
也更新了,但是组件不会刷新,因为组件自己没有状态。
那怎么办,加上状态吧:
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
current: store.getState().value
}
}
handleIncrement = (e)=>{
e.preventDefault();
store.dispatch(selfIncrement(1));
this.setState({current:store.getState().value});
}
render() {
return (
<div className="App">
<button onClick={this.handleIncrement}>+1</button>
<button>-1</button>
<h2>{this.state.current}</h2>
</div>
);
}
}
export default App;
现在点击就可以正常自增1了,等等。。。我本来引入Redux
就是要集中状态管理,现在我的组件里还有state
,store
中也存了一份和组件里state
相同的数据,这不是吃饱了撑的嘛。有没有一种方法,就是让store
更新之后,无需通过组件自己的state
进行中转,直接把变化反映到组件上呢?
答案是有的,就是神奇的react-redux
库,用于将React
这个负责渲染的UI
和Redux
关联起来。在此之前,需要了解容器型组件与展示型组件概念,而为了了解这两兄弟,还得看一下高阶组件。
Redux全家桶
前边提到了React-Redux,其实Redux团队的东西如下:
- Redux,简约不简单的集中状态管理库
- React-Redux,库更新的时候通知React刷新组件
- Redux-Thunk,弥补Redux不支持异步Action的弱点
这三个只要用到Redux,基本上就是要一起用,因为既然使用了状态管理,那么与后端交互并且更新store,然后通知React刷新组件是必然的应用,所以这几个也是Redux全家桶,Redux全家桶又是React全家桶(React,Redux,React-Router)的一部分。