共计 7107 个字符,预计需要花费 18 分钟才能阅读完成。
说来我也有很久没有更新过分享了,也始终处在温水煮青蛙的状态。因为一些起因加上这个社会真的太卷了,所以我也被迫退出了内卷大军,重温了很多常识,同时的确也发现了本人很多有余,也想借此机会让大家卷起来,毕竟能力是本人。原文链接
先说两句
对于我明天想写的内容,大部分你其实都能够在 React
官网文档上学习到。那为什么我还是想写?因为作为一个写了差不多有三年的 React
的人,我竟然没有正儿八经的通读过官网文档,我想通知的可能是和我相似的人吧,同时也补充一些我本人的了解和认识。
问你几个问题
性能优化这个问题啊,真的是永远都逃不了,是个面试官都要问几句,不过说实话不晓得是 React
做的太好了,还是我做的我的项目都太根底了,根本没遇到过什么性能问题,导致我在很长一段时间内基本不晓得 React 还有很多跟性能优化无关的 API。
先来看个代码,我间接在一个文件里定义多个组件不便大家观看,正式编写代码的时候一个文件就是一个组件。
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>
);
}
}
这代码没什么好说的,每次点击 click
更新state
,我当初问几个问题,你先思考一下~
- 每次点击
click
的时候,Test
组件会打印Test componentDidUpdate
吗? - 如果我把
Test
组件的React.Component
替换为React.PureComponent
,后果与下面一样吗?如果不一样,为什么? - 如果我批改这一行代码
<Test onClick={this.handleTestClick} />
为<Test onClick={() => {}} />
后果又如何?
shouldComponentUpdate
如同所有的内容都要从这个货色说起,shouldComponentUpdate
作为 React
生命周期的一部分,大多数 React
开发者至多还是据说过它的,简略来说在这个函数中返回一个布尔值,React
会依据这个布尔值来判断组件是否须要从新渲染。
shouldComponentUpdate
接管两个参数,一个是更新后的 props
,一个是更新后的state
,能够通过比拟两个props
和state
来决定是否须要从新渲染组件。
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>
);
}
}
这段代码也算比拟直观的阐明了 shouldComponentUpdate
的用法,为什么要这么做?当只有一个 Test
组件的时候可能影响不大,那如果有一千个乃至一万个 Test
的时候呢,每点击一次 click
就有一千个、一万个 Test
的componentDidUpdate
被调用,这就有点夸大了。所以当你在应用循环渲染组件的时候就肯定要留神到这一个点,它可能会成为你利用的瓶颈。
当初咱们来解一下第一个问题,每次点击 click
的时候,Test
组件会打印 Test componentDidUpdate
吗?
是的,每次点击 click
的时候,Test
组件会打印 Test componentDidUpdate
,除非咱们在Test
中定义了 shouldComponentUpdate
,同时返回了false
阻止其从新渲染。
PureComponent
对于 React
的这个 API,置信大家也没有那么生疏,依据官网文档的说法 Component
和PureComponent
很类似,两者的区别在于 PureComponent
中实现了 shouldComponentUpdate
函数,这也是为什么我说要从 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>;}
}
如果你在 PureComponent
中又应用了 shouldComponentUpdate
你应该会失去这样一个正告,侧面也通知咱们 PureComponent
曾经实现了 shouldComponentUpdate
这个函数了。
Test has a method called shouldComponentUpdate(). shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.
官网文档中说 PureComponent
中以浅层比照 props
和state
的形式来实现了这个函数,也就是浅比拟,那什么又是浅比拟呢?能够简略的了解为a === b
,这外面还是有一些说头的,不过不在本文探讨范畴内,举两个例子,大家能够自行搜寻了解。
let a = 5;
let b = 5;
let c = {};
let d = {};
console.log(a === b); // true
console.log(c === d); // false
在来看一段因为不当的代码导致的问题,大家肯定要留神这部分的内容。
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>
);
}
}
看到这里置信你应该能解答第二个问题和第三个问题了,不过咱们还是一起再来看看~
问:如果我把 Test
组件的 React.Component
替换为React.PureComponent
,后果与下面一样吗?如果不一样,为什么?
答:因为每次传递 props
中的 onClick
都是 App
组件中的 handleTestClick
,同时应用了PureComponent
,所以每次浅比拟都是统一的,所以不会在打印Test componentDidUpdate
了。
问:如果我批改这一行代码 <Test onClick={this.handleTestClick} />
为<Test onClick={() => {}} />
后果又如何?
答:尽管应用了 PureComponent
,然而因为App
每次调用 render
函数的时候都会从新申明一个办法,此办法和上一次传递给 Test
的办法不同,所以每次点击还是会打印Test componentDidUpdate
。
剩点内容补充
除了上述两个 API 以外,其余 API 或多或少只是它们的改版,所以我就放在一起说了。
memo
React.memo
在我看来就是 PureComponent
无状态组件版本,如果用的是 class
就用PureComponent
,如果用的是无状态组件就用memo
。
import React from 'react';
export default React.memo(function Test() {return <div>Test Component</div>;});
// 它也能够接管第二个参数, 相似 shouldComponentUpdate
// 两个参数上次的 props, 和以后的 props
// 不传默认状况它们两个做浅比拟, 传了由你本人管制
留神:此办法返回值与 shouldComponentUpdate
相同,返回值为 true
时不从新渲染组件。
useCallback 和 useMemo
这两个 API 是 React Hook
的一部分,为什么要放在一起说呢?因为它们十分的相似,据官网文档 useCallback(fn, deps)
相当于useMemo(() => fn, deps)
。
因为 React Hook
,我当初根本很少写class
组件了,起因是什么置信用过的小伙伴都分明,本文不论述这方面的内容,只想再问你一个问题:handleClick
办法每次都会从新定义吗?
import React from 'react';
export default function Test() {const handleClick = () => console.log('click');
return <div onClick={handleClick}>Test</div>
}
答案是会的,不信你能够验证一下。
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>
);
}
用法和阐明其实从上述的样例能够看出,就不在额定的阐明了。有时候咱们除了函数须要缓存以外,一个值可能也须要,这时候就须要应用useMemo
,它们两的区别也在于此,间接看用法。
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>
);
}
这外面又波及到一个 JavaScript
中根本类型和援用类型的区别,与下面浅比拟相似,你能够试试当 obj
等于一个根本类型时候的成果。
Profiler
最初的最初咱们来说一下Profiler
,它是用来测量被其包裹的 DOM 树渲染所带来的开销,帮忙你排查性能瓶颈,间接看用法。
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>
);
}
最初的最初
我所晓得的 React
跟性能优化相干的 API 都在下面了,说实话咱们遇到须要性能优化的场景真的太少了,这也是为什么面试官挑人的时候总喜爱问这方面的问题,因为大多数人都没有关注过,咱们想要的也是那一小部分人。所以,多学点常识真的对本人很好,了解更多的内容,你能力更好的了解他人的代码。