2026/1/14 9:22:04
网站建设
项目流程
phpmysql网站开发全程实例 第2版,求个网站2021,网站的跳出率,搜索引擎和门户网站的区别问题
因为之前 Next 和 React 接连出现安全问题#xff0c;于是把博客的依赖升级了一下#xff0c;没想到就搞出问题了#xff0c;如下图所示#xff1a;初次渲染时样式丢失#xff0c;在客户端上会短暂展示 Antd 组件无样式界面#xff0c;出现样式闪烁的情况。项目是 N…问题因为之前 Next 和 React 接连出现安全问题于是把博客的依赖升级了一下没想到就搞出问题了如下图所示初次渲染时样式丢失在客户端上会短暂展示 Antd 组件无样式界面出现样式闪烁的情况。项目是 Next 14React 18 的 App Router 项目依赖版本ant-design/nextjs-registry: ^1.3.0antd: ^5.14.2。解决思路因为 Antd 是 CSS-in-js 的 UI 库按照官方文档呢我们需要一个 ant-design/nextjs-registry 包裹整个页面在 SSR 时收集所有组件的样式并且通过script标签在客户端首次渲染时带上。/* by 01130.hk - online tools website : 01130.hk/zh/chaodai.html */ // src/app/layout.tsx import { AntdRegistry } from ant-design/nextjs-registry export default async function RootLayout({ children }: Readonly{ children: React.ReactNode }) { return ( html langen head {/* ... */} /head body AntdRegistry {/* ... 假装这是页面代码 */} /AntdRegistry /body /html ) }对照了一下官方文档也问了下 AI没发现我的写法有什么问题。就在这个时候我猛然间看见了 Antd 的 Pages Router 使用的注意事项我寻思可能我遇到的情况和这里一样是内部依赖版本ant-design/cssinj不对引起的。输入npm ls ant-design/cssinjs看了一下/* by 01130.hk - online tools website : 01130.hk/zh/chaodai.html */ ├─┬ ant-design/nextjs-registry1.3.0 │ └── ant-design/cssinjs2.0.1 └─┬ antd5.14.2 └── ant-design/cssinjs1.24.0 dedupedant-design/nextjs-registry内部也使用了ant-design/cssinjs而且它的版本和antd内置版本还不一样这就是问题的所在了。接下来把ant-design/nextjs-registry的版本降到了 1.2.0这时候版本对上了bug 也就修复了。├─┬ ant-design/nextjs-registry1.2.0 │ └── ant-design/cssinjs1.24.0 └─┬ antd5.14.2 └── ant-design/cssinjs1.24.0 dedupedant-design/nextjs-registry 的内部发生了什么AntdRegistry这勾起了我的好奇心就让我们来看看ant-design/nextjs-registry干了些什么https://github.com/ant-design/nextjs-registry// /src/AntdRegistry.tsx use client; import type { StyleProviderProps } from ant-design/cssinjs; import type { FC } from react; import { createCache, extractStyle, StyleProvider } from ant-design/cssinjs; import { useServerInsertedHTML } from next/navigation; import React, { useState } from react; type AntdRegistryProps OmitStyleProviderProps, cache; const AntdRegistry: FCAntdRegistryProps (props) { const [cache] useState(() createCache()); useServerInsertedHTML(() { const styleText extractStyle(cache, { plain: true, once: true }); if (styleText.includes(.data-ant-cssinjs-cache-path{content:;})) { return null; } return ( style idantd-cssinjs // to make sure this style is inserted before Ant Designs style generated by client >ant-design/cssinjs首先来看上文const [cache] useState(() createCache())这一行。ant-design/cssinjs 部分仓库在 https://github.com/ant-design/cssinjs它干了几件事生成唯一实例 ID。仅客户端将 body 中的样式移到 head 中并且去重。export function createCache() { const cssinjsInstanceId Math.random().toString(12).slice(2); // Tricky SSR: Move all inline style to the head. // PS: We do not recommend tricky mode. if (typeof document ! undefined document.head document.body) { const styles document.body.querySelectorAll(style[${ATTR_MARK}]) || []; const { firstChild } document.head; Array.from(styles).forEach((style) { (style as any)[CSS_IN_JS_INSTANCE] (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId; // Not force move if no head if ((style as any)[CSS_IN_JS_INSTANCE] cssinjsInstanceId) { document.head.insertBefore(style, firstChild); } }); // Deduplicate of moved styles const styleHash: Recordstring, boolean {}; Array.from(document.querySelectorAll(style[${ATTR_MARK}])).forEach( (style) { const hash style.getAttribute(ATTR_MARK)!; if (styleHash[hash]) { if ((style as any)[CSS_IN_JS_INSTANCE] cssinjsInstanceId) { style.parentNode?.removeChild(style); } } else { styleHash[hash] true; } }, ); } return new CacheEntity(cssinjsInstanceId); }返回一个类包裹的Map结构在StyleProvider中由后代组件把首屏所需样式传回。结构如下所示export type KeyType string | number; type ValueType [number, any]; /** Connect key with SPLIT */ export declare function pathKey(keys: KeyType[]): string; declare class Entity { instanceId: string; constructor(instanceId: string); /** private Internal cache map. Do not access this directly */ cache: Mapstring, ValueType; extracted: Setstring; get(keys: KeyType[]): ValueType | null; /** A fast get cache with get concat. */ opGet(keyPathStr: string): ValueType | null; update(keys: KeyType[], valueFn: (origin: ValueType | null) ValueType | null): void; /** A fast get cache with get concat. */ opUpdate(keyPathStr: string, valueFn: (origin: ValueType | null) ValueType | null): void; } export default Entity;至于StyleProvider除了整合上层StyleProvider注入的样式外它基本上是一个普通的Context.Provider作用也很好猜把createCache返回的Map结构注入到下层组件中。const StyleContext React.createContextStyleContextProps({ hashPriority: low, cache: createCache(), defaultCache: true, autoPrefix: false, }) export const StyleProvider: React.FCStyleProviderProps (props) { // ... return ( StyleContext.Provider value{context}{children}/StyleContext.Provider ); };Antd 组件的调用路径具体源码就不细看了以按钮组件 Button 为例调用路径大致如下flowchart TD subgraph CSSInJS 底层机制 genStyleUtils[ant-design/cssinjs-utilsbr/genStyleUtils] genStyleHooks[ant-design/cssinjs-utilsbr/genStyleHooks] genComponentStyleHook[ant-design/cssinjs-utilsbr/genComponentStyleHook] useStyleRegister[ant-design/cssinjsbr/useStyleRegister] useGlobalCache[ant-design/cssinjsbr/useGlobalCache] end subgraph Antd 组件层 useStyleAntd[useStyle] Button[Button组件] JSX[写入到JSX并返回] end genStyleUtils --|生成| genStyleHooks genStyleHooks --|调用| genComponentStyleHook genComponentStyleHook --|调用| useStyleRegister useStyleRegister --|调用| useGlobalCache genStyleHooks --|返回| useStyleAntd Button --|调用| useStyleAntd useStyleAntd --|样式注入| JSX在 useGlobalCache 中 调用React.useContext(StyleContext)的cache.onUpdate方法更新缓存。总结这次碰到的问题其实挺典型的升级了依赖结果页面出问题了。解决方法很简单——把 ant-design/nextjs-registry 从 1.3.0 降级到 1.2.0让它跟 antd 用的 ant-design/cssinjs 内部版本对上就行了。以后要是用 Next.js App Router 配 Ant Design 遇到类似情况可以先看看这两个包的版本是不是兼容。有时候问题没看起来那么复杂可能就是版本没对上。出于好奇我还顺便看了一下 AntdRegistry 内部的实现——发现它主要是通过StyleProvider在服务端收集样式然后通过useServerInsertedHTML在客户端首次渲染时注入到style标签中这样就能避免样式闪烁的问题。大家的阅读是我发帖的动力本文首发于我的博客deer.shika-blog.xyz欢迎大家来玩喵 转载请注明出处。