redux 工作原理
Redux
和 React
之间并没有什么关系,脱离了 React
,Redux
也可以与其它的 js 库(甚至是原生 js)搭配使用,Redux
只是一个状态管理库,但它与 React
搭配时却很好用,使开发 React 应用更加简介。而使用 Redux 库时,需要先做“配置”,因为这些代码的书写是必不可少的。下面的图是 redux 的工作流:
首先,react 组件从 store 中获取原始的数据,然后渲染。当 react 中的数据发生改变时,react 就需要使用 action,让 action 携带新的数据值派发给 store,store 把 action 发给 reducer 函数,reducer 函数处理新的数据然后返回给 store,最后 react 组件拿到更新后的数据渲染页面,达到页面更新的目的。
需要注意的是,在使用 Redux 时,最好不要监视最外层的容器,这样会把整个页面重新渲染,这是很浪费的,你应该绑定到像 App 这样的容器组件中。然后在容器组件中通过 props 向展示组件传递数据。
实现 Redux
首先捋一下思路,Redux 库中都有哪些函数?这些函数的参数都有哪些?参数类型是什么?执行函数后会返回什么?下面就一一介绍一下 redux 中的函数,当然在实际的 redux 源码中要复杂一些,不过在这篇文章中核心概念是一样的。
1. createStore
该函数会创建一个 store,专门用于存储数据。他返回四个函数:
- dispatch:用于派发 action;
- getState:用于获得 store 中的数据;
- subscribe:订阅函数,当 state 数据改变后,就会触发监听函数;
- replaceReducer:reducer 增强器;
createStore
可以接收三个参数:
- reducer - 我们自己写的 reducer 函数;
- preloadedState - 可选参数,表示默认的 state 值,没有这个参数时,enhancer 可以是 createStore 的第二个参数;
- enhancer - 可选参数,增强器,比如 applyMiddleware 就是一个 enhancer;
该函数的模样:
function createStore(reducer,preloadedState,enhancer){
let state;
// 用于存放被 subscribe 订阅的函数(监听函数)
let listeners = [];
// getState 是一个很简单的函数
const getState = () => state;
return {
dispatch,
getState,
subscribe,
replaceReducer
}
}
那就来一一实现各个“子函数”。
dispatch
该函数是派发 action 的,因此会接受一个 action 对象作为参数:
function dispatch(action) {
// 通过 reducer 返回新的 state
// 这个 reducer 就是 createStore 函数的第一个参数
state = reducer(state, action);
// 每一次状态更新后,都需要调用 listeners 数组中的每一个监听函数
listeners.forEach(listener => listener());
return action; // 返回 action
}
subscribe
这个函数主要是往 listeners
数组中放入监听函数,参数就是一个监听函数。它还会返回一个 unsubscribe
函数,用于取消当前的订阅。
function subscribe(listener){
listeners.push(listener);
// 函数取消订阅函数
return () => {
listeners = listeners.filter(fn => fn !== listener);
}
}
replaceReducer
顾名思义,这个函数可以替换 reducer,它传入一个 reducer 用以替代当前执行的 reducer 函数。
function replaceReducer(reducer){
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer;
}
需要注意的是,在源码中完成负值后还会再派发一个类型为 @@redux/INIT
的 action。
2. combineReducers
该函数接收一个对象参数,对象的值是小的 reducer 函数。combineReducers 函数会返回总的 reducer 函数。combineReducers 函数样子:
function combineReducers(reducers){
// 返回总的 reducer 函数,
// 与小的 reducer 函数功能一样,返回更新后的 state
return (state = {},action) => {
// ...
}
}
调用 combineReducers 函数:
import { combineReducers, createStore } from "redux";
import reducer1 from "./reducer1";
import reducer2 from "./reducer2";
// rootReducer 是一个新的 reducer 函数
const rootReducer = combineReducers({
reducer1,
reducer2
});
var store = createStore(rootReducer);
具体实现:
function combineReducers(reducers){
return (state = {},action) => {
// 返回的是一个对象,reducer 就是返回的对象
return Object.keys(reducers).reduce(
(accum,currentKey) => {
accum[currentKey] = reducers[currentKey](state[currentKey],action);
return accum;
},{} // accum 初始值是空对象
);
}
}
3. 写一个中间件
redux-thunk
redux-thunk 实现起来就更简单了,先回顾一下 redux-thunk 的使用方式,要想用 dispatch 派发异步请求来的数据需要在定义一个函数,该函数返回一个函数,参数是 dispatch:
// actions.js
const ajaxDataAction = (data) => ({ type: "ajax_data", payload: data });
export function ajaxAction(){
// 这个 action 会返回一个函数
return (dispatch) => {
fetch("/info").then(json => json())
.then(data => {
// 得到数据后派发 action
dispatch(ajaxDataAction(data));
});
}
}
因此,redux-thunk 函数内部需要先拦截 dispatch 函数,判断 action 参数的数据类型是不是函数,如果是函数就执行函数:
function thunk({ getState, dispatch }){
return next => action => {
if(typeof action === "function"){
return action(dispatch, getState);
}
// 传递给下一个中间件
return next(action);
}
}