IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    解决 Ant Design V5 样式兼容问题

    hwjfqr发表于 2023-07-06 14:10:15
    love 0

    引言

    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>
    );

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

    import {FC, useEffect} from "react";
    import {App} from "antd";
    
    const Main: FC = () => {
      const {message, modal} = App.useApp();
    
      useEffect(()=>{
        message.success("成功");
      },[])
    
      return (
        <div>
        </div>
      );
    };
    
    export default Main;
    

    通过数据通信机制解决非组件内调用 Message 等组件

    通过 Redux 解决

    以在 Axios 响应拦截器内使用 Message 组件完成错误提示为例,其他 Modal、Notification 组件使用方式同理。
    依赖:react-redux、@reduxjs/toolkit

    MessageAlert 组件 - 用于通知此组件触发相关的消息通知:

    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) => state.global.messageInfo);
      useEffect(() => {
        const {type, content} = messageInfo;
        if (messageApi && 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;

    入口文件:

    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(
      <StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}> /* 关闭降权操作,移除 :where() 选择器 */
        <App> /* Ant Design 提供的包裹组件,用于使得 Message、Modal、Notification 等组件的静态调用形式能获取到上下文,从而顺利读取移除 :where() 选择器的配置  */
          <RouterProvider router={router}/>
          <MessageAlert></MessageAlert> /* 消息提示组件,用于在非组件内通过数据通信方式实现调用。 */
        </App>
      </StyleProvider>
    );

    Redux Store 配置
    store/global.ts

    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<MessageInfoType>) => {
          state.messageInfo = {...action.payload};
        }
      },
    });
    
    export const {saveMessageInfo} = globalSlice.actions;
    
    export default globalSlice.reducer;

    store/index.ts

    import {configureStore} from '@reduxjs/toolkit';
    import globalReducer from './global';
    
    export const store = configureStore({
      reducer: {
        global: globalReducer,
      },
    });
    
    export type RootState = ReturnType<typeof store.getState>
    export type AppDispatch = typeof store.dispatch

    请求拦截器内调用相关 action 实现消息通知:

    import {store} from "@/store";
    
    // 其他无关代码已省略...
    
    axiosInstance.interceptors.response.use(
      async (response) => {
        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;

    不引入 Redux(自行实现小型状态管理器)

    如果项目中不想额外引入 Redux,可以考虑自行实现小型状态管理器。

    store.ts - 适用于简单的数据共享场景的小型状态管理器

    export type MessageInfoType = {
      type: 'success' | 'error' | 'info' | 'warning';
      content?: string;
    }
    type State = {
      messageInfo: MessageInfoType;
    };
    type Listener = () => 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) => l !== listener);
      }
    
      // 用于通知所有组件对状态进行更新
      private notify(): void {
        for (const listener of this.listeners) {
          listener();
        }
      }
    }
    
    const store = new Store();
    
    export {store};

    MessageAlert 组件 - 使用小型状态管理器

    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<MessageInfoType>(store.getState().messageInfo);
    
      useEffect(() => {
        const handleMessageInfoChange = () => {
          setMessageInfo(store.getState().messageInfo);
        };
        store.subscribe(handleMessageInfoChange);
        return () => {
          store.unsubscribe(handleMessageInfoChange);
        };
      }, []);
    
      useEffect(() => {
        const {type, content} = messageInfo;
        if (messageApi && 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;

    请求拦截器配置

    import {store} from "@/store.ts";
    
    // 其他无关代码已省略...
    
    axiosInstance.interceptors.response.use(
      async (response) => {
        const {data} = response;
        const {code, msg} = data;
        if (code !== 0) {
          // 错误提示
          store.setState({messageInfo: {type: 'error', content: msg || '服务端发生错误'}});
        }
        return response;
      },
      // ...
    );
    
    export default axiosInstance;


沪ICP备19023445号-2号
友情链接