logoAnt Design

⌘ K
  • 设计
  • 研发
  • 组件
  • 博客
  • 资源
  • 国内镜像
5.26.0
  • v6 的一些 CSS 琐事
  • 👀 视觉回归测试
  • 为什么禁用日期这么难?
  • 封装 Form.Item 实现数组转对象
  • 行省略计算
  • 📢 v4 维护周期截止
  • antd 里常用的 TypeScript 工具方法
  • 一个构建的幽灵
  • 当 Ant Design 遇上 CSS 变量
  • API 的历史债务
  • 灵动的 Notification
  • 色彩模型与颜色选择器
  • 主题拓展
  • 虚拟表格来了!
  • 快乐工作主题(一)
  • 动态样式去哪儿了?
  • Suspense 引发的样式丢失问题
  • 打包体积优化
  • 你好,GitHub Actions
  • 所见即所得
  • 静态方法之痛
  • SSR 静态样式导出
  • 依赖排查
  • 贡献者开发维护指南
  • 转载-如何提交无法解答的问题
  • 新的 Tooltip 对齐方式
  • 非必要的渲染
  • 如何成长为 Collaborator
  • Modal hook 的有趣 BUG
  • antd 测试库迁移的那些事儿
  • Tree 的勾选传导
  • getContainer 的一些变化
  • 组件级别的 CSS-in-JS
静态方法
Context 丢失之痛
Hooks
最后

静态方法之痛

2023-04-26
@zombieJ

文章被以下专栏收录:

antd

Ant Design

一个 UI 设计体系
我有想法,去参与讨论
antd

Ant Design

Ant Design 官方专栏
我有想法,去参与讨论
文档贡献者
  • 所见即所得SSR 静态样式导出

    相关资源

    Ant Design X
    Ant Design Charts
    Ant Design Pro
    Pro Components
    Ant Design Mobile
    Ant Design Mini
    Ant Design Web3
    Ant Design Landing-首页模板集
    Scaffolds-脚手架市场
    Umi-React 应用开发框架
    dumi-组件/文档研发工具
    qiankun-微前端框架
    Ant Motion-设计动效
    国内镜像站点 🇨🇳

    社区

    Awesome Ant Design
    Medium
    Twitter
    yuque logoAnt Design 语雀专栏
    Ant Design 知乎专栏
    体验科技专栏
    seeconf logoSEE Conf-蚂蚁体验科技大会
    加入我们

    帮助

    GitHub
    更新日志
    常见问题
    报告 Bug
    议题
    讨论区
    StackOverflow
    SegmentFault

    Ant XTech logo更多产品

    yuque logo语雀-构建你的数字花园
    AntV logoAntV-数据可视化解决方案
    Egg logoEgg-企业级 Node.js 框架
    Kitchen logoKitchen-Sketch 工具集
    Galacean logoGalacean-互动图形解决方案
    xtech logo蚂蚁体验科技
    主题编辑器
    Made with ❤ by
    蚂蚁集团和 Ant Design 开源社区

    message.success 用的好好的为什么要 warning 我去用 hooks?antd 越做越垃圾,走好不送!

    我们在社交渠道听到了不少关于静态方法转 hooks 的质疑。我们深知这非常痛苦,但是在经过多年的考虑后,我们还是决定在 v5 做一次切割(是的,这个讨论甚至比 hooks 存在还久远,但是在 hooks 之前一直没有简单的实现方式,我们也就一直将其搁置在一边)。

    静态方法

    在 JS 之初,就存在一个简单好用的 API alert。你可以在任何时候、任何地方调用它。而到了框架层面,这种便捷同样令人心驰神往。一个常见的例子就是我在 Redux 中,ajax 获取数据失败就调用一下 message.error 在屏幕上展示一个错误信息:

    Fetch Failed

    然而从数据流角度看,这其实耦合了 UI 和 数据层。只是因为其看起来调用时并不直接依赖 UI 上下文,所以它看起来是无害的而已。从测试角度看,这种耦合也会让测试变得复杂。

    Context 丢失之痛

    在函数中调用静态方法,虽然看起来存在上下文。但是实际上静态方法并不会消费上下文,它会独立于当前 React 生命周期,因而通过 Context 获取的内容其实什么都得不到:

    tsx
    const Context = React.createContext('default');
    const MyContent = () => React.useContext(Context);
    const Wrapper = () => {
    React.useEffect(() => {
    // Static function is out of context.
    // We can only get `default` instead of `Hello World`.
    message.success(<MyContent />);
    }, []);
    return null;
    };
    const Demo = () => (
    <Context.Provider value="Hello World">
    <Wrapper />
    </Context.Provider>
    );

    静态方法实现其实是通过独立的 ReactDOM.render 来创建一个新的 React 实例,在可以获得任意场景调用的同时也和当前调用者上下文完全无关。所以你很容易就想到,如果我配置了主题、国际化、全局配置等等,那么这些配置都不会生效。

    在我说到这里的同时,你可能会反应到:“等等!antd 的静态方法国际化是生效的呀!”

    没错,但是这并不是真正的消费了 Context,而是我们做了一个非常 Hack 的实现。当用户通过 ConfigProvider 提供 locale 属性时,我们会临时将其存到一个全局变量中。而当静态方法调用时,则使用其进行填充:

    tsx
    // Sample. Not real world code.
    let globalLocale = null;
    const ConfigProvider = (props) => {
    if (props.locale) {
    globalLocale = props.locale;
    }
    // ...
    };
    Modal.confirm = (props) => {
    // ...
    ReactDOM.render(
    <ConfigProvider locale={globalLocale}>
    <Modal {...modalProps} />
    </ConfigProvider>,
    );
    };

    你可以很容易看出来,这个代码非常不健壮。静态方法其实根本不知道调用层级是什么,它可能是在 ConfigProvider 之内调用,也可能是在之外调用。甚至可能同时存在配置了多个 ConfigProvider 的情况。这种情况下,我们无法也不可能保证静态方法能够正确的获取到当前的配置。

    而当我们开始支持动态主题的时候,这个问题就会变得更加明显。在主题中,很容易遇到混合主题的情况。开发者在不同层级调用 Modal、message、notification 它们的样式可能完全不同。

    Hooks

    就如上文所述,为了消费 Context。我们调用方法时需要知道当前的节点位置,因而在 v4 中为静态方法引入了对应的 Hooks 方法:

    tsx
    const Demo = () => {
    const [messageApi, contextHolder] = message.useMessage();
    const info = () => {
    messageApi.info('Hello, Ant Design!');
    };
    return (
    <>
    {/* Different insert holder position will get different context */}
    {contextHolder}
    <Button type="primary" onClick={info}>
    Display normal message
    </Button>
    </>
    );
    };

    你可以发现,这其实不太方便。对于开发者而言,每个使用的地方都从过去直接调用,变成了需要额外设置注入 Context 节点。而在大多数情况下,过去静态方法的 Context 往往只需要关注国际化、主题等比较稳定的配置。所以我们如果可以有一个地方放置 Holder,其他地方直接复用那就更好了。

    App

    因而在 v5 版本中,我们提供了 App 组件。这个组件本身带有 DOM 结构,会为自节点添加一些重置样式(比如在过去版本被人诟病的全局样式污染,现在只会作用到 App 之下)。同时也为 Modal、message、notification 添加了 ContextHolder。这样开发者在应用最外层添加 App 后,代码中就可以简单的使用它们了:

    tsx
    const Demo = () => {
    const { message } = App.useApp();
    React.useEffect(() => {
    message.success('Hello World');
    }, []);
    // ...
    };

    最后

    从设计角度来说,静态方法是一个非常不好的实现。但是我们深知在业务场景中,静态方法是如此便利、如此好用。即便它有一些“无伤大雅”的缺点,但是它仍然在历史中值得有一席之地。所以我们在思考,是否可以有其他的方式来将这些副作用从组件库中剥离但是同时又可以服务开发者。比如说改进 umi antd 插件,当配置 appData 时,自动将顶层的 App 实例静态化到 antd 中。当然,这只是一些想法。我们会在后续的版本中继续探索这个问题。