redux本身是一个有一定难度的东西,对新手来说学起来颇为吃力。所以在这篇文章中,我将从建立一个最简单的例子开始,最开始只使用react-native和redux,然后在后续的教程中,逐渐加上redux-saga,flow,Immutable,code-push等工具,逐步将这个例子拓展可供实际生产环境使用的demo。同时在拓展的过程中逐步讲解redux原理,希望本文能对你有所帮助。
编写基于redux的hello world
安装redux
进入项目根目录,打开控制台,利用npm或者yarn下载redux相关的包。
npm install --save redux
如果你使用yarn:
yarn add redux
创建store
创建store.js(文件名可自取)文件,并输入以下代码:
import { createStore } from 'redux';
import helloWorld from 'reducer文件所在地址';
const initValue={
helloWorld:'initText'
};
const store=createStore(helloWorld,initValue);
export default store;
在这段代码里,我们利用redux提供的createStore 方法,创建了一个store对象。这个对象只保存了一个属性,即helloWorld
createStore方法接收三个参数,第一个传入reducer(我们很快会讲到);第二个参数是我们保存在redux中的状态初始值,这样app在启动时,不用我们做一些额外操作,redux就能自动拥有一些数据;第三个参数可选,代表Store Enhancer,一般用来操作中间件,我们现在暂时用不到。
什么是store?
众所周知,redux是一个状态管理容器(在react-native中,你可以将状态理解为组件中的state)。既然是容器,那么必然要有一个地方用来存放状态,store便起到这样的作用。
store并不是redux的里面保存的状态本身,而是提供了一系列的函数,方便我们对状态进行操作,它们分别是:
-
dispatch: 用来修改state;
-
subscribe: 监听状态是否发生改变;
-
getState: 获得保存在redux中的状态;
你可以把store想象成一个盒子,平时我们把页面中的状态都存放到这个盒子里,需要的时候就从里面取出来。不过有一点需要注意,我们只能从store中读取数据,而不能对其内部的数据进行修改。
那么我们要怎么修改状态呢?这就是reducer的任务了。
redux三大设计原则之一:单一数据源
引用官方的话:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
通俗简单地概括,就是所有的state都被保存在一个对象里面。尽管页面中的可能有成千上百个state,但redux都将它们统统保存在一个objcet里面(这个object可以通过store.getState()这个方法得到)。redux并不禁止创建多个store,但这样做没必要。从单一的数据源中获取state,明显会为开发带来很多便利。
创建reducer
接下来,创建一个名为reducer.js的文件,并复制以下代码。
const reducer = function(state, action) {
return state;
};
export default reducer;
这个reducer并不具备任何功能,只是创建store的时候必须要有一个reducer函数,所以我们暂时先不管他。
做好这两步,虽然还不具备任何功能,但app已经能够成功执行了。下面就让我们开始正式构建redux版的hello world吧!
创建页面
首先让我们创建这样一个简单的页面:页面中间有一个按钮,点击该按钮页面会alert出一行文字"hello world"。
这里用react-native作为代码演示,如果你用react或者其他框架,也可以制作一个自己喜欢的页面,只要页面上有一个按钮就行:
import React,{Component} from 'react';
import {
View,
Button,
} from "react-native";
import store from './redux/store/configure-store'
class HelloWorld extends Component {
constructor(props){
super(props);
this.state={
alertTexts:store.getState().helloWorld
}
}
render(){
return (
<View style={{
flex:1,
backgroundColor:'#c1c1c1',
alignItems:'center',
justifyContent:'center',
}}>
<Button
onPress={this.sayHelloWorld}
title="press me (hello world!)"
color="#841584"
/>
</View>
);
}
sayHelloWorld=()=>{
alert(this.state.alertTexts)
}
}
export default HelloWorld;
运行不出错的话,你应该看到如下样式。

点击中间那个按钮,会弹出带有initText字样的警示框。
在这个页面里,关于redux代码只有两行。
一是在头部,我们通过import导入了我们创建的store;
import store from './redux/store/configure-store'
二、是在构造器constructor中,我们通过store.getState().helloWorld,获得了store初始化时的值,并赋给了this.state.alertTexts。
constructor(props){
super(props);
this.state={
alertTexts:store.getState().helloWorld
}
}
:::
聪明的你应该已经猜到了,通过store.getState()方法就可以取出state。
接下来,就让我们通过redux,把initText修改为hello world吧!
发出action
在redux中,要想修改redux中的数据,有且只有一个方法,就是发出一个action。
在上面那段代码的基础上,我们修改sayHelloWorld这个方法如下(顺便一提,这种写法是ES6语法中的箭头函数,效果等同于一个function):
sayHelloWorld=()=>{
store.dispatch({
type:'changeTextToHelloWorld',
word:'hello word'
})
}
在这段代码中,我们通过store自带的dispatch方法发出了一个action。
什么是action?
action本身不具有任何业务相关的逻辑,你可以理解为action就是让我们告诉redux我们准备做什么事。
就跟调用函数时,我们要用函数名区分不同的函数一样。redux也要用不同的action名称,区分你发出的是哪个action。只不过函数名直接用字面量描述,而action一般用一个对象描述(这个对象至少应该包含一个type属性,type的值便是用来描述action的字符串)。另外,要成功发出一个action只能使用dispatch方法。
当redux检测到一个action被dispatch时,这些数据就会被传入reducer里面。
关于store.dispatch
dispatch方法接收一个对象,该对象有一个必须的字段——type。type属性用来区分我们发出的是什么样的action,type的值可以随意填写(一般是一个字符串),只要不与其他action的type重复就好。
其他的属性(例如上面的word)我们可以随意增加,这些属性我们可以在reducer里面取到。
redux三大设计原则之二:State 是只读的
这句话的意思就是,你只能读取存储在redux中的state而不能直接修改(即用=号直接对它赋值),唯一改变 state 的方法就是触发 action。
state设计成只读的,可以防止我们从页面直接对state进行修改,避免了项目较大时的逻辑以及数据交互的混乱。在本文的末尾会有更详细的原因说明。
编写reducer
打开我们之前创建的reducer文件,修改代码:
const reducer = function(state, action) {
switch (action.type) {
case 'changeTextToHelloWorld':
return {...state, helloWorld: action.word};
default:
return state
}
};
export default reducer;
你可以看到reducer里面有两个参数,第一个是state——也就是存放在redux中的状态(还记得上面说过的单一数据源吗)。
第二个参数便是我们上面提到的action,它的值与我们在dispatch中传递的参数相同。
在reducer函数内部,我们从action中取出type属性(也就是我们用来描述action的字符串),放到switch中进行判断,这样就达到了区分不同action的目的。当我们有新的action要处理时,只要增加一个case就好。
然后我们再取出action中的word属性——也就是"helloWorld"这个字符串,与旧的state合并成新的对象,然后把它return出去。
当reducer函数return了一个新的对象时,redux会用这个对象直接替换掉旧的状态,这样redux中的状态就被更新了。
有些人可能会困扰 {...state, helloWorld: 'helloWorld'} 是什么意思。这是ES6中的新语法——扩展运算符。这个写法与Object.assign()作用相同,等值于下面的语句:
Object.assign({},state,helloWorld: 'helloWorld')
什么是reducer?
前面说到,redux中有一个负责存放状态的Objcet(我们简称state),然而在redux的设计哲学中,你不能直接对store对象本身进行操作(state 是只读的)。只有当reducer函数return一个新的对象时,旧的状态被该对象替换掉,redux的数据才会更新。
还记得在配置store的时候,我们在createStore函数里传入了reducer吗?实际上,redux的逻辑很简单,当你dispatch一个action的时候,它会直接调用你传进去reducer函数,并把action和state作为参数传进去,然后把reducer的返回值赋值给state。
总而言之:reducer参数里的state是旧的store,return的state是新的store。当新的store被return时,store中的数据就更新了。
redux三大设计原则之三:使用纯函数来执行修改
这一条也可以描述为:reducer是一个纯函数。
什么是纯函数?纯函数的意思就是,函数的返回结果只依赖于它的参数,而不依赖外部数据;同时在函数执行过程中,也不会对其他外部数据产生影响。
更直白的描述是,纯函数的返回值只允许通过对参数进行计算得来。修改参数,操作/获取外部的变量,打印log,发送http请求等操作都是不允许的。
纯函数的使用是非常安全的,因为我们不必担心它做出一些出乎我们意料之外的操作。一个纯函数在相同的输入下总会有相同的输出。
正因为直接修改reducer的参数是不被允许的(因为在函数执行的时候,参数实际是外部传进来的对象),所以我们最后通过return的方式直接替换整个state,而不是直接修改旧的state。
订阅store变化
redux的数据已经产生变化了,然而页面本身是没法知道redux是否变化的,这时候就要用到store的订阅功能。
打开root.js文件,在组件HelloWorld的两个生命周期函数里面,分别写入注册和移除store订阅的代码:
class HelloWorld extends Component {
//...省略其他代码
//注册监听
componentDidMount() {
this.storeSubscribe=store.subscribe(()=>{
alert(store.getState().helloWorld)
})
}
//页面注销时移除监听
componentWillUnmount() {
this.storeSubscribe.unsubscribe();
}
//...省略其他代码
}
调用tore.subscribe()并传入一个回调函数,当redux中的状态发生改变时,就会直接调用这个回调函数。在回调函数内部,可以通过store.getState()获取到改变后的状态。
这时候我们重新加载页面,再点击中间的按钮,就会惊喜地发现警示框里面的语句变成了helloWorld。
恭喜你,你已经掌握了redux的基本概念。
知识点总结
redux执行流程图

总而言之,redux遵循这样一个执行流程:
1.组件利用store自带的dispatch方法,发出一个action。
2.redux接收到action,将业务分发给对应的reducer处理。
3.reducer处理完毕后,返回处理后的state对象。
4.redux接收到新的state对象后,通知store.subscribe()函数执行。
永远牢记redux的执行流程:组件=>action=>reducer=>store=>组件。
虽然看似概念很多,但在绝大多数情况下,我们只需要关注组件、action、reducer这三个部分中的代码就好。
redux三大件:store、action、reducer简述
store
一个对象,保存着对redux中的操作方法,包括dispatch(发送action),subscribe(订阅redux中的状态变化)和getState(获得状态)。store是只读的,Redux 应用有且只有一个单一的 store。
action
主要有两个作用:
- 用type字段描述我们准备执行的动作,通知reducer进行相应的处理;
- 向reducer传递数据,它是store数据的唯一来源。
reducer
也有两个作用:
- 分发action,根据action中type的不同,处理不同的事件;
- 返回新的state。
redux三大原则
-
单一数据源:所有状态都保存在单一的store中
-
state是只读的:不能直接对store进行修改,只能用新的store替换旧的store
-
使用纯函数来执行修改:reducer是只读的