Speaking of which, I haven't updated the sharing for a long time, and I have been in the state of boiling frogs in warm water. Because of some reasons and the fact that this society is really too scrolled, I was forced to join the scrolling army and revisit a lot of knowledge. At the same time, I did discover a lot of my own shortcomings. I also want to take this opportunity to get everyone to curl up. After all The ability is oneself. link to original text
Let me say a few words
Regarding what I want to write today, most of you can actually learn from the official React
Then why do I still want to write? React
for almost three years, I have not read the official documents seriously. What I want to tell may be someone similar to me, and also add some my own understanding and opinions.
Ask you a few questions
Performance optimization is a problem, I really can’t escape it forever. It’s an interviewer who has to ask a few words, but to be honest React
did a great job, or the projects I’ve done are too basic, and I haven’t encountered them. What performance issues have caused me for a long time to have no idea that React has many APIs related to performance optimization.
Let's first look at the code. I directly define multiple components in a file for everyone to watch. When the code is formally written, a file is a component.
import React from 'react';
class Test extends React.Component {
componentDidUpdate() {
console.log('Test componentDidUpdate');
}
render() {
return <div></div>;
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => ({
count: state.count + 1,
}));
}
handleTestClick() {}
render() {
return (
<div>
<div>{this.state.count}</div>
<div onClick={this.handleClick}>click</div>
<Test onClick={this.handleTestClick} />
</div>
);
}
}
There is nothing to say about this code, every time you click click
update state
, I will ask a few questions now, you should think about it first~
- Every time I click
click
, will theTest
component printTest componentDidUpdate
? - If I
Test
componentsReact.Component
replacedReact.PureComponent
, the above results the same? If it is not the same, why? - What if I modify this line of code from
<Test onClick={this.handleTestClick} />
to<Test onClick={() => {}} />
?
shouldComponentUpdate
If all the contents to be talking about this stuff, shouldComponentUpdate
as React
part of the life cycle, most React
developers or at least heard of it, it simply returns a Boolean value in this function, React
based on this boolean Value to determine whether the component needs to be re-rendered.
shouldComponentUpdate
receives two parameters, one is the updated props
and the other is the updated state
. You can compare the two props
and state
to determine whether you need to re-render the component.
import React from 'react';
class Test extends React.Component {
componentDidUpdate() {
console.log('Test componentDidUpdate');
}
// 每次点击 click 都会打印 Test componentDidUpdate
// 添加这个函数后当 count 没有变化时不会打印 Test componentDidUpdate
shouldComponentUpdate(nextProps) {
if (this.props.count === nextProps.count) {
return false;
}
return true;
}
render() {
return <div></div>;
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => ({
count: state.count,
}));
}
render() {
return (
<div>
<div>{this.state.count}</div>
<div onClick={this.handleClick}>click</div>
<Test count={this.state.count} />
</div>
);
}
}
This code can also be regarded as a relatively intuitive explanation of shouldComponentUpdate
, why do you want to do this? When there is only one Test
component, it may not have much impact. If there are 1,000 or even 10,000 Test
, every time click
there will be 1,000 or 10,000 Test
componentDidUpdate
called, which is a bit exaggerated Up. So when you use loop rendering components, you must pay attention to this point, it may become the bottleneck of your application.
Now let's solve the first question, every time I click click
, will the Test
component print Test componentDidUpdate
?
Yes, every time you click click
, the Test
component will print Test componentDidUpdate
, unless we define Test
shouldComponentUpdate
, and return false
prevent it from re-rendering.
PureComponent
Regarding React
, I believe everyone is not that unfamiliar. According to the official document, Component
and PureComponent
are very similar. The difference between the two is that PureComponent
implements the shouldComponentUpdate
function, which is why I said to shouldComponentUpdate
.
import React from 'react';
class Test extends React.PureComponent {
componentDidUpdate() {
console.log('Test componentDidUpdate');
}
// 错误的用法
shouldComponentUpdate(nextProps) {
if (this.props.count === nextProps.count) {
return false;
}
return true;
}
render() {
return <div></div>;
}
}
If you shouldComponentUpdate
PureComponent
again, you should get such a warning. The side also tells us that PureComponent
has implemented the function shouldComponentUpdate
Test has a method called shouldComponentUpdate(). shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.
Official website document said PureComponent
to shallow contrast props
and state
ways to achieve this function, which is relatively shallow, relatively shallow that what is it? It can be simply understood as a === b
, there is still some talk about it, but it is not within the scope of this article. Give two examples, you can search and understand by yourself.
let a = 5;
let b = 5;
let c = {};
let d = {};
console.log(a === b); // true
console.log(c === d); // false
When looking at a problem caused by improper code, everyone must pay attention to the content of this part.
import React from 'react';
class Test extends React.PureComponent {
// 根据从 App 中传来的 animal 渲染组件
// 在 App 中每次点击添加新的动物后, 这里还是原来的 dog
render() {
return <div>Test: {this.props.animal.join(',')}</div>;
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
// 默认为一只狗
this.state = { animal: ['dog'] };
this.handleClick = this.handleClick.bind(this);
}
// 每次点击把新的值添加进 animal 中
// 此处有一个 Bug, 由于 animal.push 方法虽然更新了原来的数组
// 但是他们还是一个数组(这个说法有些奇怪), 指针还是一样的
// 可能需要读者自行搜索理解 JS 中基本类型和引用类型的存储方式
// 所以当 Test 组件接收到新的 animal 时, 通过浅比较会发现它们其实是一样的
// 也就意味着 Test 不会重新渲染
handleClick(val) {
const { animal } = this.state;
animal.push(val)
this.setState({
animal,
});
}
// 根据 state 中的 animal 渲染组件
render() {
return (
<div>
<div>App: {this.state.animal.join(',')}</div>
<div onClick={() => this.handleClick('cat')}>click</div>
<Test animal={this.state.animal} />
</div>
);
}
}
Seeing this, I believe you should be able to answer the second and third questions, but let’s take a look again~
Q: If I Test
components React.Component
replaced React.PureComponent
, the above results the same? If it is not the same, why?
A: Because each pass props
in onClick
are App
components handleTestClick
, using both PureComponent
, relatively shallow so every time is the same, it will not print Test componentDidUpdate
up.
Question: What if I modify this line of code <Test onClick={this.handleTestClick} />
to <Test onClick={() => {}} />
?
Answer: Although PureComponent
is used, because App
will redeclare a method every time the render
Test
last time, so each click will still print Test componentDidUpdate
.
Remaining content to add
In addition to the above two APIs, the other APIs are more or less just their revisions, so I will talk about them together.
memo
React.memo
in my opinion is PureComponent
stateless component versions, if using a class
to use PureComponent
, if using a stateless component to use memo
.
import React from 'react';
export default React.memo(function Test() {
return <div>Test Component</div>;
});
// 它也可以接收第二个参数, 类似 shouldComponentUpdate
// 两个参数上次的props, 和当前的props
// 不传默认情况它们两个做浅比较, 传了由你自己控制
Note: This method returns the value shouldComponentUpdate
contrast, the return value true
not re-render the component.
useCallback and useMemo
These two APIs are React Hook
, why should they be put together? Because they are very similar, according to the official document useCallback(fn, deps)
equivalent to useMemo(() => fn, deps)
.
Because of React Hook
, I rarely write the class
component now. The reason is that I believe the friends who have used it will know it. This article does not elaborate on this aspect. I just want to ask you one more question: handleClick
method be redefined every time?
import React from 'react';
export default function Test() {
const handleClick = () => console.log('click');
return <div onClick={handleClick}>Test</div>
}
The answer is yes, you can verify if you don't believe it.
import React, { useState } from 'react';
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set
let set = new Set();
export default function App() {
const [val, setVal] = useState(0);
// 对比两种方式可以看出区别
// const handleClick = useCallback(() => console.log('click'), []);
const handleClick = () => console.log('click');
// set 存的唯一值, 每次都会添加一个新值
set.add(handleClick);
console.log(set.size);
return (
<div>
{/* 如果 Test 是个特别复杂的组件, handleClick 每次变化都会导致它重新渲染 */}
<Test onClick={handleClick}>Test</Test>
<div onClick={() => setVal(val + 1)}>click</div>
</div>
);
}
Usage and explanation can be seen from the above examples, so there is no additional explanation. Sometimes we need a value in addition to the function that needs to be cached. At this time, we need to use useMemo
. The difference between the two is also in this, just look at the usage.
import React, { useState, useMemo } from 'react';
let set = new Set();
export default function App() {
const [val, setVal] = useState(0);
// 对比三种方式可以看出区别
// const obj = useMemo(() => ({ label: 'Test', value: 'test' }),[]);
// const obj = 'obj';
const obj = { label: 'Test', value: 'test' };
set.add(obj);
console.log(set.size);
return (
<div>
{/* 如果 Test 是个特别复杂的组件, obj 每次变化都会导致它重新渲染 */}
<Test obj={obj}>Test</Test>
<div onClick={() => setVal(val + 1)}>click</div>
</div>
);
}
This involves a JavaScript
, which is similar to the above. You can try obj
is equal to a basic type.
Profiler
Finally, let's Profiler
about 060f4f8d40e9d9, which is used to measure the overhead caused by the DOM tree rendering wrapped by it, to help you troubleshoot performance bottlenecks, and look directly at the usage.
import React, { Profiler, useState } from 'react';
function Test() {
return <div>Test</div>;
}
const callback = (
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) => {
console.log(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
);
};
export default function App() {
const [val, setVal] = useState(0);
return (
<div>
<Profiler id="test" onRender={callback}>
<Test>Test</Test>
</Profiler>
<div onClick={() => setVal(val + 1)}>click</div>
</div>
);
}
last of the last
React
that I know are all on it. To be honest, there are really too few scenarios that require performance optimization. This is why interviewers always like to ask questions in this area when they choose people, because Most people have not paid attention, and what we want is that small group of people. Therefore, learning more knowledge is really good for yourself, and understanding more content can you better understand other people's code.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。