美文网首页
Hydrate 的认识

Hydrate 的认识

作者: 莫帆海氵 | 来源:发表于2020-12-29 00:50 被阅读0次

    关键词

    react react-dom hydrate renderToString

    它的作用是什么

    用于给 React 通过服务端渲染(Server Side Rendering)生成的已经包含完整 DOM 结构的页面提供 C 端渲染能力。

    Hydrate 主要做两件事 绑定事件 和 更新文字内容。

    它会尝试给已经生成的 DOM 结构绑定事件,C 端再次渲染时更新内容仅有文字会更新,不会渲染出和服务端生成的 DOM 结构有差异。

    按照文档的说法如果服务端生成的 DOM 结构和 C 端再次渲染的 DOM 结构有差异,它会抛出一个警告。

    使用示例

    一个简单的例子如下

    import React from 'react'
    import ReactDom from 'react-dom'
    
    class Abc() {
        constructor(props) {
            super(props)
            
            this.props = {
                name: undefined,
            }
        }
        render(){
            return <div>Hello <b>{this.props.name}</b>!</div>
        }
    }
    
    ReactDom.hydrate(
        <Lego
          name="Bob"
        />,
        global.document.getElementById("root")
    )
    

    使用问题记录

    问题1: 服务端生成的 dom 结构正常,在 C 端再次渲染后界面错乱,查看最终显示的 dom 结构发现原因是生成的结构错乱了,层级并不正确。

    正常服务端和 C 端内容一致的截图

    ssr_p1.png

    服务端和 C 端内容有差异的截图

    ssr_p2.png

    服务端生成的 DOM 结构截图

    ssr_p3.png

    问题如上面截图所示,当服务端的 DOM 结构缺少 “custom-title” 这个节点,而 C 端生成的 DOM 结构又包含 “cutom-title” 节点,就造成了这个问题。

    解决办法就是让 服务端 和 C 端生成的 DOM 结构保持一致

    问题2: 接着上一个问题,如果确实需要 C 端渲染出的内容和 ssr 的不一样,怎么做呢?

    If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.

    按照文档的说明,可以通过在组件的 componentDidMount 之后在去更改某个 state 的值,从而触发再次渲染,达到渲染不一样内容的目的,但这种用法有个不好的点,就是服务端生成的内容是先显示一遍,然后再次渲染一遍才能完成差异化的内容,这对于体验会不怎么友好,所以需要自己衡量来使用。

    import React from 'react'
    import Platform from 'some-define'
    
    class CustomTitle extends React.Component {
      constructor(props) {
        super(props)
    
        this.state = {
          classNameForSSR: "", // 为了 ssr 设置的属性
        }
      }
    
      componentDidMount() {
        if (this.props.isSSRFromClient) {
          // C 端渲染在重新 render,确保能获取到正确的平台相关的信息
          // 因为服务端和 C 端获取到 Platform 信息不一致,所以需要 C 端主动更新
          this.setState({
            classNameForSSR: Platform.type,
          })
        }
      }
    
      render() {
        return (
          <div
            className={`custom-title ${this.state.classNameForSSR}`}
          >
          </div>
        )
      }
    }
    
    export default CustomTitle
    

    假如有上面的一个组件,通过给他的 DOM 增加一个平台相关的样式名,以达到根据不同平台显示隐藏节点的功能。

    因为服务端的平台信息和 C 端的平台信息并不一致,造成他们的 DOM 结构并不一致,所以这里采用的 didMount 后再次更新组件来实现

    问题3: 怎么使用让体验更好,ssr 内容能更快的呈现给用户

    最好的体验就是通过服务端生成的 DOM 结构 C 端访问的时候直接呈现(C 端不会再次触发 render),只需要能绑定上对应事件即可。

    所以在服务端渲染的页面中 C 端需要尽可能快的处理数据,推荐在页面的 constructor 中处理,且尽可能一次处理完整个状态。这里注意区分不同环境过来,比如 isSSRFromServer = 在服务端渲染时候的标记,isSSRFromClient = C 端渲染时的标记,还要考虑正常流程的影响,一般都会在组件的 componentDidMount 中获取一些远程数据等,这里也需要抛开 ssr 环境下不在请求。

    ...
    constructor(props) {
        super(props)
    
        this.isSSRFromClient = props.render_data && !props.isSSRFromServer
    
        if (this.isSSRFromClient) {
            Reflux.initStore(Store).onGetPageCompleted({
              ...props.render_data,
              _params: {
                project_id: this.project_id,
                isSSRFromClient: this.isSSRFromClient,
                isSSRFromServer: props.isSSRFromServer,
              },
            })
        }
      }
      ...
    

    相关文章

      网友评论

          本文标题:Hydrate 的认识

          本文链接:https://www.haomeiwen.com/subject/tmpgwktx.html