React 开发者:“我要弹个窗!”
Ant Design:“你要哪种?普通弹窗、确认弹窗、全屏弹窗、嵌套弹窗、还是套了 18 层 iframe 的弹窗?”
没错!Ant Design Modal 不仅仅是个弹窗,它其实是 React 设计模式的“样板间” ——
今天,我们就来扒一扒它的“底裤”,看看里面藏着多少 高阶组件、Render Props、Hooks 魔法……
准备工作
进入 Ant Design代码库 拉取项目,方便查找Ant Design Modal里面的React设计模式。
git clone git@github.com:ant-design/ant-design.git
找到components/modal就可以进去正题了。
Hook组件模式
看见modal文件夹映入眼帘的就是一个Hook 组件 useModal。这也React中很常见的一种设计模式,因为Hook可以专门设计为满足组件需求并具有额外的用例,而且隔离所有有状态逻辑,并使用自定义钩子在组件中组合或使用它,提高了代码的可读性和可维护性,更容易共享状态逻辑。
通常用useXXX来命名一个Hook 组件。
当前
useModal提供了Modal实例去创建不同类型的Modal以及Context上下文的React节点。
复合模式
复合模式稍微有点模式,简单来说,复合模式就是旨在处理一些彼此关联的组件,共享转态以及逻辑,执行同一项任务,使得组件更加自然和易于理解,同时保持了内部的封装
通常来说,一般是以下格式
Form
Form.Item
举个例子
import React, { useState, type ReactNode } from "react";
type LightContextType = {
open: boolean;
toggle: React.Dispatch<React.SetStateAction<boolean>>;
};
const LightContext = React.createContext<LightContextType>({
open: true,
toggle: () => { },
});
type LightProps = {
children: ReactNode;
};
export default function Light(props: LightProps) {
const [open, toggle] = useState(true);
return (
<LightContext.Provider value={{ open, toggle }}>
{props.children}
</LightContext.Provider>
);
}
export function On() {
const { open, toggle } = React.useContext(LightContext);
const handleClick = () => {
toggle(false);
}
return (
<>
{open && <div className="p-8 text-center">
<h1 className="text-3xl font-bold mb-4">On</h1>
<p className="text-lg text-gray-600">This is the On page.</p>
<button
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded cursor-pointer"
onClick={handleClick}
>
Toggle Off
</button>
</div>}
</>
);
}
export function Off() {
const { open, toggle } = React.useContext(LightContext);
const handleClick = () => {
toggle(true);
}
return (
<>
{!open && <div className="p-8 text-center">
<h1 className="text-3xl font-bold mb-4">Off</h1>
<p className="text-lg text-gray-600">This is the Off page.</p>
<button
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded cursor-pointer"
onClick={handleClick}
>
Toggle On
</button>
</div>}
</>
);
}
Light.On = On;
Light.Off = Off;
上面的这个例子中就是在Light组件中定义了灯的开关状态以及开关方法,通过context传递给开关组件,开关组件通过context上下文去执行灯的开启与关闭。
进入
index.tsx页面就发现了Modal.info 、Modal.success ...,这么快就发现发现了React 复合模式。
实不相瞒,这并不是的,因为这是只通过confirm方法去渲染一个dom元素去实现一个弹框。
实际上并没有使用通过上下文去协调工作,执行同一项任务,
Modal.info 、Modal.success ...仅仅就是今天方法的调用罢了。
HOC模式
Hoc组件又称为高阶组件,接受一个组件的组件,然后返回一个增强的新组件,旨在不同的组件复用相同的逻辑,可以在多个组件之间共享横切关注点,如权限控制,样式处理,共同状态等等。
通常来说格式为withXXX
举个例子
type Person = {
name: string;
age: number;
address: string;
}
type Props = {
person: Person;
}
const withAuth = (Component: React.ComponentType<Props>) => {
return function AuthenticatedComponent(props: Props) {
console.log("AuthenticatedComponent", props);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 5000);
}).then((res) => {
setIsAuthenticated(res as boolean);
});
}, []);
if (!isAuthenticated) {
return <div className="p-8 text-center">Please log in to view this content.</div>;
}
return <Component {...props} />;
};
};
export default function Home() {
const [data, setData] = useState<Person | null>(null);
const fetchData = () => {
const person: Person = {
name: "John Doe",
age: 30,
address: "123 Main Stare, Anytown, USA",
};
setData(person);
};
useEffect(() => {
fetchData();
}, []);
const AuthShowPerson = withAuth(ShowPerson);
return <>
<AuthShowPerson person={data!} />
</>;
}
上面的这个例子就是用一个增强的组件去判断永福是否有权限,从而控制不同的加载方式。
注意:在index.tsx中就存在withInfo、widthSuccess等等的方法,其实这也不是一个高阶组件,因 为没有传入一个组件,也没有返回一个组件。
在PurePanel.tsx文件中就发现了一个withPureRenderTheme的高阶组件,用于增强PurePanel。
Render Props模式
在 React 组件之间共享代码的技术,通过一个 prop(通常是一个函数)来传递需要渲染的内容或逻辑。组件不渲染固定的 UI,而是调用这个函数 prop,并将内部状态或逻辑作为参数传递给它,由这个函数来决定最终渲染是什么,提供了极大的灵活性,可以将可复用的行为或数据逻辑注入到不同的组件中,而无需关心这些组件的具体渲染方式。
举个例子
type HomeProps = {
render: () => React.ReactNode;
}
const MyDiv = (props: HomeProps) => {
return (
<div {...props}>
{props.render()}
</div>
);
}
function RenderContent(props: HomeProps) {
return (
<MyDiv {...props} />
);
}
这种模式在Modal组件中隐藏得比较深,但是在一个demo例子中体现了出来。
可以看出来modalRender这个方法是在Modal组件下的,细扒看Modal组件又用了Dialog组件,
import Dialog from 'rc-dialog';可知Dialog是通过这个包rc-dialog导入的。
直接去看rc-dialog这个包的源码发现了。
容器和表示模式
容器和表示模式旨在将表示逻辑和业务逻辑从代码中分开,从而使其模块化、可测试,并遵循关注点分离原则。
- 容器模式:负责数据和逻辑。它们通常是类组件或使用 Hooks 的函数组件,负责获取数据、管理状态、处理业务逻辑,并将数据和回调函数通过 props 传递给表示组件。
- 表示模式:负责 UI 的外观。它们通常是函数组件,接收 props 并渲染 UI。它们不关心数据如何加载或修改,只负责展示数据。
举个例子
容器模式
type Person = {
name: string;
age: number;
address: string;
}
export default function Home() {
const [data, setData] = useState<Person | null>(null);
const fetchData = () => {
const person: Person = {
name: "John Doe",
age: 30,
address: "123 Main Stare, Anytown, USA",
};
setData(person);
};
useEffect(() => {
fetchData();
}, []);
return <ShowPerson person={data!} />;
}
表示模式
type Props = {
person: Person;
}
function ShowPerson(porps: Props) {
const { person } = porps;
if (!person) {
return <div>Loading...</div>;
}
return (
<div className="p-8 text-center">
<h1>{person.name}</h1>
<p>Age: {person.age}</p>
<p>Address: {person.address}</p>
</div>
);
}
这种模式在Modal的这个组件设计中没有体现出来,因为这种模式是用于业务场景。
总结
在React项目中,React 设计模式无处不在,我们通过Ant Design Modal了解到了React 设计模式设计模式的精髓体现,希望我们在React项目中也能熟练运用这几种设计模式,写出良好优美的代码。