Redux 基本用法
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。
有两个方面它没有涉及
- 代码结构
- 组件之间的通讯
对大型复杂应用来说,这两方面也是最重要的
Redux 使用场景
使用场景 多交互、多数据源
Redux 是一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化
例:
- 某个组件状态需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件改变全局状态
- 一个组件需要改变另一个组件状态
设计思想
- Web 应用是一个状态机,试图与状态一一对应的
- 所有状态,保存在一个对象里面
基本概念和 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 函数最重要的的特性是一个纯函数
只要是同样的输入,必定得到同样的输出
纯函数是函数式编程的概念,必须遵守以下约束
- 不得改写参数
- 不能调用系统 I/O 的 API
- 不能调用 Date.now() 或者 Math.random 等不纯的方法
由于 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 提供了三个方法
- store.getState()
- store.dispatch()
- store.subscribe()
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 三个属性
- ADD_CHAT:chatLog 属性
- CHANGE_STATUS:statusMessage 属性
- CHANGE_USERNAME:userName 属性
这三个属性之间没有联系,我们可以拆分 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);
工作流程
-
首先
用户发出 Actionstore.dispatch(action);
-
然后
Store 自动调用 Reducer
传入两个参数 当前 State 和 收到的 Action
返回新的 Statelet nextState = todoApp(previousState, action);
Store 一旦有变化,Store 就会调用监听函数
// 设置监听函数 store.subscribe(listener);
listener 可以通过 store.getState() 得到当前状态
如果使用的是 React,则触发重新渲染 Viewfunction listerner() { let newState = store.getState(); component.setState(newState); }
参见