前言
探索Redux 和 Mobx 原理從我做起,從這篇文章看起!
所以
一位程序員的職業生涯大約十年,只有人壽命的十分之一。前端項目只是你生活工作的一部分,而你卻是它的全部,你是他的靈魂。請放下長時間的遊戲、工作時的摸魚。多學習來以最完美的狀態好好陪你項目!
正文
這篇文章將會詳細分析 Redux 和 Mobx 核心 Api, 看一遍學不會就看兩次、三次、手寫一次!
知識點
- Redux 基本使用(沒有)
- createStore
- bindActionCreators
- combineReducers
- applyMiddleware
- 彩蛋
thunk
- Mobx 基本使用(沒有)
- observable
- autorun
- observer
這個 代碼很多哦 , 我這菜鳥看不懂 ,我看了文章,應該能實現 這些api 和 vue的的 響應式差不多.
Redux
createStore
這個是個比較核心的函數之創建倉庫,分為了3個核心函數 dispatch,getState,subscribe
這三個函數的基本使用是這樣的...
// 構建倉庫
const store = createStore(reducer);
const unListen = store.subscribe(() => {
console.log("監聽器1", store.getState());
})
store.dispatch(createAddUserAction({
id: 1,
name: "遇見同學",
age: 21
}));
unListen(); //取消監聽
本質就如思維導圖思路
基本實現且看代碼的詳細解釋
/**
* 得到一個指定長度的隨機字符串
* @param {*} length
*/
function getRandomString(length) {
return Math.random().toString(36).substr(2, length).split("").join(".")
}
/**
* 判斷某個對象是否是一個plain-object
* @param {*} obj
*/
function isPlainObject(obj) {
if (typeof obj !== "object") {
return false;
}
return Object.getPrototypeOf(obj) === Object.prototype;
}
// 上面連個都為輔助工具函數
/**
* 實現createStore的功能
* @param {function} reducer reducer
* @param {any} defaultState 默認的狀態值
*/
export default function createStore(reducer, defaultState) {
let currentReducer = reducer, //當前使用的reducer
currentState = defaultState; //當前倉庫中的狀態
const listeners = []; //記錄所有的監聽器(訂閲者)
function dispatch(action) {
//驗證action
if (!isPlainObject(action)) {
throw new TypeError("action must be a plain object");
}
//驗證action的type屬性是否存在
if (action.type === undefined) {
throw new TypeError("action must has a property of type");
}
currentState = currentReducer(currentState, action)
//運行所有的訂閲者(監聽器)
for (const listener of listeners) {
listener();
}
}
function getState() {
return currentState;
}
/**
* 添加一個監聽器(訂閲器)
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
listeners.push(listener); //將監聽器加入到數組中
let isSubscribed = false;//是否已經移除掉了
return function () {
if (isSubscribed) {
return;
}
//將listener從數組中移除
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
isSubscribed = true;
}
}
//創建倉庫時,需要分發一次初始的action
dispatch({
type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
})
return {
dispatch,
getState,
subscribe
}
}
實現的代碼基本對標 Github Redux
由於官方使用的是Ts 寫的 閲讀起來可能更加困難
所以過濾許多的細節處理和TS,將本質的思維呈現出來!
從入口帶你理解下一個核心函數bindActionCreators
bindActionCreators
- 作用
用於返回可直接調用的dispatch的函數方法 -
參數
- object | function
- (store.dispatch)
const actionCreators = { addUser: createAddUserAction, deleteUser: createDeleteUserAction } // 這可直接傳入一個函數 const actions = bindActionCreators(actionCreators, store.dispatch) actions.addUser({ id: 1, name: "遇見同學", age: 21 })
使用還是非常簡單的, 本質也是一個函數, 非常的精妙,一起看看吧!
思路是這樣,核心函數是另外一個getAutoDispatchActionCreator
/**
* @param {object | function } actionCreators
* @param {*} dispatch
* @returns
*/
export default function (actionCreators,dispatch) {
// 為函數時 直接調用自動分發的action創建函數
if(typeof actionCreators === 'function'){
_getAutoDispatchActionCreator(actionCreators,dispatch)
// 為對象時 遍歷對象 轉化為已創建自動分發的action創建函數的對象
}else if(typeof actionCreators === 'object'){
const result = {}
Object.keys(actionCreators).forEach((key)=>{
result[key] = _getAutoDispatchActionCreator(actionCreators[key],dispatch)
})
return result
}else{
throw new TypeError("actionCreators must be an object or function which means action creator")
}
}
/**
* 自動分發的action創建函數
* @param {function} actionCreator
* @param {any} dispatch
* @returns
*/
function _getAutoDispatchActionCreator(actionCreator, dispatch){
return function(...args){
return dispatch(actionCreator(...args))
}
}
是不是豁然開朗了,乘熱打鐵進入下一個函數吧combineReducers
combinReducers
基本思路是這樣的
**
* 合併reducers函數
* @param {object} reducers
* @returns
*/
export default function (reducers) {
validateReducers(reducers);
/**
* 返回的是一個reducer函數
*/
return function (state = {}, action) {
const newState = {}; //要返回的新的狀態
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key];
newState[key] = reducer(state[key], action);
}
}
return newState; //返回狀態
}
}
function validateReducers(reducers) {
if (typeof reducers !== "object") {
throw new TypeError("reducers must be an object");
}
if (!isPlainObject(reducers)) {
throw new TypeError("reducers must be a plain object");
}
//驗證reducer的返回結果是不是undefined
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key];//拿到reducer
//傳遞一個特殊的type值
let state = reducer(undefined, {
type:`@@redux/INIT${getRandomString(6)}`
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined");
}
state = reducer(undefined, {
type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined");
}
}
}
}
來對比一下這個Redux 的 combinReducers官方代碼
總得來説這幾個都屬於輔助函數,而createStore 和 傳入的reducer 才是調動整個 狀態數據的核心, 不過Redux 提供了 一個核心機制 中間件 也是Redux 的靈魂之一。
接下來看下 applyMiddleware 是如何實現的
applyMiddleware
像 Koa 、Redux 等優秀庫都提供了中間思想, 中間件一出, 必有 組合函數
我們看下 Redux 的組合函數
function compose(...funcs: Function[]) {
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
省略一些邊界判斷 , 實現就一個 reducer
就這一行
funcs.reduce((a, b) => (...args) => a(b(...args)))
函數由內向外,返回作為 reducer 的新的累加值 依次執行 b、a、...
再看看applyMiddleware函數
import compose from "./compose"
/**
* 註冊中間件
* @param {...any} middlewares 所有的中間件 本質是一個函數
*/
export default function (...middlewares) {
return function (createStore) { //給我創建倉庫的函數
//下面的函數用於創建倉庫
return function (reducer, defaultState) {
//創建倉庫
const store = createStore(reducer, defaultState);
let dispatch = () => { throw new Error("目前還不能使用dispatch") };
const simpleStore = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
//給dispatch賦值
//根據中間件數組,得到一個dispatch創建函數的數組
const dispatchProducers = middlewares.map(mid => mid(simpleStore));
dispatch = compose(...dispatchProducers)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
以我理解幹了這幾件事
調用返回了 一個具備以上能力的函數 拋出給 createStore 的第二個參數, 我們看看 createStore 有何改動
export default function createStore(reducer, defaultState, enhanced) {
//enhanced表示applymiddleware返回的函數
if (typeof defaultState === "function") {
//第二個參數是應用中間件的函數返回值
enhanced = defaultState;
defaultState = undefined;
}
if (typeof enhanced === "function") {
//進入applyMiddleWare的處理邏輯
return enhanced(createStore)(reducer, defaultState);
}
// ....同上面代碼
return {
dispatch,
getState,
subscribe
}
}
-
執行
applyMiddleware返回一個需要倉庫函數 的函數applyMiddleware( thunk, // 返回中間件函數 logger ) - 接着 傳入
createStore倉庫函數函數本身 很巧妙 - 得到一個新的倉庫構造函數 傳入
(reducer, defaultState) - 用第一次傳入的
createStore和(reducer, defaultState)執行構造出沒使用中間件的倉庫 - 掛載
store和dispatch新對象傳給 每一箇中間件函數,返回新的dispatch數組 - 合併所有新的
dispatch,執行第一次 - 返回
store和 新的dispatch
這便是 中間件的基本 運行流程 有些繞 好好學習,天天向上
送你一個彩蛋 thunk 實現
代碼是個只有幾行的函數
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
// 創建 thunk 中間件
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
看圖 我帶你 穿起來 代碼 一起解讀
紅色部分是 中間件函數 將 dispatch, getState 丟給 thunk , 接着返回函數
圖中紅絲部分代表着 thunk 處理部分 , 判斷是否 為 函數 不是就對給下步去執行
我們再看下 next 是什麼。
清晰的看一下 next 就是 dispatch 函數
後面兩個 action 就是 一個普通 平面對象 類型 和 異步函數 thunk 處理類型
Mobx
Mobx 也是一款狀態管理工具 ,但是思維模式不同
類似 Vue 響應式原理 , 配合 觀察者模式 收集依賴 實現
Api 有點多 我沒看懂 , 下面僅供參考
observable
這個方法顯而易見 構建出 一個 可觀察的對象
import reaction from './reaction'
export default function observable(target,key,descritor){
// 當使用裝飾器 修飾屬性時
if( typeof key === 'string'){
let Reaction = new reaction
let v = descritor.initailizer()
v = createObservable(v)
return {
enumerable:true,
configurable:true,
get(){
Reaction.collect()
return v
},
set(value){
v = value
Reaction.run()
},
}
}
return createObservable(target)
}
function createObservable(val){
let handle = () =>{
let Reaction = new reaction
return {
get(target,key){
Reaction.collect()
return Reflect.get(target,key)
},
set(target,key,value){
let v = Reflect.set(target,key,value)
Reaction.run()
return v
},
}
}
return deepProxy(val,handle)
}
function deepProxy(val,handle){
if(typeof val !== 'object') return val
// 從後往前依此實現代理
for(let key in val){
val[key] = deepProxy(val[key],handle)
}
return createObservable(val, handle())
}
可以很清楚的看到 這個函數實現了以下
- 判斷是否處於裝飾器修飾使用狀態
- Proxy觀察一個深度對象
- 在get時觸發 依賴收集
reaction
let nowFn = null
let counter = 0
class Reaction {
constructor(){
this.id = ++counter
this.store = {}
}
run(){
if(this.store[this.id]){
this.store[this.id].forEach(w=>w())
}
}
collect(){
if(nowFn){
this.store[this.id] = this.store[this.id] || []
this.store[this.id].push(nowFn)
}
}
static start(handle){
nowFn = handle
}
static end (){
nowFn = null
}
}
autorun
import reaction from './reaction'
export default function autorun(handle){
reaction.start()
handle()
reaction.end()
}
再 看看 autorun 和 reaction
基本就是一個簡單的依賴收集 和 觸發
相信 各位 知道 觀察者模式 和 瞭解 Vue 依賴收集的對這樣的寫法並不陌生
而 最後的一個 observer
observer
/**
* 裝飾器 mobx-react 修飾類組件
* @param {*} target
*/
export default function observer(target){
let cwm = target.prototype.componentWillMount;
target.prototype.componentWillMount = function(){
cwm && cwm.call(this);
autorun(()=>{
this.render();
this.forceUpdate();
})
}
}
這個僅只是 修飾 react 類組件的 狀態
對比
Redux
- 單一數據源
- 狀態數據只讀
- 使用純函數修改狀態
Mobx
- 多數據源
- 被觀察對象
- 觀察依賴
- 觸發動作
兩者的區別:
| 比較 | redux | mobx |
|---|---|---|
| 核心模塊 | Action,Reducer,Store,沒有調度器的概念 | observerState、Derivations、Actions... |
| Store | 只有一個 Store, Store 和更改邏輯是分開的,帶有分層 reducer的單一 Stor | 多個 store |
| 狀態 | 狀態是不可改變的(建議不可變值) | 通常將狀態包裝成可觀察對象,觀察者依賴收集模式 |
| 編程思想 | 遵循函數式編程思想 | 函數響應式編程編程 |
| 對象 | JavaScript對象 | 可觀察對象 |
總結
- Redux 的 源碼實現 瞭解中間件的核心思想
- 實現Mobx 的 核心 (其他的慢慢摸索是吧)
- 對比兩中狀態管理的 差異 優劣
本文首發 GitHub |
源碼 Analysis