Redux 01 基本概念

Redux 01 基本概念

React全家桶一点一点吃......

在之前的文章里,我提到了React理念好掌握,但是只看书不知道到底实践中代码怎么组织,感谢刘架构提供了一个视频React16+Redux 实战企业级大众点评Web App,这一个星期都在看这个视频,一个星期看了前边四部分,终于把Redux以及connect给整明白了,赶快趁热打铁用费曼学习法输出一下。

Redux的基本概念

ReduxFlux模式的一种实现,这个模式由四部分组成:

动作->分配器->store->渲染视图

对于Redux来说,动作是一个JS对象,其中有一个属性必须为type,一般是一个常量,用于表示这是一个什么动作,在动作中,还会携带这个动作所需要的额外信息。

分配器指的是dispatch,可以把动作理解为一个指令,而分配器就是把这个指令发送到store中,用于改变state

store就是集中存放这个应用所需的全部state的地方,有了Redux之后,我们就很少在应用的各个组件中存放state,而是进行统一管理。store接收到动作之后,使用创建store时候交给storereducer函数来根据动作返回一个新的state,这个state会(部分)替代原来的statereducer必须是一个纯函数,返回的state必须是一个全新的对象。这里可见Redux的设计思想也继承了React函数式编程的思想。

在更新了state之后,就会更新对应的视图,不过这需要把ReduxReact连接起来,这里涉及到高阶组件的概念,有一些很细节很精妙的过程。

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不是同一个东西,在大部分情况下,需要使用额外的数据和函数来创建一个附带有载荷的动作。这里创建了两个默认值函数,如果不传参数,则默认number1,如果传入了就用传入的参数,来表示自增或者自减多少。

storereducer

storereducer这两个东西是redux的精髓,几乎所有的概念都离不开这两个东西。

store的概念

一般store,需要用一个初始的statereducer来创建,等于要让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});

这就创建好了storestore常用的两个方法是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这个流程,现在还剩下一个流程就是把结果渲染出来,这个要怎么做呢?

ReactRedux

关于渲染,可能会想当然的写下边的代码:

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就是要集中状态管理,现在我的组件里还有statestore中也存了一份和组件里state相同的数据,这不是吃饱了撑的嘛。有没有一种方法,就是让store更新之后,无需通过组件自己的state进行中转,直接把变化反映到组件上呢?

答案是有的,就是神奇的react-redux库,用于将React这个负责渲染的UIRedux关联起来。在此之前,需要了解容器型组件与展示型组件概念,而为了了解这两兄弟,还得看一下高阶组件。

Redux全家桶

前边提到了React-Redux,其实Redux团队的东西如下:

  1. Redux,简约不简单的集中状态管理库
  2. React-Redux,库更新的时候通知React刷新组件
  3. Redux-Thunk,弥补Redux不支持异步Action的弱点

这三个只要用到Redux,基本上就是要一起用,因为既然使用了状态管理,那么与后端交互并且更新store,然后通知React刷新组件是必然的应用,所以这几个也是Redux全家桶,Redux全家桶又是React全家桶(React,Redux,React-Router)的一部分。

LICENSED UNDER CC BY-NC-SA 4.0
Comment