
Semi Design ConfigProvider 全局配置与 semiGlobal 默认 Props 覆盖实战指南【免费下载链接】semi-designA modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.Provide 3000 Design Tokens, easy to build your design system. Make Semi Design to Any Design. Design to Code in one click项目地址: https://gitcode.com/gh_mirrors/se/semi-designConfigProvider 是 Semi Design 提供的全局配置入口基于 React Context 机制为组件树统一注入timeZone、direction、locale、getPopupContainer等跨组件公有配置当 ConfigProvider 暴露的参数仍不满足需求时semiGlobal.config.overrideDefaultProps可在全局范围内直接改写任意组件的默认 Props。本文将结合douyinfe/semi-ui源码系统讲解两种覆盖方式的选型、完整配置示例、响应式断点订阅、RTL 适配与时区标识规则帮助你在一套代码中低成本实现多时区、多语言、RTL、定制默认行为的规模化组件配置。使用场景两种全局覆盖配置如何选型Semi Design 将覆盖配置划分为两类场景对应两套不同的 API场景手段影响范围需要覆盖多个组件公有 Props如timeZone、rtlConfigProviderReact 节点树中的子组件ConfigProvider暴露参数不满足需要修改某个组件的某类 Props如让所有Button的theme为solid或所有Popover的zIndex为固定值semiGlobal整个站点单例全局生效两者的根本区别在于作用机制ConfigProvider借助 React Context 向下传递作用域是声明它的组件子树semiGlobal则是一个全局单例对象作用于所有组件实例的默认 Props。下文分别展开。ConfigProvider 的实现机制基于 React ContextConfigProvider 的底层实现位于 packages/semi-ui/configProvider/index.tsx通过React.createContextContextValue({})创建上下文上下文值的完整结构定义在 packages/semi-ui/configProvider/context.tsx包含direction、timeZone、locale、getPopupContainer、responsiveMap、onBreakpoint、screens等字段渲染时用Context.Provider value{...}包裹子组件因此只有 ConfigProvider 之下的子组件能感知到配置当direction rtl时渲染逻辑会额外包一层div.semi-rtlrenderChildren方法这是 RTL 生效的样式载体ConfigConsumer就是Context.Consumer的别名导出见源码export const ConfigConsumer Context.Consumer供函数组件或非组件场景手动消费上下文。正因为依赖 Context 机制ConfigProvider 天然支持嵌套与局部作用域你可以在应用根部放一个全局 ConfigProvider再在某个局部区域嵌套一个覆盖了不同locale或direction的 ConfigProvider。基本用法为时间类组件全局配置时区如何引入import { ConfigProvider } from douyinfe/semi-ui;时区基本用法通过传入timeZone参数可以为DatePicker、TimePicker等时间类组件统一配置时区。下面的示例构建了一个从GMT-11:00到GMT14:00的时区选择器切换时区后下方的时间组件会同步按所选时区展示时间import React, { useMemo, useState } from react; import { ConfigProvider, Select, DatePicker, TimePicker } from douyinfe/semi-ui; function Demo(props {}) { const [timeZone, setTimeZone] useState(GMT08:00); const defaultTimestamp 1581599305265; const gmtList useMemo(() { const list []; for (let hourOffset -11; hourOffset 14 ; hourOffset) { const prefix hourOffset 0 ? : -; const hOffset Math.abs(parseInt(hourOffset, 10)); list.push(GMT${prefix}${String(hOffset).padStart(2, 0)}:00); } return list; }, []); return ( ConfigProvider timeZone{timeZone} div style{{ width: 300 }} h5 style{{ margin: 10 }}Select Time Zone:/h5 Select placeholder{请选择时区} style{{ width: 300 }} value{timeZone} showClear{true} onSelect{value setTimeZone(value)} {gmtList.map(gmt ( Select.Option key{gmt} value{gmt} {gmt} /Select.Option ))} /Select br/ br/ DatePicker type{dateTime} defaultValue{defaultTimestamp} onChange{(date, dateString) console.log(DatePicker changed: , date, dateString)} / br/ br/ TimePicker defaultValue{defaultTimestamp} onChange{(date, dateString) console.log(DatePicker changed: , date, dateString)} / /div /ConfigProvider ); }从源码看timeZone真正被消费的位置在 packages/semi-foundation/datePicker/foundation.tsinitFromProps会调用parseWithTimezone将本地时间按timeZone转换为目标时区展示回传时再通过zonedTimeToUtc转回 UTC相关逻辑也见 packages/semi-foundation/timePicker/foundation.ts。仓库内的测试用例如 packages/semi-ui/datePicker/test/datePicker.test.js 中check inputValue is correct when change timeZone验证了切换时区后输入值与受控值的正确性。手动获取配置值ConfigConsumer通常情况下组件内部会自动消费 ConfigProvider 的值无需关心。但某些特殊场景如自定义组件需要读取时区、方向做额外逻辑你可以通过ConfigConsumer手动获取import React, { useMemo, useState } from react; import { ConfigProvider, ConfigConsumer, Select, DatePicker, TimePicker, Typography } from douyinfe/semi-ui; function Demo(props {}) { const [timeZone, setTimeZone] useState(GMT08:00); const defaultTimestamp 1581599305265; const gmtList useMemo(() { const list []; for (let hourOffset -11; hourOffset 14; hourOffset) { const prefix hourOffset 0 ? : -; const hOffset Math.abs(parseInt(hourOffset, 10)); list.push(GMT${prefix}${String(hOffset).padStart(2, 0)}:00); } return list; }, []); return ( ConfigProvider timeZone{timeZone} {/*...*/} ConfigConsumer {(value) { return Typography.Text ellipsis{{ showTooltip: {opts:{style:{minWidth:1200px}} }}} style{{ width: 600 }} {JSON.stringify(value)} /Typography.Text }} /ConfigConsumer {/*...*/} /ConfigProvider ); }这里value即为 Context 中传递的完整配置对象你可以按需取用其中的timeZone、direction、locale、getPopupContainer、responsiveMap等字段。响应式断点监听ConfigProvider 支持配置响应式断点并在断点变化时进行订阅回调该能力在2.97.0版本提供。开启监听与自定义断点通过responsiveObserve开启断点监听建议只在确实需要订阅的场景开启通过responsiveMap自定义断点未传入时使用默认断点可通过ConfigProvider.defaultResponsiveMap获取。import React, { useEffect, useState } from react; import { ConfigProvider, ConfigConsumer, Typography } from douyinfe/semi-ui; function BreakpointSubscriber({ onBreakpoint, screens }) { const [subscribedScreens, setSubscribedScreens] useState(screens); useEffect(() { if (!onBreakpoint) { return; } const unsubscribe onBreakpoint(next { setSubscribedScreens(next); }); return unsubscribe; }, [onBreakpoint]); return ( Typography.Text {JSON.stringify(subscribedScreens || screens)} /Typography.Text ); } function Demo() { return ( ConfigProvider responsiveObserve responsiveMap{{ xs: (max-width: 575px), sm: (min-width: 576px), md: (min-width: 768px), lg: (min-width: 992px), xl: (min-width: 1200px), xxl: (min-width: 1600px), }} ConfigConsumer {({ onBreakpoint, screens }) ( BreakpointSubscriber onBreakpoint{onBreakpoint} screens{screens} / )} /ConfigConsumer /ConfigProvider ); }订阅 API 的两种签名onBreakpoint支持两种签名均会返回取消订阅函数onBreakpoint((screens) void)回调拿到完整的 screens 映射{ xs, sm, md, lg, xl, xxl }的布尔值对象onBreakpoint([md, lg], (screen, match) void)只监听指定断点回调拿到单个断点的变化。订阅时回调会立即执行一次传入的就是当前各断点的命中情况因此你不需要在初始挂载时再单独调用一次window.matchMedia。注意事项与实现细节由于性能考虑responsiveObserve默认值为false不开启时不会注册任何matchMedia监听onBreakpoint/screens不属于 ConfigProvider 的 props需要通过ConfigConsumer获取responsiveMap是引用比较如果直接 inline 写对象每次 render 引用都不同会被识别为发生变化并重新注册全部监听。建议把它定义在组件外或使用useMemo保证引用稳定。从源码 packages/semi-ui/configProvider/index.tsx 可以看到其实现策略断点类型Breakpointxs | sm | md | lg | xl | xxl与BreakpointScreens结构定义在 packages/semi-ui/configProvider/responsiveTypes.ts默认断点映射defaultResponsiveMap即上文示例中的六组 media query同时作为静态属性ConfigProvider.defaultResponsiveMap暴露开启后采用懒注册首次出现订阅者时才通过registerMediaQueries注册window.matchMedia监听底层工具为 packages/semi-ui/_utils/index.tsx 中的registerMediaQuery内部优先使用addEventListener旧环境回退到addListener当最后一个订阅者取消订阅后会自动注销监听以节省资源componentDidUpdate中关闭responsiveObserve也会触发注销注册前会先同步读取一次各断点的matches写入currentScreensRef与state.screens保证订阅回调立即执行时拿到的是最新快照而不是过期的异步 state。RTL/LTR 全局文本方向全局配置direction可以改变组件的文本方向自1.8.0版本支持rtl表示从右到左类似希伯来语或阿拉伯语ltr表示从左到右类似中文、英语等大部分语言。import React, { useState } from react; import { ConfigProvider, ButtonGroup, Button } from douyinfe/semi-ui; function Demo(props {}) { const [direction, setDirection] useState(); return ( div div style{{ marginBottom: 20 }} ButtonGroup Button onClick{() setDirection(ltr)}LTR/Button Button onClick{() setDirection(rtl)}RTL/Button /ButtonGroup /div ConfigProvider direction{direction} {/* 此处放置你的业务组件如 Button、Input、Select、DatePicker、Timeline、Steps、Breadcrumb、Nav、Pagination 等 */} /ConfigProvider /div ); }原文档中的完整演示content/other/configprovider/index.md 的 RTL/LTR 小节覆盖了 Button、Input、TextArea、Switch、Checkbox、Radio、DatePicker、TimePicker、Select、Cascader、Breadcrumb、Nav、Pagination、Steps、Badge、Avatar、Tag、Popover、Tooltip、Rating、Timeline、Notification、Modal、Toast、Spin 等大量组件可直接照搬运行观察 RTL 效果。使用 RTL 时需注意以下几点Modal、Notification、Toast 的命令式调用需要通过 prop 传direction这些组件由命令式 API 挂载不在 ConfigProvider 的 React 节点树内见 packages/semi-ui/notification/index.tsx、packages/semi-ui/toast/index.tsx 中对 direction 的处理如果你想对有方向性的Icon做 RTL 国际化需要自己单独处理。Semi 认为对 Icon 做 RTL 会使其难以理解和维护其他组件内的 icon Semi 已经做了 RTL 适配Table 的树形数据暂不支持 RTLChrome、Safari 浏览器表现与 Firefox 表现不同固定列在 v2.32 版本开始支持 RTL。从源码看RTL 的实现是通过renderChildren在direction rtl时额外包裹div classNamesemi-rtl组件层的样式通过semi-rtl前缀的 CSS 规则实现镜像布局。API 参考ConfigProvider 的完整 Props 如下与其在 packages/semi-ui/configProvider/index.tsx 中声明的ConfigProviderProps一致属性说明类型默认值direction设置文本的方向ltr|rtlltrresponsiveObserve是否开启响应式断点监听。默认关闭以避免全局注册matchMedia带来的性能开销开启后在首次订阅时懒注册监听无订阅时会自动注销2.97.0booleanfalseresponsiveMap自定义断点配置key 为xs/sm/md/lg/xl/xxlvalue 为 media query 字符串未传入时使用默认断点可通过ConfigProvider.defaultResponsiveMap获取2.97.0object默认断点映射getPopupContainer指定父级 DOM弹层将会渲染至该 DOM 中自定义需要设置position: relative。这会改变浮层 DOM 树位置但不会改变视图渲染位置function(): HTMLElement() document.bodylocale多语言配置同LocaleProvider中locale参数的用法如果同时在ConfigProvider和LocaleProvider中配置locale前者优先级高于后者object简体中文默认文案timeZone时区标识支持数字偏移与 IANA 标识详见下文时区标识详解string | number—时区标识详解timeZone支持两种形式的取值数字例如1、-9.5代表距离 UTC 的时间偏移单位为小时可以为负数或小数字符串可以是GMT-09:30、GMT08:00这类以GMT开头的偏移字符串也可以是 IANA 标识如Asia/Shanghai、America/Los_Angeles等。当你使用数字或GMT-09:00类似写法时Semi 内部会将这些时区标识转换为 IANA 标识例如设置-9或GMT-09:00时会转换成Pacific/Gambier。某些数字对应的 IANA 标识可能有多个Semi 首选无夏令时的 IANA 标识如果该数字没有对应的无夏令时 IANA 标识如-3.5、3.5、10.5、13.75这时映射的就是一个有夏令时的 IANA 标识有夏令时的时区会在偏移量上进行调整如-3.5会在进入夏令时后在标准时间上增加 1 小时。如果你想准确设置一个地区的时区推荐直接使用 IANA 标识而不是偏移量写法可以避免夏令时带来的偏差。该转换逻辑在源码中有清晰体现 packages/semi-foundation/utils/date-fns-extra.ts 中定义了IANAOffsetMap数字偏移到 IANA 标识的映射表如-9 → Pacific/Gambier、8 → Asia/Shanghai等、IANAEtcGMTOffsetMapEtc/GMT*无夏令时标识以及核心函数toIANA(tz)utcToZonedTime与zonedTimeToUtc在调用 date-fns-tz 之前都会先经过toIANA归一化。这也解释了为什么偏移量写法会优先落到无夏令时的标识映射表按此规则编排只有像-3.5、10.5、13.75这类没有无夏令时替代项的偏移才会映射到带夏令时的时区。FAQConfigProvider 未提供全局 prefixCls 怎么办ConfigProvider 中没有提供全局自定义 prefix classname 的功能。如果确实有类似需求例如 SDK 中使用了 Semi期望打包的 DOM 样式不带.semi-xx前缀以免被宿主的全局 CSS 影响实现方式如下由于prefixCls需要同时被组件层的 JS/CSS 消费Semi 将此开关放在了webpack plugin 的配置项中而不是作为 ConfigProvider 的配置项。如果你使用 webpack请在SemiWebpackPlugin的参数中进行配置// webpack配置示例 const SemiWebpackPlugin require(douyinfe/semi-webpack-plugin).default; module.exports { plugins: [new SemiWebpackPlugin({ prefixCls: imes })], }对应插件实现在 packages/semi-webpack/src 中通过编译期改写类名前缀保证 JS 层与 SCSS 层同步生效。semiGlobal全局覆盖组件默认 Props除了 ConfigProvider 外你还可以通过semiGlobal配置覆盖全局组件的默认 Props。该能力在v2.59.0之后提供。用法在semiGlobal.config.overrideDefaultProps中配置组件默认 Props。你需要将配置放到整个站点的入口处即优先于所有 Semi 组件执行import { semiGlobal } from douyinfe/semi-ui; semiGlobal.config.overrideDefaultProps { Select: { zIndex: 2000, }, Tooltip: { zIndex: 2001, trigger: click }, };例如上面的配置会将所有Button默认设置为warning原文示例指向 Button配置键以实际组件名为准、所有Select的zIndex默认设为 2000、所有Tooltip的zIndex设为 2001 且默认触发方式改为click。注意事项semiGlobal是单例模式会影响整个站点。如果你只想覆盖某些地方的某些组件 Props建议不要使用semiGlobal而是将对应需要覆盖的组件封装一层并在封装组件里传入修改后的默认 props。实现原理从源码结构看semiGlobal的定义在 packages/semi-ui/_utils/semi-global.ts它是一个class SemiGlobal的单例实例export default new SemiGlobal()内部维护config对象overrideDefaultProps的类型定义覆盖了 AutoComplete、Avatar、Button、Cascader、Checkbox、DatePicker、Dropdown、Form、Input、Modal、Navigation、Notification、Popconfirm、Popover、Progress、Radio、Select、SideSheet、Slider、Spin、Switch、Table、Tabs、Tag、TimePicker、Toast、Tooltip、Tree、TreeSelect、Typography、Upload 等几十个组件的PartialProps类型从semi-global.ts的SemiGlobalConfig接口可见完整清单。各组件消费该配置的统一入口是 packages/semi-ui/_utils/index.tsx 中的getDefaultPropsFromGlobalConfig(componentName, semiDefaultProps)它基于Proxy包装组件的默认 Props读取某个 key 时若全局配置中存在该 key 则优先返回全局值否则回退到组件自身默认值ownKeys会将全局配置的 key 合并进默认 Props 的枚举保证Object.keys(defaultProps)等场景也能感知到覆盖项组件侧通过static defaultProps getDefaultPropsFromGlobalConfig(Button.__SemiComponentName__)接入可见于 packages/semi-ui/button/index.tsx 等多个组件因此配置在入口执行一次后全站所有组件实例的默认值都会被改写。仓库内还提供了针对该能力的测试用例 packages/semi-ui/_utils/test/overrideDefaultProps.test.js覆盖了Input.showClear、Checkbox.defaultChecked、冻结对象Object.freeze等场景验证了覆盖优先级与 Proxy 不变式处理。小结ConfigProvider 与 semiGlobal 构成了 Semi Design 两层互补的全局配置体系需要树形作用域的跨组件公有配置时区、方向、语言、弹层挂载点、响应式断点用ConfigProviderConfigConsumer需要站点级全局改写某个组件默认 PropszIndex、trigger、theme 等用semiGlobal.config.overrideDefaultProps想要替换类名前缀以隔离宿主样式则走SemiWebpackPlugin的prefixCls配置。三者各司其职配合使用即可在不动业务代码的前提下完成多时区、RTL、多语言与组件默认行为的统一治理。【免费下载链接】semi-designA modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.Provide 3000 Design Tokens, easy to build your design system. Make Semi Design to Any Design. Design to Code in one click项目地址: https://gitcode.com/gh_mirrors/se/semi-design创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考