大多數使用 redux 都是以狀態改變來驅動畫面渲染,但有時候我要的不是狀態的變化,而是想接收事件並執行後續處理,此時就可以透過 addListener 來達成。
前言
大多數使用 redux 都是以狀態改變來驅動畫面渲染,例如修改 redux 狀態讓其他頁面有使用到該狀態的值做同步的異動,但有時候我要的並不是狀態的變化,而是想接收事件並執行後續處理,例如當某個特定組件對相依資料調整時,要通知特定頁面組件去執行一個 "動作" 如呼叫 API 刷新頁面資訊,注意這邊是 "動作" 喔!此時就可以透過 addListener 來達成,有種 event bus 的味道。
切記千萬不要濫用,因為會很容易造成程式邏輯混亂。
使用情境
假設頁面上包含許多財務資訊,在同一頁面中僅能透過開啟 modal 的方式進行資料異動,而資料異動都會影響到壓在下方頁面上的財務資訊,因此在有修改資料的情境下,需要在關閉 modal 的時候去重新刷新頁面上的財務資訊以符合資料修改後的現況;常規的處理方式是用 callback 處理(回傳有無異動資料的旗標,若有就更新頁面資訊),但筆者面對的情境是 modal 呼叫 modal 再呼叫另一個 modal 才抵達資料修改的表單,且重點是牽涉的表單複雜度高又多,那個資料是否異動的旗標又需要逐層判斷並傳遞回頁面上判斷,這樣傳來傳去我的心態應該會先崩潰。
因此我希望在頁面上去監聽一個【 Action: 資料已被修改 】,然後不管再哪個組件哪個表單只要對相依的資料有變動,就 dispatch 這個【 Action: 資料已被修改 】,此時頁面上就可以知道資料已被異動,接著來刷新頁面上的財務資料,且若有其他感興趣的組件需要知道相依資料是否被變動也可以自行監聽。
定義 Redux Slice
首先我們建立一個事件專用的 Slice 專門擺放事件專用的 Action,例如以下範例,建立了一個名為 event 的 Slice 後,因為沒有要使用到 reducer 來在這個 action 被 dispatch 時修改 state,所以就直接透過 createAction 在這個名稱下建立 financialInfoChanged 的 action ,用來表示財務資訊已被異動。
import { createAction, createSlice } from '@reduxjs/toolkit'
const initialState = {}
const eventSlice = createSlice({
name: 'event',
initialState,
reducers: {},
})
// 財務資料變動事件(想要同步更新資料的監聽事件)
export const financialInfoChanged = createAction(`${eventSlice.name}/financialInfoChanged`)
export default eventSlice.reducer
監聽 Action
在頁面組件上使用 useEffect 建立監聽,主要是透過 dispatch redux toolkit 中的 addListener 監聽特定 action,並且在這個特定 action 被 dispatch 時執行想要的 effect,最後需注意要在 useEffect 的 cleanup function 中 unsubscribe 這個監聽,這樣就大功告成了!
import { addListener } from '@reduxjs/toolkit'
import { useEffect } from 'react'
const Page = () => {
useEffect(() => {
const unsubscribe = dispatch(
addListener({
// 監聽的 action(金融資料有被改變)
actionCreator: financialInfoChanged,
// 執行的 effect
effect: (action, listenerApi) => {
// 可以從 action 中取得 payload
// 可以從 listenerApi 中取得 dispatch, getState, getOriginalState 等方法使用
// 更新本頁資料的動作
updateFinancialInfo()
},
}),
)
return unsubscribe
}, [dispatch])
// ... 略 ...
}
若有需求需要全域的監聽 action ,則可以透過 createListenerMiddleware 來實現,這部份可以參考筆者建立的 react 架構產生器中的範例
觸發 Action
觸發方式就跟一般沒兩樣,只要在需要的時機 dispatch 那個被監聽的 action 就搞定了!
import { useDispatch } from 'react-redux'
import { financialInfoChanged } from 'slices/eventSlice'
const Form = () => {
const onSubmitPopupForm = async (data) => {
const response = await apiHW020301(data).unwrap()
if (response.returnCode.isSuccess()) {
// 成功修改資料後觸發 action
dispatch(financialInfoChanged())
}
}
// == 略 ==
}
參考資訊
redux toolkit: createListenerMiddleware
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !