redux基本概念解析:三大原则与执行流程

3,081 阅读10分钟

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

主要有两个作用:

  1. 用type字段描述我们准备执行的动作,通知reducer进行相应的处理;
  2. 向reducer传递数据,它是store数据的唯一来源。

reducer

也有两个作用:

  1. 分发action,根据action中type的不同,处理不同的事件;
  2. 返回新的state。

redux三大原则

  1. 单一数据源:所有状态都保存在单一的store中

  2. state是只读的:不能直接对store进行修改,只能用新的store替换旧的store

  3. 使用纯函数来执行修改:reducer是只读的