React(四)- ReactDOM的Diffing算法和React脚手架
一. React中的key
从案例出发,从key
的角度来引入diffing算法案例1:
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 18 }
]
}
add = () => {
const { persons } = this.state
const p = { id: persons.length + 1, name: '小王', age: 20 }
// 这里是小王置于前
this.setState({ persons: [p, ...persons] })
}
render() {
return (
<div>
<h1>人员列表</h1>
<button onClick={this.add}>添加人员</button>
<ul>
{
this.state.persons.map((p, index) => {
return <li key={index}>{p.name}----{p.age}</li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'))
页面效果如下:
点击添加人员按钮后:
我们可以从代码中提到两个问题:
- React中的
key
有什么作用? - 为什么遍历列表的时候,
key
最好不要使用index
?
对于第一个问题,解答如下:
key
是虚拟DOM对象的标识,在更新显示的时候key
起着非常重要的作用。- 当状态中的数据发生变化时,React会根据
新数据
去生成新的虚拟DOM
。 - 随后React会进行
新的虚拟DOM
和旧的虚拟DOM
的diff比较
,比较规则(也就是diffing算法)如下:
1.1 diffing规则
A:旧虚拟DOM
中若找到了与新虚拟DOM
相同的key
:
(1).若
虚拟DOM
中内容没变,那么直接使用之前的真实DOM
。
(2).若虚拟DOM
中内容变了,那么生成新的真实DOM
,随后替换掉页面中之前的真实DOM
。
B:旧虚拟DOM
中若没有找到与新虚拟DOM
相同的key
:
那么根据数据创建新的
真实DOM
,随后渲染到页面。
对于第二个问题,解答如下:
- 若对数据进行
逆序添加
、逆序删除
等破坏顺序的操作,会产生没有必要的真实DOM更新,执行效率低。 - 如果结构中还包含输入类的DOM,会产生错误DOM更新,页面显示会有问题。
- 若不存在上述情况,仅仅用于渲染列表的展示,那么使用index作为key是没问题的。
那么上述Demo中会有怎样的问题呢?来分析下:
初始数据以及初始的虚拟DOM:
数据:
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 18 }
虚拟DOM:
<li key=0>小张----18</li>
<li key=1>小李----18</li>
更新后的数据以及虚拟DOM:
{ id: 3, name: '小王', age: 20 },
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 18 }
虚拟DOM:
<li key=0>小王----20</li>
<li key=1>小张----18</li>
<li key=2>小李----18</li>
这里可以进行对比,发现原本的虚拟DOM
和更新后的虚拟DOM
里面的key对应的内容全部对不上,那么根据以上规则:会将所有数据转化为真实DOM,并且渲染到页面。
即顺序打乱了,明明有两条数据的DOM可以作为复用,但是实际上导致了没有必要的虚拟DOM的更新。 那么如果数据有1W条,那么上述情况就会将1W条数据全部重新渲染,效率低下!
而从分析中我们可以发现,index
也就是其标签索引,下标从0开始。
那么上述情况该怎么解决?我们可以用Id
(数据的唯一标识)作为key
:
this.state.persons.map((p) => {
return <li key={p.id}>{p.name}----{p.age}<input type="text"/></li>
})
我们在原本代码的基础上增加一个输入框(见上面的代码),然后看看使用索引和数据唯一标识两种情况下的区别:
使用index
作为key
:(提前往输入框中复制对应的属性)
点击添加后,可以发现数据对不上:
使用Id
作为key
:可以发现,数据能够对得上,也就佐证了上述的问题。
那么在实际开发的时候,我们如何去选择key
?
- 最好使用每条数据的唯一标识作为
key
,比如Id
、手机号、身份证号等。 - 如果确定只是简单的展示数据,比如做一个Demo,那么使用
index
也是可以的(即不考虑效率的情况下)
二. React脚手架
我们利用React脚手架来快速创建一个机遇React库的模板项目,其中:
- 包含了所有需要的相关配置(语法检查、jsx编译、devServer等)
- 下载好了所有相关的依赖。
- 可以直接运行一个简单效果。
React提供的脚手架库:create-react-app
项目的整体技术架构是:react+webpack+es6+eslint
2.1 初始化React脚手架
步骤如下:
- 全局安装
npm install -g create-react-app
- 到自己想创建项目的目录,使用命令:
create-react-app xxx
- 进入到对应的项目目录后启动即可:
npm start
启动之后可以发现有如下提示信息:
2.2 脚手架文件介绍
public
目录:静态资源文件夹
favicon.ico
:网站页签图标index.html
:主页面logo02.png
:logo图logo512.png
:logo图manifest.json
:应用加壳的配置文件rebots.txt
:爬虫协议文件
src
目录:源码文件夹
App.css
:App组件的样式App.js
:App组件,所有组件的一个容器App.test.js
:用于给App做测试index.css
:样式index.js
:入口文件logo.svg
:logo图reportWebVitals.js
:页面性能分析文件setupTests.js
:组件单元测试的文件
2.3 写一个简单的Hello组件
项目结构如下:
1.入口文件index.js
:
// 引入React核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
// 渲染App到页面
ReactDOM.render(<App />, document.getElementById('root'))
2.外壳文件App.js
:(其他定义的组件都放在这里)
// 创建外壳组件App
import React, { Component } from 'react'
import Hello from './component/Hello/Hello'
import World from './component/World/World'
// 暴露App组件,其他Js就可以引用了
export default class App extends Component {
render() {
return (
<div>
<Hello/>
<World/>
</div>
)
}
}
3.以Hello组件为例,Hello.js
:组件js一般都以大写开头(和普通js文件进行区分)
import React, { Component } from 'react'
import './Hello.css'
export default class Hello extends Component {
render() {
return (
<h2 className="title">Hello</h2>
)
}
}
4.对应的css
文件:
.title{
background-color: rgb(167, 157, 20);
}
5.运行结果如下:
Tip1:为了更加直观的区分,可以把组件的文件都改为.jsx
结尾,此时对应的文件图标也会改变哦~
Tip2:每个组件一般都有自己的专属文件夹,那么直接将组件的名字改为index.jsx
即可:(这种方式并不推荐,因为最好将每个组件都定义一个名字,更加规范)
若改为这种形式,那么App.js中进行引入的时候,就不需要引入到具体的文件了:
2.3.1 样式的模块化
改变如下:css
文件的名字加一个module
引用的时候改为:
Tip3:VsCode快捷创建组件
在该目录下创建一个Test.jsx
文件:
直接输入rcc
,回车即可:
则代码自动生成:
文章结尾预告:,下一篇文章准备从功能界面的组件化编码流程来讲,也就是如何将多个组件进行组合使用。