Ant Design Modal 的“套娃”艺术:React 设计模式

316 阅读5分钟

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就可以进去正题了。

image.png

Hook组件模式

看见modal文件夹映入眼帘的就是一个Hook 组件 useModal。这也React中很常见的一种设计模式,因为Hook可以专门设计为满足组件需求并具有额外的用例,而且隔离所有有状态逻辑,并使用自定义钩子在组件中组合或使用它,提高了代码的可读性和可维护性,更容易共享状态逻辑。

通常用useXXX来命名一个Hook 组件

image.png 当前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上下文去执行灯的开启与关闭。

image.png 进入index.tsx页面就发现了Modal.info 、Modal.success ...,这么快就发现发现了React 复合模式。 实不相瞒,这并不是的,因为这是只通过confirm方法去渲染一个dom元素去实现一个弹框。 image.png 实际上并没有使用通过上下文去协调工作,执行同一项任务,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

image.png

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例子中体现了出来。

image.png

可以看出来modalRender这个方法是在Modal组件下的,细扒看Modal组件又用了Dialog组件, import Dialog from 'rc-dialog';可知Dialog是通过这个包rc-dialog导入的。 直接去看rc-dialog这个包的源码发现了。

image.png

容器和表示模式

容器和表示模式旨在将表示逻辑和业务逻辑从代码中分开,从而使其模块化、可测试,并遵循关注点分离原则。

  • 容器模式:负责数据和逻辑。它们通常是类组件或使用 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项目中也能熟练运用这几种设计模式,写出良好优美的代码。