基本功練習: Hacker News! — Part 5: Redux
基本功練習: 使用純 JS + Redux 把 Hacker News 做出來。
前情提要
我們個別頁面跟留言都處理好了,現在我們要來動手寫我的最愛了!
Redux
上一篇提到,我們只剩下最後一個功能,我的最愛頁面。
而這一塊,我想用 Redux。
這樣就可以讓用戶在新聞列表直接將喜歡的新聞加進我的最愛頁面,也可以在將新聞從我的最愛移除。
最重要的是,在我們切換頁面時,資料不會流失!
我們來快速回顧下 Redux 是什麼:
Redux 是個全域的狀態管理物件,用來管理 state,主要目的是方便我們把 state 資料傳來傳去。
我們用 Action 下指令,接著 Reducer 會依照發射的指令處理我們在 state 的資料,再把更新好的資料傳到 store 裡,最後 store 會依照拿到的資料將畫面更新,這樣子輪迴。
喔對了,神奇的小常識。
Redux 跟 Flux 其實基本上指的是同樣的 concept,不過 Flux 可以同時擁有很多 store,Redux 只能有一個。
回顧完了 Redux,我們接著寫 Hacker News。
我的最愛: Store
在開寫前,我們先想想這頁要幹嘛。
我想要搜集所有用戶按下喜歡的新聞,並把它們存放到這頁,而其他的新聞列表上,如果用戶已經按下喜歡了,那列表上則會顯示「從我的最愛中移除」。
要做這個功能的話,我們需要一個地方搜集所有的喜歡資料,而這個地方,我就叫它 store.js。
但是在開始創建 store 前,我需要先有 reducer 來幫我保管 state,所以我們先寫 reducer。
Reducer = 如上,在 Redux 裡面,用來保管 state 的東東,可以接受指令去更新 state。
store.js:
1
2
3
4
5
6
7
8
9
10
const initialState = {
favorites: []
}
function favoritesReducer(state = initialState, action) {
switch (action.type) {
default:
return state;
}
}
我們空的 initialState,還有空空的 favoriteReducer。
在 favoriteReducer 裡面,我們需要兩個功能:加入我的最愛 + 移除我的最愛。
加入我的最愛:
1
2
3
4
5
case "ADD_FAVORITE": {
const addedFavorite = action.payload.favorite;
const favorites = [...state.favorites, addedFavorite];
return { favorites };
}
創建一個 array,用 spread operator 在現有的 state 裡面將 addedFavorite 裝進去。
移除我的最愛:
1
2
3
4
5
case "REMOVE_FAVORITE": {
const removedFavorite = action.payload.favorite;
const favorites = state.favorites.filter(favorite => favorite.id !== removedFavorite.id);
return { favorites };
}
找到要移除我的最愛,在現有的 state 裡面,用 .filter 排除掉這個 id。
至於什麼 action 都沒設定時,如果 favoritesReducer 被呼叫,那就會直接回傳 state 了。
1
2
default:
return state;
把他們加起來,就是這樣:
store.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const initialState = {
favorites: []
}
function favoritesReducer(state = initialState, action) {
switch (action.type) {
case "ADD_FAVORITE": {
const addedFavorite = action.payload.favorite;
const favorites = [...state.favorites, addedFavorite];
return { favorites };
}
case "REMOVE_FAVORITE": {
const removedFavorite = action.payload.favorite;
const favorites = state.favorites.filter(favorite => favorite.id !== removedFavorite.id);
return { favorites };
}
default:
return state;
}
}
現在 Reducer 有了,我們把 store 建立出來吧:
1
2
3
function createStore(reducer) {
let currentState = reducer(undefined, {});
}
createStore 會接受一個 reducer,而 currentState 最開始創建事是空的,也沒有任何需要被下的指令,所以我們寫這個: reducer(undefined, {})
這樣 default state 就會被回傳回來了。
可是,我們要怎麼拿到這個 currentState 咧?
我們幫 createStore 創建兩個 function: getState 跟 dispatch:
1
2
3
4
5
6
7
8
9
10
function createStore(reducer) {
let currentState = reducer(undefined, {});
return {
getState: () => currentState,
dispatch: action => {
currentState = reducer(currentState, action);
}
}
}
getState 可以讓我們拿到現有的 state,而 dispatch 會依照收到的 action 幫我們自動更新 currentState。
剩下就是把 store 建起來,讓整個 app 能用了。
1
2
3
const store = createStore(favoritesReducer);
export default store;
這個寫好後,我們就可以將 store 導入進需要的畫面,歡樂的使用 store.getState() 跟 store.dispatch(action) 了!
佈局這麼久,終於可以開始寫我的最愛了,耶~~