1

我已经从 create-react-app 构建了 react.js 站点。但是在生产模式下,存在 FOUC,因为样式是在呈现 html 之后加载的。

有没有办法解决这个问题?我一直在谷歌搜索答案,但还没有找到合适的答案。

4

1 回答 1

7

FOUC

FOUC - 所谓的 Flash of Unstyled Content可能与解决此问题的许多尝试一样非常成问题。

切中要害

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

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

wherePageLayout是一个简单的hoc,包含带有类的 div 包装器page-layout并返回它的子级。

现在,让我们关注基于路由的组件渲染。通常你会使用component一个 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是对 DOM 类属性进行操作的 polyfill。

Loader是一个显示微调器的自定义组件。

为什么setTimeout

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

正如您在组件中看到的,我们通过添加类AsyncImport来修改反应根容器。fouc所以为了清楚起见,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挂载和渲染的函数。同时,我们通过在 react 根 DOM 元素上AsyncImport使用一个类来切换内容的可见性。fouc当所有内容加载完毕后,就该显示所有内容了,因此我们删除了该类。

希望有帮助!

谢谢

这篇文章,动态导入的想法(我认为)来自react-loadable

资源

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

于 2018-06-06T19:01:48.880 回答