React 服务端渲染后,客户端再次渲染,div 错位

React 16,server render 的时候遇见一个问题,

  • 页面内容:div1,div2,div3 三个从上往下
  • 服务端渲染逻辑,div2,div3 (div1 判断是服务端时,返回空 null)
  • 网页端渲染逻辑,div1,div2,div3 (div1 判断是网页端时,返回正常 html)

实际上看到的,

  • div1 的内容 + div2 的样式 混合在一起,
  • div2 的内容 + div3 的样式,混合在一起
  • div3 的内容 + div3 的样式,又出现了一次

问了一下 AI,就是服务端和客户端渲染的逻辑不一致,这个在 react 中,有个专业术语:hydrate 水合

当使用 ReactDOM.hydrate 时,如果服务端和客户端的渲染结果不一致,可能会导致一些意外的行为。例如,如果服务端根据某些条件渲染了一个 div,而在客户端这些条件不满足,导致这个 div 不存在,那么客户端在激活时可能会出现错位或混在一起渲染的问题。

为了解决这个问题,可以采取以下几种方法:

1. 确保 服务端和客户端 条件一致

确保服务端和客户端的渲染条件完全一致。这意味着在服务端和客户端使用相同的逻辑和状态来决定是否渲染某个组件。

javascript 复制代码
// 服务端和客户端共享的逻辑
const shouldRenderDiv = checkSomeCondition();

const App = () => (
  <div>
    {shouldRenderDiv && <div>Conditional Div</div>}
    <div>Another Div</div>
  </div>
);

2. 使用占位符

如果某个组件可能被渲染为 null,可以在服务端和客户端都渲染一个占位符元素,以确保结构一致。

javascript 复制代码
const App = () => (
  <div>
    <div style={{ display: shouldRenderDiv ? 'block' : 'none' }}>
      Conditional Div
    </div>
    <div>Another Div</div>
  </div>
);

这样,即使 Conditional Div 不被渲染,占位符元素仍然存在,确保了结构的一致性。

3. 使用 key 属性

在 React 中,使用 key 属性可以帮助 React 更好地识别和管理列表中的元素。同样,在条件渲染的组件中,使用 key 属性也可以帮助 React 更好地匹配和激活组件。

javascript 复制代码
const App = () => (
  <div>
    {shouldRenderDiv && <div key="conditional">Conditional Div</div>}
    <div key="another">Another Div</div>
  </div>
);

4. 检查并清理 DOM

在客户端激活之前,可以检查并清理 DOM,以确保其结构与 React 组件树一致。

javascript 复制代码
const rootElement = document.getElementById('root');

// 清理 rootElement 中的多余节点
while (rootElement.firstChild) {
  rootElement.removeChild(rootElement.firstChild);
}

// 然后使用 ReactDOM.hydrate
ReactDOM.hydrate(<App />, rootElement);

5. 使用 Error Boundaries

如果由于某些原因导致渲染结果不一致,可以使用 React 的 Error Boundaries 来捕获并处理错误。

javascript 复制代码
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 处理错误
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

const App = () => (
  <ErrorBoundary>
    <div>
      {shouldRenderDiv && <div>Conditional Div</div>}
      <div>Another Div</div>
    </div>
  </ErrorBoundary>
);

通过以上方法,可以尽量确保服务端和客户端的渲染结果一致,从而避免 ReactDOM.hydrate 时出现错位或混在一起渲染的问题。

扩展阅读

相关推荐
工呈士3 小时前
MobX与响应式编程实践
前端·react.js·面试
木木夕酱4 小时前
前端响应式网站编写套路
css·react.js
小李小李不讲道理4 小时前
「Ant Design 组件库探索」二:Button组件
前端·react.js·ant design
溪饱鱼7 小时前
React源码阅读-fiber核心构建原理
前端·javascript·react.js
mpr0xy9 小时前
React Router 中 navigate 后浏览器返回按钮不起作用的问题记录
javascript·react.js·浏览器·路由
JacksonGao10 小时前
React Fiber的调度算法你了解多少呢?
前端·react.js
白瓷梅子汤10 小时前
跟着官方示例学习 @tanStack-table --- Column Filters
前端·react.js
卸任11 小时前
Electron自制翻译工具:增加中英互译
前端·react.js·electron
谢尔登12 小时前
【React】useId
前端·javascript·react.js
NoneCoder12 小时前
Redux 实践与中间件应用
前端·react.js·中间件·面试