React 16.6.0 新特性

Avatar of Kingsley Silas
Kingsley Silas

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

React 16.6.0 于 2018 年 10 月发布,它带来了一些新特性,使我们使用 React 进行开发的方式更加丰富。我们将介绍我认为其中最好的新特性,并举例说明如何在工作中使用它们。

React.memo() 避免不必要的重新渲染

在某些情况下,即使组件的状态和属性都没有改变,它也会重新渲染。这会导致性能问题,因为重新渲染是一个代价较高的操作。

以下是一个计数器的示例,用于说明我们正在讨论的内容

查看 CodePen 上的示例
没有使用 React.memo() 的 React 计数器
by CSS-Tricks (@css-tricks)
on CodePen.

我们有一个子组件,它接收一个作为属性传递的特定值,该值不会改变。

const Child = props => {
  console.log("rendered");
  return <React.Fragment>{props.name}</React.Fragment>;
}

子组件的值由 App 组件的状态决定。App 组件的状态没有改变,它的属性保持不变。

class App extends React.Component {
  state = {
    count: 1,
    name: "Jioke"
  };

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  render() {
    return (
      <React.Fragment>
        <Child name={this.state.name} />
        <div>{this.state.count}</div>
        <button onClick={this.handleClick}>+</button>
      </React.Fragment>
    );
  }
}

然而,每次点击按钮都会发生两件事:count 的值会递增,并且子组件会重新渲染。请注意观察

我们可以使用类组件中的 shouldComponentUpdate() 生命周期钩子来解决这个问题,代码如下所示

class Child extends React.Component {
  
  // No re-render, please!
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.name != this.props.name
  }
  
  render() {
    console.log('rendered')
    return <React.Fragment>{this.props.name}</React.Fragment>
  }
}

这就是 React.memo() 发挥作用的地方。它是一个高阶组件,我们可以将其包裹在子组件周围,这样子组件就不会被不必要地重新渲染了。

const Child = React.memo(props => {
  console.log("rendered");
  return <React.Fragment>{props.name}</React.Fragment>;
});

查看 CodePen 上的示例
React.memo 2
by CSS-Tricks (@css-tricks)
on CodePen.

React.lazy() 简化文件导入,Suspense 提供备用 UI

代码分割在 Web 开发中至关重要——它使我们能够仅导入所需的那些文件,这不仅减少了应用程序的初始加载时间,而且是 React 框架的核心原则。

现在,React 允许使用 React.lazy() 和 Suspense 在组件级别进行代码分割。

默认情况下,如果使用某个组件(即使它的使用取决于某个条件),那么我们会将其导入到使用它的文件中。React.lazy() 现在可以像这样处理导入:

const MyCounter = lazy(() => import("./Counter"));

这行代码返回一个 Promise,该 Promise 解析为导入的组件。从这里,我们可以像往常一样使用该组件。

const App = () => (
  <div>
    <MyCounter />
  </div>
);

在某些情况下,我们可能希望在组件准备好渲染之前渲染一个备用 UI。例如,API 调用可能需要一段时间才能获取并返回数据。这是一个在用户等待时显示加载状态的好机会。Suspense 可以做到这一点。

// Using React.lazy() to import the Counter component
const MyCounter = lazy(() => import("./Counter"));
const App = () => (
  <div>
    // Using Suspense to render a loading state while we wait for the Counter
    <Suspense fallback={<div>Loading...</div>}>
      <MyCounter />
    </Suspense>
  </div>
);

Suspense 的 fallback 属性可以接受一个 React 元素,所以可以尽情发挥。它可以用来在组件加载时显示任何我们想要的备用 UI。

contextType 访问 Provider 上下文并在无需 Render Props 的情况下传递状态

Context API 使得在多个组件之间共享状态成为可能,而无需使用第三方库。

在 React 16.6 中,可以在组件中声明 contextType 来访问 Provider 中的上下文。这使我们无需使用 Render Props 将上下文传递给 Consumer。

查看 CodePen 上的示例
React contextType
by CSS-Tricks (@css-tricks)
on CodePen.

首先,让我们创建我们的上下文

const UserContext = React.createContext({});

const UserProvider = UserContext.Provider;
const UserConsumer = UserContext.Consumer;

我们将在 App 组件中使用 Provider

class App extends React.Component {
  state = {
    input: "",
    name: 'John Doe'
  };

  handleInputChange = event => {
    event.preventDefault();
    this.setState({ input: event.target.value });
  };

  handleSubmit = event => {
    event.preventDefault();
    this.setState({ name: this.state.input, input: '' })
  };
  render() {
    return (
      <div>
        <UserProvider
          value={{
            state: this.state,
            actions: {
              handleSubmit: this.handleSubmit,
              handleInputChange: this.handleInputChange
            }
          }}
        >
          <User />
        </UserProvider>
      </div>
    );
  }
}

Provider 通过 value 属性将状态和方法传递给将使用它们的 Consumer 组件。要访问上下文,我们将使用 this.context 而不是像往常一样使用 Render Props。

class User extends React.Component {
  static contextType = UserContext;
  render() {
    const { state, actions } = this.context;
    return (
      <div>
        <div>
          <h2>Hello, {state.name}!</h2>
        </div>
        <div>
          <div>
            <input
              type="text"
              value={state.input}
              placeholder="Name"
              onChange={actions.handleInputChange}
            />
          </div>
          <div>
            <button onClick={actions.handleSubmit}>Submit</button>
          </div>
        </div>
      </div>
    );
  }
}

我们将 static contextType 设置为我们之前创建的 UserContext。通过这样做,我们能够提取上下文,其中包括来自 this.context 的状态和方法。我们使用 ES6 解构来获取值,以便我们可以在 Consumer 组件 User 中使用它们。与使用 Render Props 相比,这看起来更加简洁易读。

getDerivedStateFromErrors()

我们有 错误边界 来处理错误,它使用 componentDidCatch(),并在 DOM 更新后触发。它非常适合错误报告。但现在我们有了 getDerivedStateFromErrors(),如果捕获到错误,它可以在渲染完成之前渲染一个备用 UI。与 Suspense 的概念类似,但用于错误状态而不是加载状态。

查看 CodePen 上的示例
React getDerivedStateFromError
by CSS-Tricks (@css-tricks)
on CodePen.

让我们创建我们的错误边界组件来捕获出现错误的时刻

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }
  
  // If hasError is true, then trigger the fallback UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  // The fallback UI
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }
    return this.props.children;
  }
}

我们使用 getDerivedStateFromError() 来检测错误边界是否捕获了错误,然后在发生错误时将 hasError 返回为 true。发生这种情况时,我们希望显示一条消息,通知用户发生了错误。

class Counter extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  // If the count is greater than 5, throw an error
  render() {
    if (this.state.count > 5) {
      throw new Error('Error')
    }
    return (
      <div>
        <h2>{this.state.count}</h2>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}

count 的值大于 5 时,这将触发错误。接下来,我们需要将 Counter 组件作为 ErrorBoundary 组件的子组件进行包装,以将错误条件应用于该组件

const App = () => (
  <div>
    // Wrap the component in the ErrorBoundary to attach the error conditions and UI
    <ErrorBoundary>
      <Counter />
    </ErrorBoundary>
  </div>
)

我们甚至可以将错误限制在损坏的特定部分。例如,让我们获取一个地点列表。与其将整个地点列表替换为错误 UI,不如在发生错误的特定位置显示错误 UI。

查看 CodePen 上的示例
React getDerivedStateFromError 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

很不错,对吧?

React 继续添加了许多有用的功能,并且每次发布都会使编写代码变得更容易,v16.6 也不例外。如果您已经开始使用此版本中发布的任何最新功能,请告诉我——我很想知道您如何在实际项目中使用它们。

更多信息