刚开始写react可能只是写出来完成业务就完了,后期审查代码发现可能很多地方其实都可以优化,之前可能有些地方似是而非,在此小结一下。
react引入了一个叫做虚拟DOM的概念,安插在JavaScript逻辑和实际的DOM之间。这一概念提高了Web性能。在UI渲染过程中,React通过在虚拟DOM中的微操作来实对现实际DOM的局部更新。
在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因,React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A变成B,然后又从B变成A,React会认为UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
react的组件渲染分为初始化渲染和更新渲染。
+ 初始化渲染
+ 在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染
+ 更新渲染
+ 当我们要更新某个子组件的时候,我们期待的是只变化需要变化的组件,其他组件保持不变。
+ 但是,react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费
在开发模式下, 在支持的浏览器内使用性能工具可以直观的了解组件何时挂载,更新和卸载
+ 打开Chrome开发工具Performance 标签页点击Record
+ 执行你想要分析的动作。不要记录超过20s,不然Chrome可能会挂起。
+ 停止记录。
+ React事件将会被归类在 User Timing标签下。
绑定this的方式:一般有下面几种方式
+ constructor中绑定
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); //构造函数中绑定
}
//然后可以
<p onClick={this.handleClick}>
<p onClick={this.handleClick.bind(this)}>
<p onClick={() => { this.handleClick() }}>
shouldComponentUpdate是决定react组件什么时候能够不重新渲染的函数,返回true时更新,false时不更新。默认返回true,即每次重新渲染,因此我们可以重写个函数从而达到”个性化定制更新”的效果。
+ 栗子
class Title extends React.Component {
constructor(props) {
super(props)
}
render() {
console.log('title render')
return (
<div>{this.props.title}div>
)
}
}
class PureCom extends React.Component {
constructor(props) {
super(props)
this.state = {
title: 'pure',
num: 0
}
this.add = this.add.bind(this);
}
add() {
let { num } = this.state;
num++;
this.setState({ num })
}
render() {
console.log('pure render')
return (
<div>
<Title title={this.state.title} />
<p>{this.state.num}p>
<button onClick={this.add}>addbutton>
div>
)
}
}
class Title extends React.Component {
constructor(props) {
super(props)
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.title != this.props.title) {
return true //只有title变化时才更新
} else {
return false
}
}
render() {
console.log('title render')
return (
<div>{this.props.title}div>
)
}
}
类似上面的情况其实我们经常遇到,因此react提供了PureComponent来解决类似的问题,可以让我们少写许多的shouldComponentUpdate。
class Title extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
console.log('title render')
return (
<div>{this.props.title}div>
)
}
}
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextState);
}
大多数情况PureComponent都可以解决,但是之前也说过,他是“浅比较”,如果遇到数据结构比较复杂,他是无法识别的。
class PureCom extends PureComponent {
constructor(props) {
super(props)
this.state = {
items: [1, 2, 3],
title: 'pure',
}
this.add = this.add.bind(this);
}
add() {
let { items } = this.state;
items.push(23);
this.setState({ items })
}
render() {
console.log('pure render')
return (
<div>
<Title title={this.state.title} />
<ul>
{this.state.items.map((e, i) => {
return <li key={i}>{e}li>
})}
ul>
<button onClick={this.add}>addbutton>
div>
)
}
}
items
其实是和state里面的items
指向相同引用。原理和下面一样。let a={val:1};
let b=a;
b.val=2;
console.log(a)//{val:2}
console.log(b)//{val:2}
add() {
let items =JSON.parse(JSON.stringify(this.state.items));//黑科技
//或者let items=deepCopy(this.state.items);
items.push(23);
this.setState({ items })
}
add() {
let { items } = this.state;
items=items.concat(23) //此时的items是一个新数组
this.setState({ items })
}
add() {
let { items } = this.state;
items = update(items, { $push: [23] });
this.setState({ items })
}
个人感觉redux的渲染机制也是和PureComponent类似的,都是浅比较,因此上面的3种解决办法也适用于redux.
一些生命周期会被删除,将在17.0:删除componentWillMount,componentWillReceiveProps和componentWillUpdate。
componentWillMount
=> componentDidMount
componentWillReceiveProps
=> getDerivedStateFromProps
componentWillUpdate
=> getSnapshotBeforeUpdate
//代替componentWillReceiveProps,因为是静态方法,不能访问到 this,避免了一些可能有副作用的逻辑,比如访问 DOM 等等
//会在第一次挂载和重绘的时候都会调用到,因此你基本不用在constructor里根据传入的props来setState
static getDerivedStateFromProps(nextProps, prevState) {
console.log(nextProps, prevState)
if (prevState.music !== nextProps.music) {
return {
music: nextProps.music,
music_file: music_file,
index:prevState.index+1
};
//document.getElementById('PLAYER').load(); //这里不对,应该放在getSnapshotBeforeUpdate 和 componentDidUpdate
}
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
if (this.state.music != prevState.music) { //进行aduio的重载
return true
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
document.getElementById('PLAYER').load(); //重载
}
}
//新的getSnapshotBeforeUpdate生命周期在更新之前被调用(例如,在DOM被更新之前)。此生命周期的返回值将作为第三个参数传递给componentDidUpdate。 (这个生命周期不是经常需要的,但可以用于在恢复期间手动保存滚动位置的情况。)
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) { //snapshot
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}div>
);
}
}
//有一个常见的错误观念认为,在componentWillMount中提取可以避免第一个空的渲染。在实践中,这从来都不是真的,因为React总是在componentWillMount之后立即执行渲染。如果数据在componentWillMount触发的时间内不可用,则无论你在哪里提取数据,第一个渲染仍将显示加载状态。
// After
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentDidMount() {
this._asyncRequest = asyncLoadData().then(
externalData => {
this._asyncRequest = null;
this.setState({ externalData });
}
);
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
}
大家好,这里是「 TaoLand 」,这个博客主要用于记录一个菜鸟程序猿的Growth之路。这也是自己第一次做博客,希望和大家多多交流,一起成长!文章将会在下列地址同步更新……
个人博客:www.yangyuetao.cn
小程序:TaoLand