代发帖子 本文将从 render 函数的角度总结 React App 的优化技巧。需要提醒的是,文中将涉及 React 16.8.2 版本的内容(也即 Hooks),因此请至少了解 useState 以保证食用效果。 正文开始。 当我们讨论 React App 的性能问题时,组件的 渲染 速度是一个重要问题。在进入到具体优化建议之前,我们先要理解以下 3 点: 这里 。 当我们在说「render」时,我们在说什么?这个问题其实写过 React 的人都会知道,这里再简单说下: 在 class 组件中,我们指的是 render 方法: - class Foo extends React.Component {
- render() {
- return
Foo ; - }
- }
在函数式组件中,我们指的是函数组件本身: - function Foo() {
- return
Foo ; - }
什么时候会执行「render」?render 函数会在两种场景下被调用: 1. 状态更新时a. 继承自 React.Component 的 class 组件更新状态时- import React from "react";
- import ReactDOM from "react-dom";
-
- class App extends React.Component {
- render() {
- return ;
- }
- }
-
- class Foo extends React.Component {
- state = { count: 0 };
-
- increment = () => {
- const { count } = this.state;
-
- const newCount = count < 10 ? count + 1 : count;
-
- this.setState({ count: newCount });
- };
-
- render() {
- const { count } = this.state;
- console.log("Foo render");
-
- return (
-
-
{count} - .increment}>Increment
-
); } } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); 可以看到,代码中的逻辑是我们点击就会更新 count,到 10 以后,就会维持在 10。增加一个 console.log,这样我们就可以知道 render 是否被调用了。从执行结果可以知道,即使 count 到了 10 以上,render 仍然会被调用。 总结:继承了 React.Component 的 class 组件,即使状态没变化,只要调用了setState 就会触发 render。b. 函数式组件更新状态时我们用函数实现相同的组件,当然因为要有状态,我们用上了 useState hook: - import React, { useState } from "react";
- import ReactDOM from "react-dom";
-
- class App extends React.Component {
- render() {
- return ;
- }
- }
-
- function Foo() {
- const [count, setCount] = useState(0);
-
- function increment() {
- const newCount = count < 10 ? count + 1 : count;
- setCount(newCount);
- }
-
- console.log("Foo render");
-
- return (
-
-
{count} -
-
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); 我们可以注意到,当状态值不再改变之后,render 的调用就停止了。 总结:对函数式组件来说,状态值改变时才会触发 render 函数的调用。2. 父容器重新渲染时- import React from "react";
- import ReactDOM from "react-dom";
-
- class App extends React.Component {
- state = { name: "App" };
- render() {
- return (
-
"App" > -
-
- Change name
-
-
); } } function Foo() { console.log("Foo render"); return ( Foo ); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); 只要点击了 App 组件内的 Change name 按钮,就会重新 render。而且可以注意到,不管 Foo 具体实现是什么,Foo 都会被重新渲染。 总结:无论组件是继承自 React.Component 的 class 组件还是函数式组件,一旦父容器重新 render,组件的 render 都会再次被调用。在「render」过程中会发生什么?只要 render 函数被调用,就会有两个步骤按顺序执行。这两个步骤非常重要,理解了它们才好知道如何去优化 React App。 Diffing在此步骤中,React 将新调用的 render 函数返回的树与旧版本的树进行比较,这一步是 React 决定如何更新 DOM 的必要步骤。虽然 React 使用高度优化的算法执行此步骤,但仍然有一定的性能开销。 Reconciliation基于 diffing 的结果,React 更新 DOM 树。这一步因为需要卸载和挂载 DOM 节点同样存在许多性能开销。 参考文档 。- const Bar = React.memo(
- function Bar({ name: { first, last } }) {
- console.log("update");
- return (
-
- {first} {last}
-
- );
- },
- (prevProps, newProps) =>
- prevProps.name.first === newProps.name.first &&
- prevProps.name.last === newProps.name.last
- );
尽管这条建议是可行的,但我们仍要注意 比较函数的性能开销 。如果 props 对象过深,反而会消耗不少的性能。 总结上述场景仍不够全面,但多少能带来一些启发性思考。当然在性能方面,我们还有许多其他的问题需要考虑,但遵守上述的准则仍能带来相当不错的性能提升。
|