state

一个简单的对象,用于保存数据。

Action

Action 是把数据从应用传到 store 的有效载荷。

说的简单点,就是一个具有 type 属性的对象

{
    type: 'ADD_TODO',
    text: 'hello world'
}

对象的 type 属性规定了 Action 的行为: ADD_TODO,其他属性作为该 Action 需要处理的数据。

可以手动编写,也可以由函数生成。

function createTodo(text){
    return {
        type: 'ADD_TODO',
        text: text
    }
}

函数生成可以简化 Action 的编写,同时将 type 固化,不用特意去找 Action 的类型。

Reducer

Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的过程,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state

Reducer 是一个函数,接收 stateaction 作为参数.
函数内部将 action 应用于 state 生成一个新的 state

函数行为可以描述为:

(previousState, action) => newState

注: 一个 Reducer 需要有一个明确的返回,因此它应该是一个同步纯函数。

由于在 redux 初始化时,状态树为空,因此 state 需要有一个默认值。使用 ES6 默认值的写法处理。

function todoApp(state = [], action){
    switch (action.type) {
        case 'ADD_TODO':
            return [
                ...state,
                action.text
            ]
        default:
            return state
    }
}

注:

  1. 不要修改原本的 state 这会照成副作用,也不符合纯函数的定义。
  2. 在遇到未知情况时,如 actiontype 出错,那么一定要返回旧的 state

一个 Reducer 可以对应整个状态树的一部分(当然也可以是一整课树),多个不同的 Reducer 可以通过组合形成一个针对于完整状态树的处理函数。

function todos(state = [], action){
    switch (action.type) {
        case 'ADD_TODO':
            return [
                ...state,
                action.text
            ]
        default:
            return state
    }
}

function other(state = {}, action){
    switch (action.type) {
        // ...
        default:
            return state
    }
}

function appReducers(state = {}, action){
    return {
        todos: todos(state.todos, action),
        other: other(state.other, action)
    }
}

上诉代码产生了 3Reducer ,两个部分的 Reducertodos & other),一个由两个部分 Reducer 组合形成的完整的 ReducerApp)。

Redux 提供了 combineReducers 来处理 Reducer 的合并。作用就是上面的合并代码。

import { combineReducers } from 'redux'

// 设置的 key 与函数同名
const appReducers = combineReducers({
    todes,
    other
})

// 自定义设置 key
const appReducers2 = combineReducers({
    a: todes,
    b: other
})

当然我们也可以自己实现一下这个 combineReducers

function combineReducers(reducerObj){
    return function(state = {}, action){

        function createState(reducerObj, state){
            let newState = {...state};
            Object.entries(reducerObj).forEach(([name, value]) => {
                // 如果是对象继续遍历,将返回值赋给 state
                if(typeof value === 'object'){
                    newState[name] = createState(value, newState[name]);
                // 如果是函数就执行将返回值赋给 state
                }else if(typeof value === 'function'){
                    newState[name] = value(newState[name], action);
                // 直接赋值
                }else{
                    newState[name] = value;
                }
            })
            return newState;
        }

        return createState(reducerObj, state)
    }
}

结合 Action 我们来模拟下生成 state 和修改 state 的过程。

// 生成 state
function createState(reducer){
    // 调用 reducer 生成 state
    let init = reducer(undefined, {})
    return init
}

const state = createState(appReducers)
console.log(state)
// { todos: [], other: {} }

function changeState(reducer, state, action){
    // 调用 reducer 重新生成 state
    let change = reducer(state, action)
    return change
}

const state_2 = changeState(appReducers, state, {
    type: 'ADD_TODO',
    text: 'hello world'
})
console.log(state_2)
// { todos: [ 'hello world' ], other: {} }

通过 action/reducer 和两个执行函数(createState/changeState)做到以下几点

  1. 生成一个状态树 state
  2. action 对象表示的操作应用到 state
  3. 生成一个新的状态树 state_2

这样就完成了不操作状态树的情况下通过 action 更改了状态树。

至于为什么要通过这么复杂的操作来实现对 state 的操作,有以下几点

  1. 增加操作 state 的难度,使得 state 在正常操作中不会被轻易修改,有利于应用的稳定。
  2. 可以在 changeState 函数内截获对 state 操作,实现发布/订阅模式,这样就可以通知订阅的函数执行,实现对绑定数据的更新。

createState

简单的实现一下

function createStore(reducer){
    let state = reducer({});
    let callbacks = [];
    return {
        getState(){
            return state;
        },
        dispatch(action){
            state = reducer(state, action);
            callbacks.forEach(callback => callback(state));
        },
        subscribe(callback){
            callbacks.push(callback);
        }
    }
}