Skip to main content

[Implement] 34 行實現 Redux

為什麼要存在 Redux? Flux pattern 的實現

在實現 Redux 之前,我們先來了解一下為什麼要存在 Redux? 瞭解我們要解決的問題後,我們才知道 Redux 的重點功能有哪些

Flux pattern 的文章中,有提到我們需要一個全局的單一實例 store 來管理所有的狀態,而 Redux 就是為了解決這個問題而存在的

而 Redux 的存在就是為了讓 Flux pattern 中的 DispatchStore 部分得以實現


Redux 使用前須知:須遵守一定的 "規範" 才能使用

其實使用 Redux,需要遵循很多慣例 or 規範,再搭配 Redux 簡短的程式碼,才能實現 Flux pattern 的概念,而我們要遵守的規範有一下三點:

  1. State 都存在在一個 single tree
  2. Action 表示狀態改變
  3. 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 簡介

如果不知道什麼是 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)


  1. 透過 reduceractioncurrentState 結合產生一個新的 state
  2. 並通知所有的 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)


  1. 讓你可以監聽 store 是不是有 state change (或是 action 被觸發)
  2. 同時也回傳一個 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,
};
}

結論

  1. Redux 的程式碼很間單,利用 Closure + Flux pattern 的概念就可以實現
  2. 大部分都是遵循慣例(Convention),Redux 程式碼其實只佔一小部分


參考資源