React diff原理探究以及应用实践

img

实际我们只需对D执行移动操作,然而由于D在旧集合中的位置是最大的,导致其他节点的oldIndex < maxIndex,造成D没有执行移动操作,而是A、B、C全部移动到D节点后面的现象。针对这种情况,官方建议:

在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作。当节点数量过大或更新操作过于频繁时,这在一定程度上会影响React的渲染性能。


由于key的存在,react可以准确地判断出该节点在新集合中是否存在,这极大地提高了diff效率。我们在开发过中进行列表渲染的时候,若没有加key,react会抛出警告要求开发者加上key,就是为了提高diff效率。但是加了key一定要比没加key的性能更高吗?我们再来看一个例子:

现在有一集合[1,2,3,4,5],渲染成如下的样子:

1
2
3
4
5

现在我们将这个集合的顺序打乱变成[1,3,2,5,4]。

1.加key

1
1
2
3
3
========>
2
4
5
5
4

操作:节点2移动至下标为2的位置,节点4移动至下标为4的位置。

2.不加key

1
1
2
3
3
========>
2
4
5
5
4

操作:修改第1个到第5个节点的innerText


如果我们对这个集合进行增删的操作改成[1,3,2,5,6]。

1.加key

1
1
2
3
3
========>
2
4
5
5
6

操作:节点2移动至下标为2的位置,新增节点6至下标为4的位置,删除节点4。

2.不加key

1
1
2
3
3
========>
2
4
5
5
6

操作:修改第1个到第5个节点的innerText


通过上面这两个例子我们发现:

由于dom节点的移动操作开销是比较昂贵的,没有key的情况下要比有key的性能更好。

通过上面的例子我们发现,虽然加了key提高了diff效率,但是未必一定提升了页面的性能。因此我们要注意这么一点:

对于简单列表页渲染来说,不加key要比加了key的性能更好

根据上面的情况,最后我们总结一下key的作用:

  • 准确判断出当前节点是否在旧集合中

  • 极大地减少遍历次数

应用实践

==============================================================

示例代码地址:https://github.com/ruichengpi…

页面指定区域刷新


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AgBbpToV-1590739472382)(https://segmentfault.com/img/bVbrxNK?w=840&h=426)]

现在有这么一个需求,当用户身份变化时,当前页面重新加载数据。猛一看过去觉得非常简单,没啥难度的,只要在componentDidUpdate这个生命周期里去判断用户身份是否发生改变,如果发生改变就重新请求数据,于是就有了以下这一段代码:

import React from ‘react’;

import {connect} from ‘react-redux’;

let oldAuthType = ‘’;//用来存储旧的用户身份

@connect(

state=>state.user

)

class Page1 extends React.PureComponent{

state={

loading:true

}

loadMainData(){

//这里采用了定时器去模拟数据请求

this.setState({

loading:true

});

const timer = setTimeout(()=>{

this.setState({

loading:false

});

clearTimeout(timer);

},2000);

}

componentDidUpdate(){

const {authType} = this.props;

//判断当前用户身份是否发生了改变

if(authType!==oldAuthType){

//存储新的用户身份

oldAuthType=authType;

//重新加载数据

this.loadMainData();

}

}

componentDidMount(){

oldAuthType=this.props.authType;

this.loadMainData();

}

render(){

const {loading} = this.state;

return (

{`页面1${loading?'加载中...':'加载完成'}`}

)

}

}

export default Page1;

看上去我们仅仅通过加上一段代码就完成了这一需求,但是当我们页面是几十个的时候,那这种方法就显得捉襟见肘了。哪有没有一个很好的方法来实现这个需求呢?其实很简单,利用react diff的特性就可以实现它。对于这个需求,实际上就是希望当前组件可以销毁在重新生成,那怎么才能让其销毁并重新生成呢?通过上面的总结我发现两种情况,可以实现组件的销毁并重新生成。

  • 当组件类型发生改变

  • 当key值发生变化

接下来我们就结合这两个特点,用两种方法去实现。

第一种:引入一个loading组件。切换身份时设置loading为true,此时loading组件显示;切换身份完成,loading变为false,其子节点children显示。

{loading?:children}

第二种:在刷新区域加上一个key值就可以了,用户身份一改变,key值就发生改变。

{children}

第一种和第二种取舍上,个人建议的是这样子的:

如果需要请求服务器的,用第一种,因为请求服务器会有一定等待时间,加入loading组件可以让用户有感知,体验更好。如果是不需要请求服务器的情况下,选用第二种,因为第二种更简单实用。

更加方便地监听props改变


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zt4JNYab-1590739472383)(https://segmentfault.com/img/remote/1460000018914265)]

针对这个需求,我们喜欢将搜索条件封装成一个组件,查询列表封装成一个组件。其中查询列表会接收一个查询参数的属性,如下所示:

import React from ‘react’;

import {Card} from ‘antd’;

import Filter from ‘./components/filter’;

import Teacher from ‘./components/teacher’;

export default class Demo2 extends React.PureComponent{

state={

filters:{

name:undefined,

height:undefined,

age:undefined

}

}

handleFilterChange=(filters)=>{

this.setState({

filters

});

}

render(){

const {filters} = this.state;

return

{/* 过滤器 */}

{/* 查询列表 */}

}

}

现在我们面临一个问题,如何在组件Teacher中监听filters的变化,由于filters是一个引用类型,想监听其变化变得有些复杂,好在lodash提供了比较两个对象的工具方法,使其简单了。但是如果后期给Teacher加了额外的props,此时你要监听多个props的变化时,你的代码将变得比较难以维护。针对这个问题,我们依旧可以通过key值去实现,当每次搜索时,重新生成一个key,那么Teacher组件就会重新加载了。代码如下:

import React from ‘react’;

import {Card} from ‘antd’;

import Filter from ‘./components/filter’;

import Teacher from ‘./components/teacher’;

export default class Demo2 extends React.PureComponent{

state={

filters:{

name:undefined,

height:undefined,

age:undefined

},

tableKey:this.createTableKey()

}

createTableKey(){

return Math.random().toString(36).substring(7);

}

handleFilterChange=(filters)=>{

this.setState({

filters,

//重新生成tableKey

tableKey:this.createTableKey()

});

}

render(){

const {filters,tableKey} = this.state;

return

{/* 过滤器 */}

{/* 查询列表 */}

}

}

即使后期给Teacher加入新的props,也没有问题,只需拼接一下key即可:

<Teacher key={${tableKey}-${prop1}-${prop2}} filters={filters} prop1={prop1} prop2={prop2}/>

react-router中Link问题


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dvjYkaY-1590739472385)(https://segmentfault.com/img/remote/1460000018914266)]

先看一下demo代码:

import React from ‘react’;

import {Card,Spin,Divider,Row,Col} from ‘antd’;

import {Link} from ‘react-router-dom’;

const bookList = [{

bookId:‘1’,

bookName:‘三国演义’,

author:‘罗贯中’

},{

bookId:‘2’,

bookName:‘水浒传’,

author:‘施耐庵’

}]

export default class Demo3 extends React.PureComponent{

state={

bookList:[],

bookId:‘’,

loading:true

}

loadBookList(bookId){

this.setState({

loading:true

});

const timer = setTimeout(()=>{

this.setState({

loading:false,

bookId,

bookList

});

clearTimeout(timer);

},2000);

}

componentDidMount(){

const {match} = this.props;

const {params} = match;

const {bookId} = params;

this.loadBookList(bookId);

}

render(){

const {bookList,bookId,loading} = this.state;

const selectedBook = bookList.find((book)=>book.bookId===bookId);

return

{

selectedBook&&(

<img width=“120” src={/static/images/book_cover_${bookId}.jpeg}/>

书名:{selectedBook?selectedBook.bookName:'--'}
作者:{selectedBook?selectedBook.author:'--'}
)

}

关联图书

{

最后

面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。

大厂面试题

面试题目录

okId===bookId);

return

{

selectedBook&&(

<img width=“120” src={/static/images/book_cover_${bookId}.jpeg}/>

书名:{selectedBook?selectedBook.bookName:'--'}
作者:{selectedBook?selectedBook.author:'--'}
)

}

关联图书

{

最后

面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。

[外链图片转存中…(img-UAbnZDk1-1718017758040)]

[外链图片转存中…(img-MwTPM8XF-1718017758041)]

[外链图片转存中…(img-Il5OKP3U-1718017758041)]

[外链图片转存中…(img-WKNPS7Zs-1718017758042)]

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值