如何在 React.js 中解决 FOUC

How to resolve FOUC in React.js

我已经通过 create-react-app 构建了 react.js 站点。 但是在生产模式下,有 FOUC 因为样式是在 html 渲染之后加载的。

有什么办法可以解决吗?我一直在寻找 google 的答案,但还没有找到合适的答案。

FOUC

FOUC - 所谓的无样式内容的闪现 可能与解决此问题的许多尝试一样问题重重。

进入正题

让我们考虑以下路由配置 (react-router):

...
<PageLayout>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/example' component={Example} />
  <Switch>
</PageLayout>
...

其中 PageLayout 是一个简单的 hoc,包含 div 包装器和 page-layout class 并返回它的子项。

下面我们重点介绍基于路由的组件渲染。通常你会使用 as component prop a React Compoment。但在我们的例子中,我们需要动态获取它,以应用有助于我们避免 FOUC 的功能。所以我们的代码将如下所示:

import asyncRoute from './asyncRoute'

const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))

...

<PageLayout>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/example' component={Example} />
  <Switch>
</PageLayout>

...

为了清楚起见,我们还展示一下 asyncRoute.js 模块的外观:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

import Loader from 'components/Loader'

class AsyncImport extends Component {
  static propTypes = {
    load: PropTypes.func.isRequired,
    children: PropTypes.node.isRequired
  }

  state = {
    component: null
  }

  toggleFoucClass () {
    const root = document.getElementById('react-app')
    if (root.hasClass('fouc')) {
      root.removeClass('fouc')
    } else {
      root.addClass('fouc')
    }
  }

  componentWillMount () {
    this.toggleFoucClass()
  }

  componentDidMount () {
    this.props.load()
      .then((component) => {
        setTimeout(() => this.toggleFoucClass(), 0)
        this.setState(() => ({
          component: component.default
        }))
      })
  }

  render () {
    return this.props.children(this.state.component)
  }
}

const asyncRoute = (importFunc) =>
  (props) => (
    <AsyncImport load={importFunc}>
      {(Component) => {
        return Component === null
          ? <Loader loading />
          : <Component {...props} />
      }}
    </AsyncImport>
  )

export default asyncRoute

hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.

Loader is a custom component which shows spinner.

为什么 setTimeout

只是因为我们需要在第二个刻度中删除 fouc class。否则它会像渲染组件一样发生。所以不行。

正如您在 AsyncImport 组件中看到的,我们通过添加 fouc class 修改了 React 根容器。所以 HTML 为了清楚起见:

<html lang="en">
<head></head>
<body>
  <div id="react-app"></div>
</body>
</html>

还有另一块拼图:

#react-app.fouc
    .page-layout *
        visibility: hidden

sass 在导入特定组件(即:HomeExample)时应用。

为什么不 display: none

因为我们希望所有依赖父级宽度、高度或任何其他 css 规则的组件都能正确呈现。

它是如何工作的?

主要假设是隐藏所有元素,直到组件准备好向我们展示呈现的内容。首先它触发 asyncRoute 函数,向我们显示 Loader 直到 Component 挂载和渲染。与此同时,在 AsyncImport 中,我们通过在反应根 DOM 元素上使用 class fouc 来切换内容的可见性。当一切加载完毕,就到了显示一切的时候了,所以我们删除了 class.

希望对您有所帮助!

感谢

这个article, which idea of dynamic import has been taken (I think) from react-loadable

来源

https://turkus.github.io/2018/06/06/fouc-react/