Skip to the content.

Redux 基本用法

React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。
有两个方面它没有涉及

对大型复杂应用来说,这两方面也是最重要的


Redux 使用场景


使用场景 多交互、多数据源

Redux 是一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化

例:



设计思想


  1. Web 应用是一个状态机,试图与状态一一对应的
  2. 所有状态,保存在一个对象里面



基本概念和 API


Store

Store 是保存数据的地方,也是一个容器,整个应用只能有一个 Store

Redux 提供 createStore 函数用来生成 Store
createStore 函数接收另一个函数作为参数,返回新生成的 Store 对象

import { createStore } from "redux";
const store = createStore(fn);



State

State 对象包含所有数据。
如果想要得到某个时点的数据,就要对 Store 生成快照,这种时点数据集合叫做 State

当前时刻 State 可以通过 store.getState() 得到

import { createStore } from "redux";
const store = createStore(fn);

const state = store.getState();

Redux 规定,一个 State 对应一个 View,只要 View 相同 State 就相同



Action

State 变化会导致 View 的变化,但用户接触不到 State,用户只能接触到 View
所以 State 的变化必须是 View 导致的
Action 就是 View 发出的通知,表示 State 应该要发生变化

Action 是一个对象,其中 type 是必须的,表示 Action 的名称,其他属性可自由定义-社区参考规范

const action = {
  type: "ADD_TODO",
  payload: "Learn Redux",
};

上述代码中,Active 的名称是 ADD_TODO ,它携带的信息是字符串 Learn Redux
可以这样理解:Active 描述了当前发生的事情。也是改变 State 的唯一方式。还可以把数据传输到 Store



Action Creator

View 要 发送多少种消息,就会有多少种 Action
定义一个函数来生成 Action 这个函数叫做 Action Creator

const ADD_TODO = "添加 TODO";

function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}

const action = addTodo("Learn Redux");



store.dispatch()

store.dispatch() 是 View 发出 Action 的唯一方法

import { createStore } from "redux";
const store = createStore(fn);

store.dispatch({
  type: "ADD_TODO",
  payload: "Learn Redux",
});

store.dispatch()接收一个 Action 对象作为参数
结合 Action Creator

store.dispatch(addTodo("Learn Redux"));



Reducer

Store 收到 Action 后,必须给出一个新的 State,View 才会发生变化
这种 State 的计算过程叫做 Reducer

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case "ADD":
      return state + action.payload;
    default:
      return state;
  }
};

const state = reducer(1, {
  type: "ADD",
  payload: 2,
});

上述代码中 reducer 函数收到名为 ADD 的 Action 后,返回一个新的计算结果 State
实际应用中,Reducer 函数并不会手动调用,store.dispatch方法会触发 Reducer 的自动执行
为此,Store 需要知道 Reducer 函数,做法是生成 Store 的时候,将 Reducer 传入createStore方法

import { createStore } from "redux";
const store = createStore(reducer);

上述代码中,createStore 接收 Reducer 作为参数,生成一个新的 Store
以后每当 store.dispatch 发送一个新 Action,就会自动调用 Reducer,得到新 State

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的 reduce 方法的参数

const actions = [
  { type: "ADD", payload: 0 },
  { type: "ADD", payload: 1 },
  { type: "ADD", payload: 2 },
];

const total = actions.reduce(reducer, 0); // 3

上面代码中,数组 actions 表示依次有三个 Action,分别是加 0、加 1 和加 2。
数组的 reduce 方法接受 Reducer 函数作为参数,就可以直接得到最终的状态 3。



纯函数

Reducer 函数最重要的的特性是一个纯函数
只要是同样的输入,必定得到同样的输出

纯函数是函数式编程的概念,必须遵守以下约束

由于 Reducer 是纯函数,就可以保证同样的 State,必须得到同样的 View
Reducer 函数里面不能改变 State,必须返回一个全新的对象

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
}

最好把 State 对象设置成只读,要得到新 State 的唯一办法是生成一个新对象
这样好处是,在任何时候与某个 View 对应的 State 总是一个不变的对象



store.subscribe()

Store 允许使用 store.subscribe() 方法设置监函数
一旦 State 发生变化,就自动执行这个函数

import { createStore } from "redux";
const store = createStore(reducer);

store.subscribe(listener);

只要把 View 的更新函数放入 listen 就会实现 View 的自动渲染
对于 React 项目,组件的 render 方法或 setState 方法放入 listen

store.subscribe 返回一个函数,调用这个函数就可以解除监听

let unsubscribe = store.subscribe(() => console.log(store.getState()));

unsubscribe();



Store 的实现


Store 提供了三个方法

import { createStore } from "redux";
let { subscribe, dispatch, getState } = createStore(reducer);

createStore 方法有第二个参数,表示 State 的最初是状态

let store = createStore(todoApp, window.STATE_FROM_SERVER);

window.STATE_FROM_SERVER 是整个应用的初始值,会覆盖 Reducer 函数的默认初始值

createStore的简单实现

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach((listener) => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};



Reducer 的拆分


Reducer 函数负责生成 Store,整个应用只有一个 State 对象,包含了所有数据。会导致 Reducer 函数也十分庞大

const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload),
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload,
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload,
      });
    default:
      return state;
  }
};

上面代码中三个 Action 分别改变 State 三个属性

这三个属性之间没有联系,我们可以拆分 Reducer 函数
不同函数负责处理不同属性,最终合并成一个大的 Reducer

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action),
  };
};

Reducer 函数被拆成三个小函数,每个负责对应的属性,这让 Reducer 更易读易写

Redux 提供了一个 combineReducers 方法,用于 Reducer 的拆分
只要定义各个子 Reducer 函数,就可以将他们合并成一个大 Reducer

import { combineReducers } from "redux";

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName,
});

这种写法有一个前提,State 的属性名必须与子 Reducer 同名
不同名时需要采用以下这种写法

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c,
});

// 等同于
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action),
  };
}

总之 combineReducers() 做的就是产生一个整体的 Reducer 函数
该函数根据 State 的 Key 去执行对应子 Reducer 函数,并将返回结果合并成一个大的 State 对象

combineReduce 的简单实现

const combineReducers = (reducers) => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce((nextState, key) => {
      nextState[key] = reducers[key](state[key], action);
      return nextState;
    }, {});
  };
};

在项目中一般把所有子 Reducer 放入一个文件里,然后统一引入

import { combineReducers } from "redux";
import * as reducers from "./reducers";

const reducer = combineReducers(reducers);



工作流程




参见