Redux 和 React-redux 梳理

784 阅读11分钟

随着我们的需求不断地提升,我们需要进行更加复杂的数据传递,更多层次的数据交换。因此我们为何不可以将所有的数据交给一个中转站,这个中转站独立于所有的组件之外,由这个中转站来进行数据的分发,这样不管哪个组件需要数据,我们都可以很轻易的给他派发。 而有这么一个库就可以帮助我们来实现,那就是 Redux ,它可以帮助我们实现集中式状态管理

什么情况使用 Redux ?

首先,我们先明晰 Redux 的作用 ,实现集中式状态管理。

Redux 适用于多交互、多数据源的场景。简单理解就是复杂

从组件角度去考虑的话,当我们有以下的应用场景时,我们可以尝试采用 Redux 来实现

  1. 某个组件的状态需要共享时
  2. 一个组件需要改变其他组件的状态时
  3. 一个组件需要改变全局的状态时

除此之外,还有很多情况都需要使用 Redux 来实现(还没有学 hook,或许还有更好的方法)

image-20210909194446988

这张图,非常形象的将纯 React 和 采用 Redux 的区别体现了出来

Redux 的工作流程

image-20210909194900532

首先组件会在 Redux 中派发一个 action 方法,通过调用 store.dispatch 方法,将 action 对象派发给 store ,当 store 接收到 action 对象时,会将先前的 state 与传来的 action 一同发送给 reducerreducer 在接收到数据后,进行数据的更改,返回一个新的状态给 store ,最后由 store 更改 state

img

(图来自掘金社区,侵删)

Redux 三个核心概念

1. store

store 是 Redux 的核心,可以理解为是 Redux 的数据中台,我们可以将任何我们想要存放的数据放在 store 中,在我们需要使用这些数据时,我们可以从中取出相应的数据。因此我们需要先创建一个 store ,在 Redux 中可以使用 createStore API 来创建一个 store

在生产中,我们需要在 src 目录下的 redux 文件夹中新增一个 store.js 文件,在这个文件中,创建一个 store 对象,并暴露它

因此我们需要从 redux 中暴露两个方法

import {
    createStore,
    applyMiddleware
} from 'redux'

并引入为 count 组件服务的 reducer

import countReducer from './count_reducer'

最后调用 createStore 方法来暴露 store

export default createStore(countReducer, applyMiddleware(thunk))

这里采用了中间件,本文应该不会写到~

store 对象下有一些常用的内置方法

获取当前时刻的 store ,我们可以采用 getStore 方法

const state = store.getState();

在前面我们的流程图中,我们需要通过 store 中的 dispatch 方法来派生一个 action 对象给 store

store.dispatch(`action对象`)

最后还有一个 subscribe 方法,这个方法可以帮助我们订阅 store 的改变,只要 store 发生改变,这个方法的回调就会执行

为了监听数据的更新,我们可以将 subscribe 方法绑定在组件挂载完毕生命周期函数上,但是这样,当我们的组件数量很多时,会比较的麻烦,因此我们可以直接将 subscribe 函数用来监听整个 App组件的变化

store.subscribe(() => {
    ReactDOM.render( < App /> , document.getElementById('root'))
})

2. action

actionstore 中唯一的数据来源,一般来说,我们会通过调用 store.dispatch 将 action 传到 store

我们需要传递的 action 是一个对象,它必须要有一个 type

例如,这里我们暴露了一个用于返回一个 action 对象的方法

export const createIncrementAction = data => ({
    type: INCREMENT,
    data
})

我们调用它时,会返回一个 action 对象

3. reducer

在 Reducer 中,我们需要指定状态的操作类型,要做怎样的数据更新,因此这个类型是必要的。

reducer 会根据 action 的指示,对 state 进行对应的操作,然后返回操作后的 state

如下,我们对接收的 action 中传来的 type 进行判断

export default function countReducer(preState = initState, action) {
    const {
        type,
        data
    } = action;
    switch (type) {
        case INCREMENT:
            return preState + data
        case DECREMENT:
            return preState - data
        default:
            return preState
    }
}

更改数据,返回新的状态

创建 constant 文件

在我们正常的编码中,有可能会出现拼写错误的情况,但是我们会发现,拼写错误了不一定会报错,因此就会比较难搞。

我们可以在 redux 目录下,创建一个 constant 文件,这个文件用于定义我们代码中常用的一些变量,例如

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

将这两个单词写在 constant 文件中,并对外暴露,当我们需要使用时,我们可以引入这个文件,并直接使用它的名称即可

直接使用 INCREMENT 即可

实现异步 action

一开始,我们直接调用一个异步函数,这虽然没有什么问题,但是难道 redux 就不可以实现了吗?

incrementAsync = () => {
    const { value } = this.selectNumber
    const { count } = this.state;
    setTimeout(() => {
        this.setState({ count: count + value * 1 })
    }, 500);
}

我们可以先尝试将它封装到 action 对象中调用

export const createIncrementAsyncAction = (data, time) => {
    // 无需引入 store ,在调用的时候是由 store 调用的
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncrementAction(data))
        }, time)
    }
}

当我们点击异步加操作时,我们会调用这个函数,在这个函数里接收一个延时加的时间,还有action所需的数据,和原先的区别只在于返回的时一个定时器函数

但是如果仅仅这样,很显然是会报错的,它默认需要接收一个对象

如果我们需要实现传入函数,那我们就需要告诉:你只需要默默的帮我执行以下这个函数就好!

这时我们就需要引入中间件,在原生的 redux 中暴露出 applyMiddleware 中间件执行函数,并引入 redux-thunk 中间件(需要手动下载)

import thunk from 'redux-thunk'

通过第二个参数传递下去就可以了

export default createStore(countReducer, applyMiddleware(thunk))

注意:异步 action 不是必须要写的,完全可以自己等待异步任务的结果后再去分发同步action

Redux 三大原则

理解好 Redux 有助于我们更好的理解接下来的 React -Redux

第一个原则

单向数据流:整个 Redux 中,数据流向是单向的

UI 组件 ---> action ---> store ---> reducer ---> store

第二个原则

state 只读:在 Redux 中不能通过直接改变 state ,来控制状态的改变,如果想要改变 state ,则需要触发一次 action。通过 action 执行 reducer

第三个原则

纯函数执行:每一个reducer 都是一个纯函数,不会有任何副作用,返回是一个新的 state,state 改变会触发 store 中的 subscribe

Redux简介

  • redux是react全家桶的一员,它试图为 React 应用提供「可预测化的状态管理」机制。

  • Redux是将整个应用状态存储到到一个地方,称为store

  • 里面保存一棵状态树(state tree)

  • 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件

  • 其它组件可以通过订阅store中的状态(state)来刷新自己的视图

Redux核心

1 State

state是数据集合

可以理解为工厂加工商品所需的原材料

2 action

State的变化,会导致View的变化。但是,用户接触不到 State,只能接触到View 所以,State的变化必须是 View导致的。

action就是改变state的指令,有多少操作state的动作就会有多少action。

可以将action理解为描述发生了什么的指示器

3 reducer 加工函数

action发出命令后将state放入reucer加工函数中,返回新的state。 可以理解为加工的机器

4 store

store 可以理解为有多个加工机器的总工厂

let store = createStore(reducers);

Store 就是把它们联系到一起的对象。Store 有以下职责:

维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。

我们可以通过store.getState()来了解工厂中商品的状态, 使用store.dispatch发送action指令。

经典案例

这是一个redux的经典案例

  • 定义reducer函数根据action的类型改变state

  • actions 定义指令

  • 通过createStore创建store

  • 调用store.dispatch()发出修改state的命令

 import { createStore } from 'redux'
    
    const reducer = (state = {count: 0}, action) => {
      switch (action.type){
        case 'INCREASE': return {count: state.count + 1};
        case 'DECREASE': return {count: state.count - 1};
        default: return state;
      }
    }
    
    const actions = {
      increase: () => ({type: 'INCREASE'}),
      decrease: () => ({type: 'DECREASE'})
    }
    
    const store = createStore(reducer);
    
    store.subscribe(() =>
      console.log(store.getState())
    );
    
    store.dispatch(actions.increase()) // {count: 1}
    store.dispatch(actions.increase()) // {count: 2}
    store.dispatch(actions.increase()) // {count: 3}

我们可以直接在react component上使用store.dispatch,但是这样不太方便,这个时候我们需要react-redux

class Todos extends Component {
    render(){
        return(
            <div onCLick={()=>store.dispatch(actions.delTodo()) }>test</div>
        )
    }
}

React-redux

Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。

1 安装

npm install --save react-redux

2 核心

  • < Provider store>
  • connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

Provider 内的任何一个组件(比如这里的 Comp),如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件(MyComp)」进行包装后的产物。

这个函数允许我们将 store 中的数据作为 props 绑定到组件上。

简单的流程如下图所示:

react-redux中的connect方法将store上的getState 和 dispatch 包装成组件的props。

将之前直接在组件上dispatch的代码修改为如下:

index.js

import React, { Component } from 'react';
import store from '../store';
import actions from '../store/actions/list';
import {connect} from 'react-redux';

class Todos extends Component {
    render(){
        return(
            <div onCLick={()=>this.props.del_todo() }>test</div>
        )
    }
}

export default connect(
    state=>state,
    actions
)(Todos);

Provider 能拿到关键的store并传递给每个子组件

React Redux 将组件区分为 容器组件 和 UI 组件

  1. 前者会处理逻辑
  2. 后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控

两个核心

  • Provider

    看我上边那个代码的顶层组件4个字。对,你没有猜错。这个顶级组件就是Provider,一般我们都将顶层组件包裹在Provider组件之中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中去

    <Provider store = {store}>
        <App />
    <Provider>
    
    这个组件的目的是让所有组件都能够访问到Redux中的数据。 
    
  • connect

    这个才是react-redux中比较难的部分,我们详细解释一下

    首先,先记住下边的这行代码:

    connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    

    mapStateToProps

    这个单词翻译过来就是把state映射到props中去 ,其实也就是把Redux中的数据映射到React中的props中去。

    举个栗子:

    const mapStateToProps = (state) => {
      return {
      	// prop : state.xxx  | 意思是将state中的某个数据映射到props中
        foo: state.bar
      }
    }

然后渲染的时候就可以使用this.props.foo

class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
        	// 这样子渲染的其实就是state.bar的数据了
            <div>this.props.foo</div>
        )
    }
}
Foo = connect()(Foo);
export default Foo;

然后这样就可以完成渲染了

mapDispatchToProps

这个单词翻译过来就是就是把各种dispatch也变成了props让你可以直接使用

const mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch
  return {
    onClick: () => {
      dispatch({
        type: 'increatment'
      });
    }
  };
}
class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
        	
             <button onClick = {this.props.onClick}>点击increase</button>
        )
    }
}
Foo = connect()(Foo);
export default Foo;

组件也就改成了上边这样,可以直接通过this.props.onClick,来调用dispatch,这样子就不需要在代码中来进行store.dispatch了

react-redux的基本介绍就到这里了

connect如何工作的?

connect() 接收四个参数,它们分别是 mapStateToProps , mapDispatchToProps, mergeProps 和 options 。

1 mapStateToProps这个函数允许我们将 store 中的数据作为 props 绑定到组件上。

reducer.js

export default function (state = { lists: [{text:'移动端计划'}],newType:'all'}, action) {
    switch (action.type) {
        case types.ADD_TODO:
            return {...state,lists:[...state.lists,{text:action.text}]}
        case types.TOGGLE_TODO:
            return {...state,lists:state.lists.map((item,index)=>{
                if(index == action.index){
                    item.completed = !item.completed
                }
                return item
            })}
        case types.DEL_TODO:
            return {...state,lists:[...state.lists.slice(0,action.index),...state.lists.slice(action.index+1)]}
        case types.SWITCH_TYPE:
            console.log({...state,newType:action.newType})
            return {...state,newType:action.newType}
        default:
            return state;
    }
}

在reducer.js中,定义了初始化的state,通过connect方法,我们就能使用this.props.lists拿到初始化的state。

import React, { Component } from 'react';
import store from '../store';
import actions from '../store/actions/list';
import {connect} from 'react-redux';

class Todos extends Component {
    render(){
        return(
            {
                + <ul>
                +    this.props.state.lists.map(list =>(
                +        <li>{list.text}</li>
                +    ))
                + </ul>   
            }
            <div onCLick={()=>this.props.del_todo() }>test</div>
        )
    }
}

export default connect(
    state=>state,
    actions
)(Todos);

当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给 MyComp。

2 mapDispatchToProps(dispatch, ownProps): dispatchProps connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props 绑定到 MyComp 上。

action.js

import * as types from "../action-types";

export default{
    add_todo(text){
        return { type: types.ADD_TODO, text: text}
    },
    del_todo(idx){
        return {type:types.DEL_TODO, index: idx}
    },
    toggle_todo(index){
        return {type:types.TOGGLE_TODO, index}
    },
    del_todo(index){
        return {type:types.DEL_TODO, index}
    },
    switch_type(newType){
        return {type:types.SWITCH_TYPE, newType}
    }
}

我在action.js中定义的修改状态的命令,会通过connect 的 mapDispatchToProps方法变为props绑定在reac组件上。

我们可以方便得使用去调用

    <div onCLick={()=>this.props.del_todo() }>test</div>

深入

了解到这里,我们会发现并没有使用store.dispatch方法去发出命令,但是state已经修改,view也变化了,那么到底发生了什么?

store.dispatch(actions.increase())

关键的是connect()

connect原理简化版

import React,{Component} from 'react';
import {bindActionCreators} from 'redux';
import propTypes from 'prop-types';

export default function(mapStateToProps,mapDispatchToProps){
   return function(WrapedComponent){
      class ProxyComponent extends Component{
          static contextTypes = {
              store:propTypes.object
          }
          constructor(props,context){
            super(props,context);
            this.store = context.store;
            this.state = mapStateToProps(this.store.getState());
          }
          componentWillMount(){
              this.unsubscribe = this.store.subscribe(()=>{
                  this.setState(mapStateToProps(this.store.getState()));
              });
          }
          componentWillUnmount(){
              this.unsubscribe();
          }
          render(){
              let actions= {};
              if(typeof mapDispatchToProps == 'function'){
                actions = mapDispatchToProps(this.store.disaptch);
              }else if(typeof mapDispatchToProps == 'object'){
                  console.log('object', mapDispatchToProps)
                actions = bindActionCreators(mapDispatchToProps,this.store.dispatch);
              }
                return <WrapedComponent {...this.state} {...actions}/>
         }
      }
      return ProxyComponent;
   }
}

1.state的返回 connect中对于Provided父组件上传来的store,通过将状态返回

mapStateToProps(this.store.getState());

通过 Redux 的辅助函数 bindActionCreators(),用dispatch监听每一个action。

 bindActionCreators(mapDispatchToProps,this.store.dispatch);

所以调用props上的方法时,会自动发起store.dispach(XXX)事件,发出命令

react-redux简单例子项目链接

参考

redux一点就透

Redux 入门教程(三):React-Redux 的用法