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