如何启用React并发模式

152 阅读6分钟

在 egghead.io 上观看 "启用 React 并发模式"(我的 "使用暂停来简化你的异步 UI"课程的一部分)。

React新的并发模式刚刚在实验发布频道发布。 这是多年研究的结果,而且显示了这一点。如果你想了解更多关于它为什么这么酷,一定要看Dan Abramov在JSIceland的演讲。 人们已经开始使用它,并看到一些很好的perf wins开箱即用。

综上所述,请记住这是实验性的。实验性的发布渠道并不支持semver(所以依赖它的代码可能会意外中断),而且肯定会有bug。但早期的实验对许多人来说是有希望的,我建议你在自己的应用程序中尝试一下。

步骤1

把它安装好。

首先,要启用并发模式,你需要有一个支持该模式的React版本。在写这篇文章的时候,React和React DOM的版本是16.11.0 ,不支持并发模式。所以我们需要安装experimental 版本。

npm install react@experimental react-dom@experimental
# or: yarn add react@experimental react-dom@experimental

第2步

确保你的应用程序可以工作,而不需要改变任何其他东西。

运行你的应用程序,运行你的构建,运行你的测试/类型检查。如果控制台中出现了以前没有的错误,那么这些可能是React的bug,你应该尝试做一个最小的重现(最好是在codesandbox中),并在React repo上开一个新问题

通常我们会跳过这一步,但我认为重要的是,如果有问题,你要确保知道这些问题是从哪一步开始的!我想说的是,这是一个很好的建议😉

第3步

启用并发模式。

在你的项目的入口文件中,你可能有像这样的东西:

import * as React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

const rootEl = document.getElementById('root')
ReactDOM.render(<App />, rootEl)

要启用并发模式,你将使用一个新的createRoot API(注意unstable_ 的前缀):

import * as React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

const rootEl = document.getElementById('root')
// ReactDOM.render(<App />, rootEl)
const root = ReactDOM.unstable_createRoot(rootEl)
root.render(<App />)

就是这样。

第4步

确保你的应用程序可以工作,而不需要改变任何其他东西。

运行你的应用程序,运行你的构建,运行你的测试/类型检查。如果控制台中出现了以前没有的错误,那么这些可能是React的bug,你应该尝试做一个最小的复制(最好是在codesandbox中),并在React repo上开一个新问题

如果这看起来很熟悉,那是因为我从第2步复制/粘贴了它 😂

然而,在这种情况下,如果事情被破坏了,或者你在控制台有新的错误。这可能是因为你的应用程序中的代码使用了并发模式不支持的功能(如String Refs,Legacy Context,或findDOMNode )。

另外请注意,所有带有unsafe_ 前缀的生命周期方法现在实际上都是不安全的,你使用这些方法会遇到错误。

第5步

试用并发模式。并发模式对我们来说有两个主要的功能:

  1. 时间切分
  2. 暂停一切异步的工作

如果你的应用中有一些用户交互,你知道它很慢,那就试试吧,如果它不那么糟糕了,那就是时间切分在起作用(观看上面链接的Dan的演讲,了解更多这方面的内容)。

你可以尝试将你的一个异步交互重构为悬念,或者只是尝试在你的应用中的某个地方添加这个。

function SuspenseDemo() {
  const [greetingResource, setGreetingResource] = React.useState(null)
  const [startTransition, isPending] = React.unstable_useTransition()

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.nameInput.value
    startTransition(() => {
      setGreetingResource(createGreetingResource(name))
    })
  }

  return (
    <div>
      <strong>Suspense Demo</strong>
      <form onSubmit={handleSubmit}>
        <label htmlFor="nameInput">Name</label>
        <input id="nameInput" />
        <button type="submit">Submit</button>
      </form>
      <ErrorBoundary>
        <React.Suspense fallback={<p>loading greeting</p>}>
          <Greeting greetingResource={greetingResource} isPending={isPending} />
        </React.Suspense>
      </ErrorBoundary>
    </div>
  )
}

function Greeting({greetingResource, isPending}) {
  return (
    <p style={{opacity: isPending ? 0.4 : 1}}>
      {greetingResource ? greetingResource.read() : 'Please submit a name'}
    </p>
  )
}

// 🐨 make this function do something else. Like an HTTP request or something
function getGreeting(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Hello ${name}!`)
      // 🐨 try rejecting instead... (make sure to comment out the resolve call)
      // reject(new Error(`Oh no. Could not load greeting for ${name}`))
    }, 1500) // 🐨 play with this number a bit
  })
}

// 🚨 This should NOT be copy/pasted for production code and is only here
// for experimentation purposes. The API for suspense (currently throwing a
// promise) is likely to change before suspense is officially released.
function createGreetingResource(name) {
  let status = 'pending'
  let result
  let suspender = getGreeting(name).then(
    greeting => {
      status = 'success'
      result = greeting
    },
    error => {
      status = 'error'
      result = error
    },
  )
  return {
    read() {
      if (status === 'pending') throw suspender
      if (status === 'error') throw result
      if (status === 'success') return result
    },
  }
}

class ErrorBoundary extends React.Component {
  state = {error: null}
  static getDerivedStateFromError(error) {
    return {error}
  }
  componentDidCatch() {
    // log the error to the server
  }
  tryAgain = () => this.setState({error: null})
  render() {
    return this.state.error ? (
      <div>
        There was an error. <button onClick={this.tryAgain}>try again</button>
        <pre style={{whiteSpace: 'normal'}}>{this.state.error.message}</pre>
      </div>
    ) : (
      this.props.children
    )
  }
}

在 codesandbox 上玩这个,而不是

我发现的一点是,suspense API是相当低级的,所以需要大量的代码来使其良好运行。但很酷的是,这些都是原子性的功能,在一个抽象中工作得非常好,可以很容易地共享。因此,一旦你得到了这个抽象,你就是金子。这是很好的。

第6步

撤销你所有的修改。

重新安装你之前安装的最后一个稳定版本,并恢复你之前的旧ReactDOM.render 。并发模式是实验性的,即使看起来没有问题,像React这样的基础性的实验性软件的运输是不明智的。React文档甚至建议,根据你的应用程序的大小和你所使用的第三方库,你可能永远无法运送并发模式(Facebook目前没有计划在旧Facebook.com上启用并发模式)。

还请记住,我们作为一个社区刚刚开始玩这个东西,所以在不同方法的权衡方面仍然有很多未知数。这是一个令人兴奋的时刻。但如果你重视稳定性,那么也许可以暂时假装并发模式和悬念不存在。

第7步

启用严格模式。

没有通过严格模式的应用程序不可能在并发模式下运行良好。 因此,如果你想努力在你的应用程序上启用并发模式,那么就启用严格模式。严格模式的一个好处是(与并发模式不同),它是可以逐步采用的。因此,你可以只将严格模式应用于你的代码库中你知道符合要求的部分,然后随着时间的推移迭代到完全支持。

请在我的博客上阅读更多信息:如何启用 React 严格模式

总结

我真的很期待数据获取的并发模式和暂停的稳定发布。当框架和库利用这些新功能时,将会更酷。就像React Hooks对React生态系统的影响一样,我认为并发模式对开发者的体验和终端用户的影响更大。

享受实验吧