[Implement] 34 行實現 Redux
為什麼要存在 Redux? Flux pattern 的實現
在實現 Redux 之前,我們先來了解一下為什麼要存在 Redux? 瞭解我們要解決的問題後,我們才知道 Redux 的重點功能有哪些
在 Flux pattern 的文章中,有提到我們需要一個全局的單一實例 store 來管理所有的狀態,而 Redux 就是為了解決這個問題而存在的
而 Redux 的存在就是為了讓 Flux pattern 中的 Dispatch
跟 Store
部分得以實現
Redux 使用前須知:須遵守一定的 "規範" 才能使用
其實使用 Redux,需要遵循很多慣例 or 規範,再搭配 Redux 簡短的程式碼,才能實現 Flux pattern 的概念,而我們要遵守的規範有一下三點:
- State 都存在在一個 single tree
- Action 表示狀態改變
- Reducer 翻譯 Action 然後改變狀態
1. State 都存在在一個 single tree
State lives in a single tree
整個 app 的狀態如下:
const initialState = { count: 0 }
2. Action 表示狀態改變
根據 Redux 的慣例,不要直接更改狀態 (mutate the state)
state.count = 1
反之,建立一些既有的"行為(Action)",讓我們可以透過這些行為來更改狀態
const actions = {
increment: { type: 'INCREMENT' },
decrement: { type: 'DECREMENT' },
};
3. Reducer 翻譯 Action 然後改變狀態
最後一階段會呼叫一個 reducer reducer: 一個 pure function 會傳一個根據前一個 state 產生的新 copy
- 如果
increment
action fire,增加state.count
- 如果
decrement
action fire,增加state.count
const countReducer = (state = initialState, action) => {
switch (action.type) {
case actions.increment.type:
return { count: state.count + 1 };
case actions.decrement.type:
return { count: state.count - 1 };
default:
return state;
}
}
有了開發者遵循這些規範,再搭配下列簡短的 Redux,才能使 Flux pattern 完整呈現
實現 Redux
功能需求
Redux 最關鍵的 api,就是 createStore
,並需要滿足以下功能:
- 接收一個 reducer function
- 產出的 store 需
- 提供
getState
功能,讓外部可以取得 store 的 state - 提供
subscribe
功能,讓外部可以監聽到 store 的 state 的變化 - 提供
unsubscribe
功能,讓監聽者可以取消監聽 - 提供
dispatch
功能,讓 user 可以透過action
更改store
的狀態
- 提供
需要能執行的實際程式碼如下:
import { createStore } from 'redux';
const store = createStore(counReducer);
/* 1. 監聽 store 的 state 變化 */
/* 2. 可以取得 store 的 state */
const unsubscribe = store.subscribe(() => {
console.log('subscriber 1 :', store.getState());
});
/* 3. 可以透過 action 更改 store 的 state */
store.dispatch(action.increment); // subscriber 1 : { count: 1 }
store.dispatch(actions.increment); // subscriber 1 : { count: 2 }
store.dispatch(actions.decrement); // subscriber 1 : { count: 1 }
/* 4. 取消監聽 */
unsubscribe();
store.dispatch(actions.decrement); // no logs
以下我們會來探討 createStore
的功能需求 & 如何實現
初始樣板 (initial boilerplate)
const createStore = (myReducer) => {
let listeners = [];
let currentState = yourReducer(undefined, {});
}
Redux 利用 function programming 的 closure 概念,來像 Class 一樣保存狀態
currentState
: 當我們使用undefined
帶入時,預設會帶入上面的initialState
如果不知道什麼是 Closure,可以參考 You Don't Know Js yet 作者的這個影片: https://frontendmasters.com/courses/deep-javascript-v3/what-is-closure/
store.getState()
回傳 store 最新的狀態
const createStore = (myReducer) => {
let listeners = [];
let currentState = myReducer(undefined, {});
const getState = () => {
return currentState;
}
return {
getState,
}
}
store.dispatch(action)
- 透過
reducer
將action
和currentState
結合產生一個新的 state- 並通知所有的 listeners
const createStore = (myReducer) => {
let listeners = [];
let currentState = myReducer(undefined, {});
const getState = () => {
return currentState;
};
const dispatch = () => {
currentState = myReducer(currentState, action);
listeners.forEach((listener) => {
listener();
});
};
}
store.subscribe(listener)
- 讓你可以監聽 store 是不是有 state change (或是 action 被觸發)
- 同時也回傳一個
unsubscribe
method,讓我們當 store 沒興趣時,可以取消監聽
const createStore = (myReducer) => {
let listeners = [];
let currentState = myReducer(undefined, {});
const getState = () => {
return currentState;
};
const dispatch = (action) => {
currentState = myReducer(currentState, action);
listeners.forEach((listener) => {
listener();
});
};
const subscribe = (newListener) => {
listeners.push(newListener);
const unsubscribe = () => {
listeners = listeners.filter((l) => (
l !== newListener,
));
};
return unsubscribe;
};
return {
getState,
dispatch,
subscribe,
};
}
結論
- Redux 的程式碼很間單,利用 Closure + Flux pattern 的概念就可以實現
- 大部分都是遵循慣例(Convention),Redux 程式碼其實只佔一小部分
參考資源
- How to Implement Redux in 24 Lines of JavaScript (freecodecamp.org)
- React-Redux 100行代码简易版探究原理。(面试热点,React Hook + TypeScript实现)-腾讯云开发者社区-腾讯云 (tencent.com)