React項目中hook實現展示對話框功能
React中使用對話框並不容易,主要因為:
- 對話框需要在父組件中聲明,才能在子組件中控制其是否顯示
- 給對話框傳遞參數隻能由props傳入,這意味著所有狀態管理都必須在更高階的組件中。而實際上這個對話框的參數隻在子組件中才會維護。這時就需要我們使用自定義事件將參數傳回
這些問題的本質就是:如何用一個統一的方式去管理對話框,從而讓對話框相關的業務邏輯更加模塊化,以及和其他業務邏輯進行解耦。
下面的方式隻是經驗總結,並不是唯一或者最佳實現:
思路:使用全局狀態管理所有對話框
對話框本質上是獨立於其他界面的一個窗口,用於完成一個獨立的功能。
所以,定義一個對話框,定位等價於定義一個具有唯一URL路徑的頁面。隻是前者由彈出層實現,後者是頁面的切換。
對話框UI彈出過程和頁面URL的切換非常類似,那麼我們就可以給每一個對話框定義一個全局唯一的ID,然後通過這個ID去顯示或者隱藏一個對話框,並且給它傳遞參數。
嘗試設計一個API去做對話框的全局管理
假設我們實現的對話框為NiceModal,那麼我們的目標是如下去使用:
const UserInfoModal = NiceModal.create( 'user-info-modal', RealUserInfoModal ) // 創建一個useNiceModal 這樣的hook去獲取某個id的對話框的操作對象 const modal = useNiceModal('user-info-modal') // 通過 modal.show 顯示一個對話框,並能夠給它傳遞參數 modal.show(args) modal.hide()
可以看到,如果有這樣的API,那麼無論在哪個層級的組件,隻要知道某個Modal的ID,那麼就都可以統一使用這些對話框,而不再需要考慮該在哪個層級的組件去定義瞭。
實現:創建NiceModal組件和相關API
創建一個處理所有對話框的action creator 和 reducer
function showModal(modalId, args) { return { type: "nice-modal/show", payload: { modalId, args } } } function hideModal(modalId, force) { return { type: "nice-modal/hide", payload: { modalId, force } } }
const modalReducer = (state = { hiding: {} }, action) { switch (action.type) { case "nice-modal/show": const {modalId, args} = action.payload return { ...state, // 如果存在 modalId 對應的狀態(即args),就顯示這個對話框 // 隻要有參數就認為對話框應該顯示,如果沒有傳遞args,在reducer中使用默認值true [modalId]: args || true, // 定義一個hiding 狀態, 用於處理對話框關閉動畫 hiding: { ...state.hiding, [modalId]: false, } } case "nice-modal/hide": const { modalId, force: boolean } = action.payload // 隻有force時才真正移除對話框,否則就是隱藏中hiding return action.payload.force ? { ...state, [modalId]: false, hiding: { [modalId]: false } } : { ...state, hiding: { [modalId]: true } } default: return state } }
這段代碼的主要思路就是通過Redux的store去存儲每個對話框狀態和參數。在這裡設計瞭兩個action,分別顯示和隱藏對話框。
特別註意的是,這裡加入瞭hiding這樣的一個狀態,用來處理對話框關閉過程動畫。
根據使用順序,首先實現 createNiceModal,
使用容器模式,在對話框不可見時直接返回null,從而不渲染任何內容,
確保即使頁面上定義瞭100個對話框,也不會影響性能。
createNiceModal = (modalId, Comp) => { return (props) => { const { visible, args } = useNiceModal(modalId) if (!visible) return null return <Comp {...args} {...props} /> } } // 使用 const MyModal = createNiceModal('my-modal', () => { return ( <NiceModal id="my-modal" title="Nice modal"> Hello NiceModal </NiceModal> ) })
實現useNiceModal,根據id,封裝一些邏輯。
讓Redux的action使用起來更方便,在其內部封裝對store的操作,從而實現對話框狀態管理的邏輯重用。
const modalCallbacks = {} const useNiceModal = (modalId) => { const dispatch = useDispatch() // 封裝Redux action 用於顯示對話框 const show = useCallback( (args) => { dispatch(showModal(modalId, args)) }, [dispatch, modalId] ) // 封裝Redux action 用於隱藏對話框 (force: boolean) const hide = useCallback( (force) => { dispatch(hideModal(modalId, force)) }, [dispatch, modalId] ) const args = useSelector((s) => s[modalId]) const hiding = useSelector((s) => s.hiding[modalId]) // 隻要有參數就認為對話框應該顯示,如果沒有傳遞args,在reducer中使用默認值true return { args, hiding, visible: !!args, show, hide } }
這樣,我們就實現瞭一個NiceModal這樣的全局對話管理框架。
這樣使用:
import { Button } from 'antd' import NiceModal, { createNiceModal, useNiceModal } from "./NiceModal" const MyModal = createNiceModal("my-modal", () => { return ( <NiceModal id="my-modal" title="Nice Modal"> Hello World </NiceModal> ) }) function MyModalExample() { const modal = useNiceModal("my-modal") return ( <> <Button type="primary" onClick={() => modal.show()}> Show my modal </Button> <MyModal /> </> ) }
處理對話框的返回值
如果說對話框和頁面這兩種UI模式基本上是一致的,都是獨立窗口完成獨立邏輯。但是在用戶交互上,有一定的差別:
- 對話框可能需要返回值給調用者
- 而頁面切換一般不會關心頁面執行的結果是什麼
基於上面的NiceModal實現邏輯,現在考慮如何讓調用者獲得返回值。
我們可以把用戶在對話框中的操作看成一個異步操作邏輯,那麼用戶在完成對話框中內容的操作後,就認為異步操作邏輯完成瞭。因此我們可以利用Promise來完成這樣的邏輯。
那麼,我們要實現的API如下:
const modal = useNiceModal('my-modal') // 實現一個 promise API 來處理返回值 modal.show(args).then(res => {})
事實上,要實現這樣一個機制並不困難,就是在 useNiceModal 這個 Hook 的實現中提供一個 modal.resolve 這樣的方法,能夠去 resolve modal.show 返回的 Promise。
代碼的核心思路就是將show 和 resolve 兩個函數通過 Promise 聯系起來。因此兩個函數調用位置不一樣,所以我們使用一個局部的臨時變量,來存放resolve回調函數。
// 使用一個 object 緩存 promise 的 resolve 回調函數 const modalCallbacks = {}; export const useNiceModal = (modalId) => { const dispatch = useDispatch(); const show = useCallback( (args) => { return new Promise((resolve) => { // 顯示對話框時,返回 promise 並且將 resolve 方法臨時存起來 modalCallbacks[modalId] = resolve; dispatch(showModal(modalId, args)); }); }, [dispatch, modalId], ); const resolve = useCallback( (args) => { if (modalCallbacks[modalId]) { // 如果存在 resolve 回調函數,那麼就調用 modalCallbacks[modalId](args); // 確保隻能 resolve 一次 delete modalCallbacks[modalId]; } }, [modalId], ); // 其它邏輯... // 將 resolve 也作為返回值的一部分 return { show, hide, resolve, visible, hiding }; };
總結
到此這篇關於React項目中hook實現展示對話框功能的文章就介紹到這瞭,更多相關React hook展示對話框內容請搜索LevelAH以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持LevelAH!
推薦閱讀:
- 使用react+redux實現彈出框案例
- Vue3中Vuex的詳細使用方法
- 詳解React 和 Redux的關系
- Modal.confirm是否違反瞭React模式分析
- 淺析Promise的介紹及基本用法