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 也不例外。如果您已经开始使用此版本中发布的任何最新功能,请告诉我——我很想知道您如何在实际项目中使用它们。
很好的文章,有示例和解释