We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
将弹窗组件嵌套在调用的组件下面, 弹窗组件的显示隐藏由父组件控制.
function Dialog({ visible, onCancel, id}) { return ( <Modal visible={visible} onCancel={onCancel}> 我是弹窗组件{id} </Modal> ) } function Parent() { const [visible, setVisible] = useState(false); const [currentId, setCurrentId] = useState(0); const open = () => { setVisible(true); setCurrentId(1); } return ( <div> <Button onClick={open}>open</Button> <Dialog id={currentId} visible={visible} onCancel={() => setVisible(false)} /> </div> ) }
针对第一个问题, 因为hooks的出现, 直接将弹窗的显示和隐藏的逻辑抽象成为一个钩子函数.
import { useReducer, useCallback, Reducer } from 'react'; type ReducerState<T> = { visible: boolean } & T; interface ReducerAction<T> { type: 'open' | 'close'; payload?: Partial<T>; } function reducer<T>(state: ReducerState<T>, action: ReducerAction<T>) { const { type, payload } = action; switch (type) { case 'open': return { ...state, visible: true, ...payload }; case 'close': return { ...state, visible: false, ...payload }; default: throw new Error(); } } export default function useDialog<T extends object>(initial: T) { const [state, dispatch] = useReducer<Reducer<ReducerState<T>, ReducerAction<T>>>(reducer, { visible: false, ...initial }); const close = useCallback( (payload: Partial<T> = {}) => { dispatch({ type: 'close', payload }); }, [dispatch] ); // 调用open的时间直接赋值其他参数过去. const open = useCallback( (payload: Partial<T> = {}) => { dispatch({ type: 'open', payload }); }, [dispatch] ); return { state, close, open }; }
function Parent() { const { open, close, state } = useDialog({ id: 0 }) return ( <div> <Button onClick={() => open({id: 1})}>open</Button> <Dialog {...state} onCancel={close} /> </div> ) }
上面写法只是解决了在一个组件下面调用的时候的简化.
可以将弹窗也看成一个页面, 和管理路由一样, 集中管理. 使用context和钩子实现. 通过useModal 钩子获取modal 关闭弹窗modal.hide(), 类似history.back() 打开弹窗modal.show(), 类似history.push()
import React, { useReducer, useContext, useMemo } from 'react'; export interface ModalManagerProps { children: React.ReactNode; modalComponentMap: { [prop: string]: React.ComponentType<any>; }; } interface ModalState { modalType: string | number; modalProps: any; } type ModalAction = | { type: 'SHOW_MODAL'; modalType: string | number; modalProps: any; } | { type: 'HIDE_MODAL'; }; // state设计成数组, 可以同时显示多个弹窗. 按照栈的方式后进先关闭. function reducer(state: ModalState[], action: ModalAction) { switch (action.type) { case 'SHOW_MODAL': return state.concat({ modalType: action.modalType, modalProps: action.modalProps }); case 'HIDE_MODAL': { const newState = state.slice(); newState.pop(); return newState; } default: return state; } } const initialState: ModalState[] = []; const ModalContext = React.createContext<{ hide(): void; show<T>(modalType: string, modalProps?: T | undefined): void; dispatch: React.Dispatch<ModalAction>; }>({} as any); export const useModal = () => { return useContext(ModalContext); }; function ModalManager({ children, modalComponentMap }: ModalManagerProps) { const [state, dispatch] = useReducer(reducer, initialState); const modal = useMemo(() => { return { hide() { dispatch({ type: 'HIDE_MODAL' as 'HIDE_MODAL' }); }, show<T>(modalType: string, modalProps?: T) { dispatch({ type: 'SHOW_MODAL' as 'SHOW_MODAL', modalType, modalProps }); }, dispatch }; }, []); const renderModal = (modalDescription: ModalState) => { const { modalType, modalProps = {} } = modalDescription; const ModalComponent = modalComponentMap[modalType]; if (!ModalComponent) { // eslint-disable-next-line no-console console.warn(`${modalType} is not in the modalComponentMap`); return null; } return <ModalComponent {...modalProps} key={modalType} />; }; return ( <ModalContext.Provider value={modal}> {children} {state.map(renderModal)} </ModalContext.Provider> ); } export default ModalManager;
使用方法, 将ModalManager 放到根组件, 通过context传递modal到下面的组件调用.
const modalComponentMap = { dialog1: DialogComponent1, dialog2: DialogComponent2 } const App = () => { return ( <ModalManager modalComponentMap={modalComponentMap}> <Router history={history}>{renderRoutes(routes)}</Router> </ModalManager> ) }
调用方法
function Parent() { const modal = useModal(); const open = () => { modal.show('dialog1', { id: 1 }) } return ( <div> <Button onClick={open}>open</Button> </div> ) } function Dialog({ id}) { const modal = useModal(); // 这里使用的antd的modal, visible要直接设置成true return ( <Modal visible onCancel={modal.close}> 我是弹窗组件{id} </Modal> ) }
## 后续可改进 现在的弹窗关闭之后都是直接销毁, 使用antd的modal,调用关系之后关闭的动画可能会没有了. 因为是直接连同modal组件一起销毁, 组件的状态都是不能保存, 重新调用之后重新渲染. 后续应该可以通过设置prop控制直接销毁还是不显示.
通过ModalStore组件内部直接代理管理visible和onClose方法, 这样也可以直接控制所有的modal是否直接销毁或者是隐藏. 完整代码: https://github.com/mengxiong10/react-modal-store
The text was updated successfully, but these errors were encountered:
大佬优化思路很好不过,请问对于父组件弹窗很重导致性能问题这种前提有具体的测试过或者深入了解过嘛,对这一点有些疑惑
Sorry, something went wrong.
@PENGFEI-CN 对的, 上面的方式是每次都销毁组件, 可以设计成不销毁弹窗组件(只是通过注入属性隐藏和显示), 其实实际中就是这么做的, 有时间我会更新一下..
@mengxiong10 老哥写的很好啊, 等后续...
No branches or pull requests
常见写法
将弹窗组件嵌套在调用的组件下面, 弹窗组件的显示隐藏由父组件控制.
遇到问题
第一次优化
针对第一个问题, 因为hooks的出现, 直接将弹窗的显示和隐藏的逻辑抽象成为一个钩子函数.
上面写法只是解决了在一个组件下面调用的时候的简化.
第二次优化
可以将弹窗也看成一个页面, 和管理路由一样, 集中管理. 使用context和钩子实现.
通过useModal 钩子获取modal
关闭弹窗modal.hide(), 类似history.back()
打开弹窗modal.show(), 类似history.push()
使用方法, 将ModalManager 放到根组件, 通过context传递modal到下面的组件调用.
调用方法
## 后续可改进现在的弹窗关闭之后都是直接销毁, 使用antd的modal,调用关系之后关闭的动画可能会没有了. 因为是直接连同modal组件一起销毁, 组件的状态都是不能保存, 重新调用之后重新渲染. 后续应该可以通过设置prop控制直接销毁还是不显示.第三次优化
通过ModalStore组件内部直接代理管理visible和onClose方法, 这样也可以直接控制所有的modal是否直接销毁或者是隐藏.
完整代码: https://github.com/mengxiong10/react-modal-store
The text was updated successfully, but these errors were encountered: