Redux 简介

React技术栈的主要适用情景是Web应用,Web应用涉及非常多的用户交互和状态数据改变。我们应该尽量控制应用中有状态组件的个数,应用数据应该尽量集中统一管理,否则后期维护成本较高。之前介绍有状态组件与无状态组件,React也推荐我们控制有状态组件的个数,在我们开发过程中编写的90%都应该是无状态组件。React本身提供给我们控制引用数据的方式只有props、state 及带有实验性质的context。React本身并没有提供应用状态管理的解决方案,在小的应用中可能感受不到状态管理是一个问题,但随着应用复杂度的增加,这个问题就会暴露得越来越明显,这个时候就可以考虑使用 Redux。

既然 React 推荐我们尽量少的编写有状态组件,为何不干脆把一个应用所有的状态数据集中到一个地方管理呢?这便是Redux的理念。Redux 把应用的数据统一储存在一个对象当中,把应用的所有的数据交互及引用状态的改变统一用固定形式的 action 动作对象来描述,并由 reducer 的方法来判断不同的动作如何改变引用状态,最后通过 store 对象来执行 action 动作获取 state 应用状态,并触发改变应用状态的事件。

实际上Redux提供的是一种应用状态的解决思路,我们可以用基本的原生JS方法来实现 Redux 的全部功能。并且我们就算引入 Redux 编写的大部分的是原生的JS函数和对象。

举个例子 ,原生JS模拟实现 Redux。

  1. 首先定义一个 counter 的函数,接受 state 和 action 参数。state 就是状态数据,而 action 是一个带有 type 的 js 对象。
  2. 通过对 type 的判断,返回新的 state,这样的函数在 redux 中被称为 redcer。
  3. 通过 counter 返回的默认值作为容器的初始state 值,为组件添加 increment、decrement 方法,事实上都是调用 dispatch 方法。
  4. 在 dispatch 方法中调用 counter 函数,将先用的应用状态和 action 方法传递。
import React, { Component } from 'react';

// Reducer
const Reducer = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  // State
  constructor(props) {
    super(props)
    this.state = Reducer(undefined,{})
  }
  
  dispatch(action) {
    this.setState(prevState => Reducer(prevState, action));
  }
    
  // Action
  increment = () => {
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    this.dispatch({ type: 'DECREMENT' });
  };
  
  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

Action

Action 是带有type属性的js对象。{ type: 'INCREMENT' }在Redux的应用操作中,我们要把所有改变应用状态的操作,规范为一个个 Action。在 Action 当中,我们也可以赋上要用来修改应用状态state的具体数据{ type: 'INCREMENT', num: 1 }。上述我们可以看到两点 Action 的作用:

  1. 定义我们的应用可以进行操作的动作或者操作的类型
  2. 传递改变引用状态的数据

Action的定义要遵从一定的标准:Flux 标准 Action

{
    // Action 类型
    type: 'INCREMENT',
    // payload 中返回我们要传递的数据,用来修改 state
    paload: {
        num: 1
    },
    // payload 数据未获取成功时返回 true
    error:false,
    // 一些不必要在 paload 中传递的其他数据
    meta: {
        success: true
    }
}

为了防止我们代码中出现很多写死的数值,减少hard code,可以使用 Redux 当中 actionCreator 的方法。

const counterActionGenerator = (type, num) => (num) => {
    let action = { type , num }
    return action
}

const addNumber = counterActionGenerator('INCREMENT', null )
const minsNumber = counterActionGenerator('DECREMENT', null )

为了清晰的表达我们所有action动作,可以在应用的开头,或者一个独立的文件中定义所有的action type。

const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
  • 定义action类型的文件类似于一个说明文档,当你想要为应用添加新特性时,可以查阅应用已有的action类型,避免冲突。
  • 在版本管理上也可以清晰看到action的记录的增删改查。
  • 如果发现错误,在import就会发现代码报错,更快的进行调试。

Reducer

在之前函数中定义了 counter 函数,它的内部时一个switch结构,接收两个参数,state 和 action ,并返回一个新的state 。

// Reducer
const counter = (state = { value: 0 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { value: state.value + 1 };
        case 'DECREMENT':
            return { value: state.value - 1 };
        default:
            return state;
    }
}

我们将上述的这个函数抽象为这个公式,更具业务现有的状态,更具触发的action ,返回新的状态的函数称之为 Reducer。

(previousState, action ) => newState

之所以将这样的函数称之为reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue)里的回调函数属于相同的类型。Reducer可以勉强翻译为缩减器或折叠器。

reduce 属于一个高阶函数,它将其中的回调函数reducer递归应用到数组中的所有元素上,并返回一个独立的值,这就是缩减折叠的意义所在。

var sum = [0, 1, 2, 3].reduce(function(acc, val) {
    return acc + val
}, 0)

/* 注意这里当中的回调函数(prev,curr) => prev + curr
*  与我们redux当中的reducer模型 (previousState, action ) => newState 看起来是不是很相似?
*/
[0, 1, 2, 3, 4].reduce( (prev, curr) => prev + curr )

const todos = (state = [], action ) => {
    switch (action.type) {
        case 'ADD_TODO':
            return [
            //ES7对象扩展运算符写法
            ...state,
            {
                id:action:id,
                text:action.text,
                completed:false
            }
            ]
        // 不知道action类型的话返回默认state
        default:
            return state
    }
}

reducer 的主体,是一个 switch 结构的运算,他只是更具传入的 state 和 action,来判断返回一个新的 state。它必须是一个纯函数,不可以修改影响输入值(需要深拷贝),并且没有副作用(异步调用、影响作用域...)。永远不要在 reducer 里做这些操作:

  1. 修改传入参数;
  2. 执行有副作用的操作,如 API 请求和路由跳转;
  3. 调用非纯函数,如 Date.now() 或 Math.random()。

React 应用当中数据发生改变,界面也要跟随相应改变,重新渲染,这这一步之间需要一个比较差异的diff操作,因此改变前后的数据都要被使用到。另外,实现一些诸如旅行的功能也需要依赖不可变数据。

Store

Store 是我们存储状态数据 state 的地方,我们通过 redux 中 createStore 的方法,传入 reducer 函数来创建 store。

  • getState 获取当前状态数据
  • dispath 执行action
  • subscribe 订阅当前状态数据发生改变时触发的事件

原生模拟 createStore

const createStore = (reducer) => {
    let state;
    let listeners = [];
    // 返回当前的state
    const getState = () => state;
    // 根据action调用reducer返回新的state并触发listener
    const dispatch = (action) => {
        state = reducer(state,action);
        listeners.forEach(listeners => listener())
    }
    
    /* 这里的subscribe有两个功能
    * 调用 subscribe(listener)会使用listeners.push(listener)注册一个listener
    * 调用 subscribe 的返回函数则会注销掉listener
    */
    const subscribe = (listener) => {
        listeners.push(listerer);
        return () => {
            listeners = listerers.filter( 1 => 1 !== listener);
        }
    }
    
    // 返回三个主要方法
    return { getState, dispatch, subscribe }
}

Middleware

Middleware可以翻译为中间件,例如网站中某些访问限制的验证功能模块就可以当作一个middleware。Redux也可以应用middleware的概念实现一些例如日志记录调试输出等功能。

例如我们想知道数据前后发生的变化,最直接的办法 。

console.log('prev state',store.getState())
store.dispatch(action)
console.log('next state',store.getState())

手动修改dispath的方法

const store = createStore(counter)
// 将原本的dispatch方法保留并附上控制台语句
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
    console.log('prev state',store.getState())
    let result = next(action)
    console.log('next state',store.getState())
    return result
}

直接修改dispath原生方法是比较脏的处理,并且有时候我们会使用多个middleware,所以我们需要把它与dispatch的逻辑拆分开来。在实际中可以再redux中引入applyMiddleware 方法

const { createStore , applyMiddleware } = Redux;
const loggerMiddleware = store => next => action => {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
}
const store = createStore(counter,
    applyMiddleware(loggerMiddleware))

注意:

  • 不要为了“用框架”而用框架
  • 真正遇到需要这些工具来解决问题时再去实际应用
  • Redux官网
  • 你可能不需要用到Redux