背景
弹窗是我们用的比较多的组件,假设我们要实现一个弹窗表单,单击某个按钮触发这个弹窗,这种在增删改查的场景下特别多。
传统做法
首先,我们会将表单作为一个组件来封装,里面使用Modal组件
// user/UserForm.jsx
const UserForm = ({onConfirm, onCancel}) => {
return <Modal>....
<Button onClick={onConfirm}>确定</Button>
<Button onClick={onCancel}>取消</Button>
</Modal>
}
其次,在主页面使用这个组件,声明一个变量showUserForm控制弹窗显示与否
import UserForm from './UserForm';
const User = () => {
const [showUserForm, setShowUserForm] = useState(true);
// 点击弹窗
const handleClick = () => {
setShowUserForm(true);
}
// 关闭弹窗
const handleClose = () => {
setShowUserForm(false);
}
return <>
//... 其它逻辑
<Button onClick={handleClick}>新增用户</Button>
showUserForm && <UerForm />
<>
}
最后,给弹窗组件增加两个回调函数onConfirm、onCancel
import UserForm from './UserForm';
const User = () => {
const [showUserForm, setShowUserForm] = useState(true);
// 点击弹窗
const handleClick = () => {
setShowUserForm(true);
}
// 关闭弹窗
const handleClose = () => {
setShowUserForm(false);
}
// 用户点击【确定】回调
const onConfirm = () => {
}
// 用户点击【取消】回调
const onCancel = () => {
handleClose();
}
return <>
//... 其它逻辑
<Button onClick={handleClick}>新增用户</Button>
showUserForm && <UerForm onConfirm={onConfirm} onCancel={onCancel} />
<>
}
问题
以上的做法,有两个问题:1. 通过一个变量去控制弹窗显示与否不够优雅;2. 逻辑太分散,一个弹窗需要使用四个方法、一个变量;3. 如果这个页面有多个弹窗的需求,那么逻辑会更复杂,导致不可维护
那么我们有没有更优雅的方式处理这类问题的,答案肯定是有的,不然也不会有这篇文章了
期望做法
首先,用户表单组件没有变,完全不用改
// user/UserForm.jsx
const UserForm = ({onConfirm, onCancel}) => {
return <Modal>....
<Button onClick={onConfirm}>确定</Button>
<Button onClick={onCancel}>取消</Button>
</Modal>
}
其次,引入CommonModal,不需要通过变量来控制弹窗显示与否
import UserForm from './UserForm';
import CommonModal from './CommonModal';
const User = () => {
const store = useMoalStore();
// 点击弹窗
const handleClick = () => {
store.openModal(<UserForm/>);
}
return <>
//... 其它逻辑
<Button onClick={handleClick}>新增用户</Button>
<CommonModal store={store} />
<>
}
最后,补充其它逻辑
import UserForm from './UserForm';
import CommonModal from './CommonModal';
const User = () => {
const store = useMoalStore();
// 点击弹窗
const handleClick = () => {
const onConfirm = () => {
}
store.openModal(<UserForm onConfirm={onConfirm}, onCancel={()=>{store.closeModal()}}/>);
}
return <>
//... 其它逻辑
<Button onClick={handleClick}>新增用户</Button>
<CommonModal store={store} />
<>
}
优势
这种写法带来几个变化:1. 不需要通过变量去控制弹窗,而是通过store对象去控制;2. 逻辑集中,所有逻辑都可以集中在一个handleClick,包括弹窗组件、两个回调函数、回调逻辑,阅读代码更方便;3. 支持多个弹窗,如果这个页面还有其它弹窗,那么不需要再写一个 ,它完全是可以复用的,任何地方都可以使用store.openMoal打开一个弹窗
CommonModal实现
它是一个公共组件,放在公共组件目录下
首先是CommonModal/index.jsx组件,它很简单,就是监听visible的变化,然后如果为true就render表单组件,如果为false就隐藏表单组件
// CommonModal/index.jsx
import React, { useEffect, useState } from 'react';
import useModalStore from './useModalStore';
import Store from './store';
export { useModalStore as useModalStore };
export { Store as Store };
const CommonModal = props => {
const { store } = props;
const [visible, setVisible] = useState(false);
useEffect(() => {
// eslint-disable-next-line no-underscore-dangle
store.__onVisibleChanged = value => {
setVisible(value);
};
}, [store]);
if (visible) {
return store.Component;
} else {
return null;
}
};
export default CommonModal;
其次是Store类,仅提供两个方法,其中openModal会将表单组件存在实例变量,并触发CommonModal更新
/** @format */
class Store {
__onVisibleChanged = null;
openModal(Component, config = {}) {
this.Component = Component;
this.__onVisibleChanged && this.__onVisibleChanged(true);
}
closeModal() {
this.__onVisibleChanged && this.__onVisibleChanged(false);
}
}
export default Store;
最后是一个useModalStore hooks,很简单,就暴露一个单例的store
import { useMemo } from 'react';
import Store from './store';
const useModalStore = () => {
const store = useMemo(() => new Store(), []);
return store;
};
export default useModalStore;
总结
使用这种方式,能让代码更紧凑、可读性更好,我见过一个文件有好多个弹窗,每个弹窗的逻辑还很复杂,不了解这块逻辑的代码是很难读懂的。 用了这种方法之后,会让代码更简洁,也更容易拆分和复用逻辑