首页 > 解决方案 > 我可以强制水合物向元素添加属性吗?

问题描述

我有以下组件在服务器(SSR)和浏览器中呈现时处理不同的事情。

它不会为元素分配 ID 以“修复”古怪的浏览器页内锚导航(由 JS 处理)。

问题是当应用程序被水合时没有添加 ID。有没有办法强制水合物添加ID?

class Heading extends Component<HeadingProps, HeadingState> {
  constructor(props: HeadingProps) {
    super(props)
    this.state = {
      hover: false,
    }
  }
  render() {
    let _slug = slug(reactNodeToString(this.props.children))
    if (typeof window !== "undefined") {
      return React.createElement(
        `h${this.props.level}`,
        {
          id: _slug,
          style: {
            position: "relative",
          },
          onMouseEnter: () => {
            this.setState({
              hover: true,
            })
          },
          onMouseLeave: () => {
            this.setState({
              hover: false,
            })
          },
        },
        <Fragment>
          <HeadingAnchor show={this.state.hover}>
            <Anchor
              story={this.props.story}
              hashLinkOffset={this.props.hashLinkOffset}
              href={`#${_slug}`}
              title={`#${_slug}`}
              className="heading-anchor"
            >
              {[
                <Fragment key={`#${_slug}`}>
                  <Icon icon={["fas", "link"]} size="xs" />
                </Fragment>,
              ]}
            </Anchor>
          </HeadingAnchor>
          {this.props.children}
        </Fragment>
      )
    } else {
      // SSR-mode
      return React.createElement(
        `h${this.props.level}`,
        this.props.children
      )
    }
  }
}

标签: reactjs

解决方案


嗯……是的,不是的。对于第一次渲染,不,你不能。原因在 React docs about hydration 中有解释:

React 期望服务器和客户端之间呈现的内容是相同的。它可以修补文本内容的差异,但您应该将不匹配视为错误并修复它们。在开发模式下,React 会警告水合期间的不匹配。无法保证在不匹配的情况下会修补属性差异。出于性能原因,这很重要,因为在大多数应用程序中,不匹配的情况很少见,因此验证所有标记的成本非常高。

https://reactjs.org/docs/react-dom.html#hydra


所以基本上,React hydration 期望在客户端中被 hydration 的内容是完全相同的。它会尝试修补一些东西,但在这件事上,属性大多被忽略了。

那你怎么解决?状态。

你不应该在你的函数中使用typeof window检查。render相反,请使用一些状态变量,例如isHydratedcomponentDidMount阶段中设置。

像这样,在你的类的精简版本中:

type HeadingProps = {
  level: number;
  children: React.ReactNode;
}

type HeadingState = {
  isHydrated: boolean;
}

class Heading extends Component<HeadingProps, HeadingState> {
  constructor(props: HeadingProps) {
    super(props)
    this.state = {
      isHydrated: false
    }
  }

  componentDidMount() {
    this.setState({
      isHydrated: true
    })
  }

  render() {
    if (this.state.isHydrated) {
      return React.createElement(
        `h${this.props.level}`,
        {
          id: "hydrated",
        },
        this.props.children
      )
    } else {
      // SSR-mode
      return React.createElement(
        `h${this.props.level}`,
        this.props.children
      )
    }
  }
}

钩子版本将是setStateuseEffect. componentDidMount并且useEffect仅称为客户端。


推荐阅读