解决 Ant Design V5 样式兼容问题

524 阅读5分钟

引言

Ant Design V5 是截止目前 Ant Design 组件库最新版本,我们在业务场景已经开始应用,但在近期的测试中发现 Ant Design 组件无法在 360 极速浏览器(极速模式)下显示组件样式。经过查看样式和查阅官方文档, Ant Design V5 组件的样式中大量使用了 :where() 选择器来降低选择器的优先级,以此减少开发者升级组件库时额外调整自定义样式的成本。:where() 对于 Chrome 仅支持 Chrome 88 以上,截止目前 360 极速浏览器的 Chrome 版本为 86,因此无法显示组件样式。

参考

  1. 浅谈CSS逻辑选择器 is、where、not、has
  2. :where() - MDN

对于此样式兼容问题,主要解决思路如下:

  1. 参照官方样式兼容文档,使用其提供的降级方案,移除掉 :where() 选择器。主要是通过 Context 为组件传入配置,关闭降低样式优先级的配置,以此移除 :where() 选择器。但针一些无法接收 Context 的场景,例如 Message、Modal 和 Notification 等组件的静态调用形式,仍然会使用 :where() 选择器。
  2. 对于第一点产生的问题,如果要在组件中使用 Message、Modal 和 Notification 组件,需要避免静态调用形式,采用官方推荐的 Hook 调用形式,可以通过 App 包裹组件完成此操作。
  3. 但 Hook 调用形式只能在组件使用,业务场景上常常需要在非组件内调用相关,比较典型就是在请求的响应拦截器中调用 Message 实现错误提示。对于在非组件调用可以通过类 Redux 数据全局共享与通信方案,在相关逻辑中(例如响应拦截器中)通知组件调用 Message ,从而实现此需求。

通过降级方案移除 :where() 选择器

入口文件:

import ReactDOM from "react-dom/client";
import {App} from "antd";
import {StyleProvider, legacyLogicalPropertiesTransformer} from '@ant-design/cssinjs';
import "./index.css";


// 其他无关代码已省略...




ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}> /* 关闭降权操作,移除 :where() 选择器 /
<App> / Ant Design 提供的包裹组件,用于使得 Message、Modal、Notification 等组件的静态调用形式能获取到上下文,从而读取移除 :where() 选择器的配置  */
<RouterProvider router={router}/>
</App>
</StyleProvider>
);

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( <StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}> /* 关闭降权操作,移除 :where() 选择器 / <App> / Ant Design 提供的包裹组件,用于使得 Message、Modal、Notification 等组件的静态调用形式能获取到上下文,从而读取移除 :where() 选择器的配置 */ <RouterProvider router={router}/> </App> </StyleProvider> );

组件内静态调用 Message、Modal、Notification 等组件的方式:

import {FC, useEffect} from "react";
import {App} from "antd";



              const Main: FC = () =&gt; {
                const {message, modal} = App.useApp();
                
                  useEffect(()=&gt;{
                      message.success("成功");
                        },[])
                        
                          return (
                              &lt;div&gt;
                                  &lt;/div&gt;
                                    );
                                    };
                                    
                                    export default Main;
                                    </code></pre><h2 id="item-0-3">通过数据通信机制解决非组件内调用 Message 等组件</h2><h3 id="item-0-4">通过 Redux 解决</h3><p>以在 Axios 响应拦截器内使用 Message 组件完成错误提示为例,其他 Modal、Notification 组件使用方式同理。<br>依赖:react-redux、@reduxjs/toolkit</p><p><strong>MessageAlert 组件 - 用于通知此组件触发相关的消息通知:</strong></p><pre><code class="jsx">import {useEffect} from "react";
                                    import {App} from "antd";
                                    import {useSelector} from "react-redux";
                                    import {RootState} from "@/store";
                                    
                                    function MessageAlert() {
                                      const {message: messageApi} = App.useApp();
                                        const messageInfo = useSelector((state: RootState) =&gt; state.global.messageInfo);
                                          useEffect(() =&gt; {
                                              const {type, content} = messageInfo;
                                                  if (messageApi &amp;&amp; content) {
                                                        switch (type) {
                                                                case "success":
                                                                          messageApi.success(content);
                                                                                    break;
                                                                                            case "error":
                                                                                                      messageApi.error(content);
                                                                                                                break;
                                                                                                                        case "warning":
                                                                                                                                  messageApi.warning(content);
                                                                                                                                            break;
                                                                                                                                                    case "info":
                                                                                                                                                              messageApi.info(content);
                                                                                                                                                                        break;
                                                                                                                                                                                default:
                                                                                                                                                                                          messageApi.error(content);
                                                                                                                                                                                                    break;
                                                                                                                                                                                                          }
                                                                                                                                                                                                              }
                                                                                                                                                                                                                }, [messageApi, messageInfo]);
                                                                                                                                                                                                                
                                                                                                                                                                                                                  return null;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                                  
                                                                                                                                                                                                                  export default MessageAlert;</code></pre><p><strong>入口文件:</strong></p><pre><code class="jsx">import ReactDOM from "react-dom/client";
                                                                                                                                                                                                                  import {App} from "antd";
                                                                                                                                                                                                                  import {StyleProvider, legacyLogicalPropertiesTransformer} from '@ant-design/cssinjs';
                                                                                                                                                                                                                  import MessageAlert from "@/components/MessageAlert";
                                                                                                                                                                                                                  import "./index.css";
                                                                                                                                                                                                                  
                                                                                                                                                                                                                  // 其他无关代码已省略...
                                                                                                                                                                                                                  
                                                                                                                                                                                                                  ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
                                                                                                                                                                                                                    &lt;StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}&gt; /* 关闭降权操作,移除 :where() 选择器 */
                                                                                                                                                                                                                        &lt;App&gt; /* Ant Design 提供的包裹组件,用于使得 Message、Modal、Notification 等组件的静态调用形式能获取到上下文,从而顺利读取移除 :where() 选择器的配置  */
                                                                                                                                                                                                                              &lt;RouterProvider router={router}/&gt;
                                                                                                                                                                                                                                    &lt;MessageAlert&gt;&lt;/MessageAlert&gt; /* 消息提示组件,用于在非组件内通过数据通信方式实现调用。 */
                                                                                                                                                                                                                                        &lt;/App&gt;
                                                                                                                                                                                                                                          &lt;/StyleProvider&gt;
                                                                                                                                                                                                                                          );</code></pre><p><strong>Redux Store 配置</strong><br><strong>store/global.ts</strong></p><pre><code class="jsx">import {createSlice} from '@reduxjs/toolkit';
                                                                                                                                                                                                                                          import type {PayloadAction} from '@reduxjs/toolkit';
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          export type MessageInfoType = {
                                                                                                                                                                                                                                            type: 'success' | 'error' | 'info' | 'warning';
                                                                                                                                                                                                                                              content?: string;
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                              export interface GlobalState {
                                                                                                                                                                                                                                                messageInfo: MessageInfoType;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                const initialState: GlobalState = {
                                                                                                                                                                                                                                                  messageInfo: {
                                                                                                                                                                                                                                                      type: 'success',
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                        };
                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                        export const globalSlice = createSlice({
                                                                                                                                                                                                                                                          name: 'global',
                                                                                                                                                                                                                                                            initialState,
                                                                                                                                                                                                                                                              reducers: {
                                                                                                                                                                                                                                                                  saveMessageInfo: (state, action: PayloadAction&lt;MessageInfoType&gt;) =&gt; {
                                                                                                                                                                                                                                                                        state.messageInfo = {...action.payload};
                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                              },
                                                                                                                                                                                                                                                                              });
                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                              export const {saveMessageInfo} = globalSlice.actions;
                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                              export default globalSlice.reducer;</code></pre><p><strong>store/index.ts</strong></p><pre><code class="jsx">import {configureStore} from '@reduxjs/toolkit';
                                                                                                                                                                                                                                                                              import globalReducer from './global';
                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                              export const store = configureStore({
                                                                                                                                                                                                                                                                                reducer: {
                                                                                                                                                                                                                                                                                    global: globalReducer,
                                                                                                                                                                                                                                                                                      },
                                                                                                                                                                                                                                                                                      });
                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                      export type RootState = ReturnType&lt;typeof store.getState&gt;
                                                                                                                                                                                                                                                                                      export type AppDispatch = typeof store.dispatch</code></pre><p><strong>请求拦截器内调用相关 action 实现消息通知:</strong></p><pre><code class="jsx">import {store} from "@/store";
                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                      // 其他无关代码已省略...
                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                      axiosInstance.interceptors.response.use(
                                                                                                                                                                                                                                                                                        async (response) =&gt; {
                                                                                                                                                                                                                                                                                            const {data} = response;
                                                                                                                                                                                                                                                                                                const {code, msg} = data;
                                                                                                                                                                                                                                                                                                    if (code !== 0) {
                                                                                                                                                                                                                                                                                                          // 错误提示
                                                                                                                                                                                                                                                                                                                store.dispatch({
                                                                                                                                                                                                                                                                                                                        type: 'global/saveMessageInfo',
                                                                                                                                                                                                                                                                                                                                payload: {
                                                                                                                                                                                                                                                                                                                                          type: 'error',
                                                                                                                                                                                                                                                                                                                                                    content: msg || '服务端发生错误'
                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                  });
                                                                                                                                                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                                                                                                                                                          return response;
                                                                                                                                                                                                                                                                                                                                                                            },
                                                                                                                                                                                                                                                                                                                                                                              // ...
                                                                                                                                                                                                                                                                                                                                                                              );
                                                                                                                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                                                                                                              export default axiosInstance;</code></pre><h3 id="item-0-5">不引入 Redux(自行实现小型状态管理器)</h3><p>如果项目中不想额外引入 Redux,可以考虑自行实现小型状态管理器。</p><p><strong>store.ts - 适用于简单的数据共享场景的小型状态管理器</strong></p><pre><code class="jsx">export type MessageInfoType = {
                                                                                                                                                                                                                                                                                                                                                                                type: 'success' | 'error' | 'info' | 'warning';
                                                                                                                                                                                                                                                                                                                                                                                  content?: string;
                                                                                                                                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                                                                                                                                  type State = {
                                                                                                                                                                                                                                                                                                                                                                                    messageInfo: MessageInfoType;
                                                                                                                                                                                                                                                                                                                                                                                    };
                                                                                                                                                                                                                                                                                                                                                                                    type Listener = () =&gt; void;
                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                    class Store {
                                                                                                                                                                                                                                                                                                                                                                                      private state: State; // 用于存储状态
                                                                                                                                                                                                                                                                                                                                                                                        private listeners: Listener[]; // 用于存储所有组件对状态进行更新的函数
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                          constructor() {
                                                                                                                                                                                                                                                                                                                                                                                              this.state = {messageInfo: {type: 'success', content: ''}};
                                                                                                                                                                                                                                                                                                                                                                                                  this.listeners = [];
                                                                                                                                                                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                      // 获取状态
                                                                                                                                                                                                                                                                                                                                                                                                        getState(): State {
                                                                                                                                                                                                                                                                                                                                                                                                            return this.state;
                                                                                                                                                                                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                                                                                                                                                // 修改状态
                                                                                                                                                                                                                                                                                                                                                                                                                  setState(newState: State): void {
                                                                                                                                                                                                                                                                                                                                                                                                                      this.state = newState;
                                                                                                                                                                                                                                                                                                                                                                                                                          this.notify();
                                                                                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                              // 订阅状态
                                                                                                                                                                                                                                                                                                                                                                                                                                subscribe(listener: Listener): void {
                                                                                                                                                                                                                                                                                                                                                                                                                                    this.listeners.push(listener);
                                                                                                                                                                                                                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                        // 取消订阅
                                                                                                                                                                                                                                                                                                                                                                                                                                          unsubscribe(listener: Listener): void {
                                                                                                                                                                                                                                                                                                                                                                                                                                              this.listeners = this.listeners.filter((l) =&gt; l !== listener);
                                                                                                                                                                                                                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                  // 用于通知所有组件对状态进行更新
                                                                                                                                                                                                                                                                                                                                                                                                                                                    private notify(): void {
                                                                                                                                                                                                                                                                                                                                                                                                                                                        for (const listener of this.listeners) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                              listener();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    const store = new Store();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    export {store};</code></pre><p><strong>MessageAlert 组件 - 使用小型状态管理器</strong></p><pre><code class="jsx">import {useEffect, useState} from "react";
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    import {App} from "antd";
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    import {MessageInfoType, store} from '@/store/store';
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    function MessageAlert() {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                      const {message: messageApi} = App.useApp();
                                                                                                                                                                                                                                                                                                                                                                                                                                                                        const [messageInfo, setMessageInfo] = useState&lt;MessageInfoType&gt;(store.getState().messageInfo);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                          useEffect(() =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                              const handleMessageInfoChange = () =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    setMessageInfo(store.getState().messageInfo);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        };
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            store.subscribe(handleMessageInfoChange);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                return () =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      store.unsubscribe(handleMessageInfoChange);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          };
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            }, []);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              useEffect(() =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  const {type, content} = messageInfo;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      if (messageApi &amp;&amp; content) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            switch (type) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    case "success":
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              messageApi.success(content);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                case "error":
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          messageApi.error(content);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            case "warning":
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      messageApi.warning(content);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        case "info":
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  messageApi.info(content);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    default:
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              messageApi.error(content);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        break;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    }, [messageApi, messageInfo]);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      return null;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      export default MessageAlert;</code></pre><p><strong>请求拦截器配置</strong></p><pre><code class="jsx">import {store} from "@/store.ts";
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      // 其他无关代码已省略...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      axiosInstance.interceptors.response.use(
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        async (response) =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            const {data} = response;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                const {code, msg} = data;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    if (code !== 0) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 错误提示
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                store.setState({messageInfo: {type: 'error', content: msg || '服务端发生错误'}});
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        return response;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          },
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            // ...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            );
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            export default axiosInstance;</code></pre>