React基础学习笔记
Author: 哇哇小仔
Date: 2021-03-19
Version: V 1.1.0
Description: 根据尚硅谷React视频教程总结的笔记
第1章 React简介
- React是什么?
- 用于构建用户界面的Javascript库
- 发送请求获取数据
- 处理数据(过滤、整理格式)
- 操作DOM呈现页面(这个是React的作用)
- React是一个将数据渲染为HTML视图的开源Javascript库
- 用于构建用户界面的Javascript库
- 谁开发的?
- 由Facebook开发,且开源
- 为什么要学?
- 原生Javascript操作DOM繁琐,效率低(DOM-API操作UI)
- 使用Javascript直接操作DOM,浏览器会进行大量的重绘重排
- 原生Javascript没有组件化编码方案,代码复用率低
- React的特点
- 采用组件化模式,声明式编程,提高开发效率及组建复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
- React包
- babel.min.js: 使用babel是将jsx==>js
- react.development.js: React的核心库
- react-dom.development.js: React的扩展库
注:必须先引入核心库,再引入扩展库
第2章 React Basic
2.1 Hello React
- 代码例子
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库,引入了React对象 -->
<script type='text/javascript' src='./js/react.development.js'></script>
<!-- 引入react-dom,用于支持react操作DOM,引入了ReactDOM对象 -->
<script type='text/javascript' src='./js/react-dom.development.js'></script>
<!-- 引入babel,用于将jsx转为js -->
<script type='text/javascript' src='./js/babel.min.js'></script>
<!-- 此处一定要写babel,就是告诉浏览器,写的是jsx,需要babel转换成js -->
<script type='text/babel'>
// 1. 创建虚拟DOM
/* 此处一定不要写引号,因为不是字符串,这是JSX,
如果想清除一些使用缩进直接在最边加上一个括号 */
const VDOM = (
<h1 id='title'>
<span>Hello React!</span>
</h1>
);
// 2. 渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'));
</script>
</body>
- 注意点:
- 必须先引入react核心库,再引入扩展库,这个顺序不能颠倒
- script标签中写react代码时,type必须是’text/babel’
- 创建虚拟DOM时,一定不能加引号
2.2 创建虚拟DOM的两种方式
- 使用JSX创建
const VDOM = <h1>Hello React</h1>
- 使用JS创建
const VDOM = React.createElement('h1', {id: 'title'}, 'Hello React')
- 关于虚拟DOM
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性
- 虚拟DOM最终会被React转化为真实DOM呈现在页面上
2.3 JSX语法规则
实际上,JSX 仅仅只是 React.createElement(component, props, …children) 函数的语法糖。
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时,要用{}
- 样式的类名指定不要用class,要用className
- 内联样式,要用style={{key: value}}的形式去写,因为右侧是相当于混入了JS表达式,外侧{}表示{}里边是JS表达式,里侧{}表示一个键值对对象
- 虚拟DOM必须只有一个根标签
- 标签必须闭合 (比如那个input自结束标签,结尾可以加上一个’/’,或者
</input>
) - 标签首字母
(1) 若小写字母开头,则将该标签转为html中同名元素,若html中没有对应的同名标签,则报错
(2) 若大写字母开头,react则去渲染对应的组件,若组件没有定义,则报错 - JSX注释的方式 {/* */}
- 补充:
一定注意区分:JS语句(代码)和 JS表达式,上边第2条中是针对JS表达式
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。下边这些都是表达式:
(1)a
(2)a+b
(3)demo(1)
(4)arr.map()
(5)function test () { } - 语句(代码):下边这些都是语句(代码)
(1)if () { }
(2)for () { }
(3)switch () {case: xxx}
2.4 模块与组件、模块化与组件化的理解
- 模块
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用js, 简化js的编写, 提高js运行效率
- 组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
- 模块化
- 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
- 组件化
- 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
第3章 React面向组件编程
3.1 基本理解和使用
3.1.1 函数式组件
- 函数式组件,函数名就是组件名
// 创建函数式组件
function MyComponent(){
console.log(this); // 此处this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
- 执行了 ReactDOM.render(…) 之后,发生了什么?
- React 解析组件标签,找到 MyComponent 组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面上
3.1.2 类式组件
// 创建类式组件
class MyComponent extends React.Component {
render(){
// rende是放在哪里的?—— MyComponet的原型对象上的
// render中的this是谁?—— MyComponent组件的实例对象
console.log('render中的this是:', this);
return <h2>我是用类定义的组件(用于复杂组件的定义)</h2>
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
- 执行了ReactDOM.render(…)之后,发生了什么?
- React解析组件标签,找到 MyComponent 组件
- 发现组件是使用类定义的,随后 new 出该类的实例,并通过该实例调用到原型上的 render 方法
- 将 render 返回的虚拟 DOM 转为真实的 DOM,随后呈现在页面中
3.2 组件实例的三大核心属性1:state
- 理解
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件,也就是重新调用组件的render方法)
- 状态驱动页面显示
- 强烈注意
- 组件中render方法中的this为组件的实例对象
- 组件自定义方法中的this为undefined,如何解决?
- 方法一:强制绑定this:通过函数对象的bind()
- 方法二:赋值语句+箭头函数
- 状态数据(也就是state里边的属性),不能直接修改或更新,需要使用setState()方法
- 代码示例
- state修改的方法一
<script type='text/babel'>
// 1. 创建组件
class Weather extends React.Component {
// 构造器调用几次? —— 1次
constructor(props){
super(props);
// 初始化状态
this.state = {isHot: true, wind: '微风'}
// 等号左边的changeWeather是在实例自身创建一个changeWeather
// 等号右边的changeWeather是找到了原型对象上的changeWeather,也就是下边定义的
// bind方法改变了等号左边的函数对象的this,也就是传递的this(也就是Weather的实例对象)
/*
bind() 方法会创建一个新函数,当这个新函数被调用时,
它的 this 值是传递给 bind() 的第一个参数,
它的参数是 bind() 的其他参数和其原本的参数。
*/
this.changeWeather = this.changeWeather.bind(this)
// this.demo = this.changeWeather.bind(this)
}
// render 调用了几次? —— n+1次,1是初始化的那次,n是状态更新的次数
render(){
// 对象的解构赋值,寻找同名属性进行解构
// 读取状态
let {isHot} = this.state;
return (<h2 onClick={this.changeWeather}>
今天天气很{isHot?'炎热':'凉爽'}, {this.state.wind}
</h2>)
// return <h2 onClick={this.demo}>今天天气很{isHot?'炎热':'凉爽'}</h2>
}
// changeWeather 调用了几次? —— 点几次调用几次
changeWeather(){
// changeWeather 放在了 Weather 的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,不是通过实例调用的,是直接调用的
// 类中的方法默认开启了局部的严格模式,严格模式下,禁止this关键字指向全局对象。
// 所以changeWeather中的this为undefined
// 获取原来的isHot值
const isHot = this.state.isHot;
// 严重注意:状态state不能直接更改,下面这行就是直接更改
// this.state.isHot = !isHot; // 这是错误的写法
// 严重注意:状态state只能通过setState修改,且更新是一种合并,不是替换
// 不是替换,因此state里边的wind属性还在
this.setState({isHot: !isHot})
}
}
// 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
- state修改的简写方法二
<script type='text/babel'>
class Weather extends React.Componenent{
// 初始化状态
state = {isHot: true, wind: '微风'}
render(){
let {isHot} = this.state;
return (<h2 onClick={this.changeWeather}>
今天天气很{isHot?'炎热':'凉爽'}, {this.state.wind}
</h2>)
}
// 自定义方法 —— 要用赋值语句的形式+箭头函数
changeWeather = () => {
// 箭头函数this是静态的,this始终指向函数声明时所在作用域下的 this 的值
// 因此这里的this是Weather的实例对象
const isHot = this.state.isHot;
this.setState({isHot: !isHot})
}
}
// 渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
3.3 组件实例的三大核心属性2:props
- 理解
- 每个组件对象都会有props属性
- 组件标签的所有属性都保存在props中
- 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意:组件内部不要修改props数据
- 编码操作
- 内部读取某个属性值:
this.props.name
- 对props中的属性值进行类型限制和必要性限制
- 第一种方式 (React v15.5 开始已经弃用)
Person.propTypes = {
name: React.PropTypes.string.isRequired,
gender: React.PropTypes.string,
age: React.PropTypes.number,
sayName: React.PropTypes.func
}
- 第二种方式(新)
使用prop-types库进行限制(需要引入prop-types库)
//这种方式是写在类的外部
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
}
// 简写方式,写在类的内部
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
}
- 扩展属性:将对象的所有属性通过props传递
const person = {name:'Danny', age:10}
<Person {...person}>
- 默认属性值
Person.defaultProps = {
age = 18,
gender = '未知'
}
5)组件类的构造函数
* 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
* 如果构造器没有接收props,没有传递给super,则constructor中访问this.props为undefined
constructor(props){
super(props)
console.log(props) // 打印所有属性
}
- 类式组件使用props(简写方法)
<div id='test1'><div>
<div id='test2'><div>
<!-- 引入prop-types 用于对组件标签属性进行限制,全局会多了一个PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type='text/babel'>
// 创建虚拟DOM
class Person extends React.Component{
/* constructor(props){ // 一般构造器都不需要写
super(props)
}*/
static propTypes = { // 设置类型和必须性限制
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string
}
static defaultProps = { // 设置默认值
age: 18,
gender: '未知'
}
render(){
return (<ul>
<li>名字:{name}</li>
<li>性别:{gender}</li>
<li>年龄:{age}</li>
</ul>)
}
}
const p = {name: 'Danny', age: 18, gender: 'Male'}
// 将虚拟DOM渲染到页面
ReactDOM.render(<Person {...p}/>, document.getElementById('test1'))
ReactDOM.render(<Person name='Jenny'/>, document.getElementById('test2'))
</script>
- 函数式组件使用props
<script type='text/babel'>
function Person (props) {
const {name, age, gender} = props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{gender}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string
}
Person.defaultProps = {
name: '',
age: 18,
gender: '未知'
}
ReactDOM.render(<Person name='Danny' age={22} gender='Male'/>, document.getElementById('test1'));
ReactDOM.render(<Person name='Jenny' age={12} />, document.getElementById('test2'));
</script>
3.4 组件实例的三大核心属性3:refs与事件处理
3.4.1 标识 refs
- 字符串形式的ref:
ref='input1'
<script>
class Demo extends React.Component{
showData = () => {
// ref的键值对key-value保存在 this.refs这个对象中
// key就是'input1', value就是input那个真实DOM
const {input1} = this.refs; // 解构赋值
alert(input1.value);
}
render(){
// ref 直接是一个字符串,不推荐使用,今后可能会被React弃用
return (
<div>
<input ref='input1' type='text' />
<button onClick={this.showData}>点我显示输入内容</button>
</div>
)
}
}
</script>
- 回调函数形式的ref:
ref = {c => this.input1 = c}
<script>
class Demo extends React.Component{
showData = () => {
const {input1} = this; // 解构赋值
alert(input1.value)
}
render(){
// 回调函数 接受一个参数 c (currentNode)
// 这个 c 实际就是这个真实的 DOM 节点 input
// ref 的回调中的 this 是 Demo 的实例对象
return (
<div>
<input ref={c => this.input1 = c} type='text' />
<button onClick={this.showData}>点我显示输入内容</button>
</div>
)
}
}
</script>
- 关于回调 refs 的说明
- 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。
- 这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
- 通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题。
- 但是大多数情况下它是无关紧要的。
saveInput = (currentNode) => {
this.input1 = currentNode
}
// 将input的ref那里写成一个Demo的绑定函数(例如:saveInput)
<input ref={this.saveInput} type='text' />
- createRef:
myRef = React.createRef(); ref={this.myRef}
<script>
class Demo extends React.Component{
// React.createRef 调用后可以返回一个容器,该容器可以存储被ref所标识的节点
// 该容器是专“人”专用的,只能存一个节点,
// 需要标记几个节点就要创建几个React.createRef()
myRef = React.createRef()
myRef2 = React.createRef(
showData = () => {
// console.log(this.myRef); // {current: input}
// 这里的 current 是固定的,不能更改
alert(this.myRef.current.value)
}
render(){
return (
<div>
<input ref={this.myRef} type='text' />
<input ref={this.myRef2} type='text' />
<button onClick={this.showData}>点我显示输入内容</button>
</div>
)
}
}
</script>
3.4.2 事件处理
- 通过 onXxx 属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —— 为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了高效
- 通过event.target得到发生事件的DOM元素对象 —— 不要过度使用ref
<div id='test'></div>
<script>
// 创建虚拟DOM
class Demo extends React.Component{
// 发生事件的元素就是我们要操作的元素,此时ref可以使省略
// 下边input失去焦点,显示的input的值,此时ref就可以省略
showData = (event) => {
alert(event.target.value)
}
render(){
return (
<input onBlur={this.showData} type='text' placeholder='失去焦点显示数据'/>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
3.5 收集表单数据
3.5.1 非受控组件
- 表单中输入类的DOM的值,是现用现取,这种叫做非受控组件
<div id='test'></div>
<script>
class Login extends React.Component{
// 点击提示输入的内容
handleSubmit = (event) => {
event.preventDefault(); // 阻止表单提交的默认行为
const {username, password} = this;
alert(`输入的用户名是${username.value},密码是${password.value}`);
}
render(){
return (
// 表单绑定提交事件(form有onSubmit事件)
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type='text' name='username'/>
密码:<input ref={c => this.password = c} type='password' name='password'/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'));
</script>
3.5.2 受控组件
- 表单中输入类的DOM,随着输入我们将内容维护到组件的状态state中,等待使用时从state中取出来,我们叫做受控组件。
- 受控组件不需要使用ref,更加推荐。
<div id='test'></div>
<script>
class Login extends React.Component{
// 状态state初始化
state = {username:'', password: ''}
// 将username存入状态中
saveUsername = (event) => {
this.setState({username: event.target.value});
}
// 将password存入状态中
savePassword = (event) => {
this.setState({password: event.target.value});
}
handleSubmit = (event) => {
event.preventDefault(); // 阻止表单提交的默认行为
const {username, password} = this.state;
alert(`您输入的用户名是${username},密码是${password}`);
}
render(){
// 为 form 绑定 onSubmit 事件
// 为 input 绑定 onChange 事件,发生改变就将改变保存到state
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type='text' name='username'/>
密码:<input onChange={this.savePassword} type='password' name='password'/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'));
</script>
3.5.3 高阶函数和函数柯里化
- 高阶函数
- 定义:如果一个函数符合下面2个规范中任意一个,那么该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
- 常见的高阶函数:
- Promise:
new Promise((reolsve, reject)=>{})
- setInterval, setTimeOut:
setTimeOut(function(){}, 1000)
- arr.map() 等等的数组上的方法
- Promise:
- 定义:如果一个函数符合下面2个规范中任意一个,那么该函数就是高阶函数
- 函数柯里化
- 通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式
- 函数柯里化的例子
<div id='test'></div>
<script>
class Login extends React.Component{
// 状态初始
state = {username:'', password:''}
// 高阶函数、函数的柯里化
saveFormData = (dataType) => {
return (event) => {
// 从对象中读取变量的值必须加上[]
this.setState({[dataType]: event.target.value})
}
handleSubmit = (event) => {
event.preventDefault(); // 阻止默认行为
const {username, password} = this.state;
alert(`您输入的用户名是${username},密码是${password}`);
}
render(){
// 内联函数写了括号就是这个函数的返回值作为回调函数
// 必须要把一个函数交个 onChange 作为回调
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveFormData('username')} type='text' name='username'/>
<input onChange={this.saveFormData('password')} type='password' name='password'/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
- 不用函数柯里化也可以实现上述目的
<div id='test'></div>
<script>
class Login extends React.Component{
state = {username:'', password:''} // 状态初始
// 不使用函数的柯里化
saveFormData = (dataType, event) => {
this.setState({[dataType]: event.target.value})
}
handleSubmit = (event) => {
event.preventDefault(); // 阻止默认行为
const {username, password} = this.state;
alert(`您输入的用户名是${username},密码是${password}`);
}
render(){
// 此时 onChange 的回调函数是一个箭头函数
return (
<form onSubmit={this.handleSubmit}>
<input onChange={e => this.saveFormData('username', e)} type='text' name='username'/>
<input onChange={e => this.saveFormData('password', e)} type='password' name='password'/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'))
</script>
3.6 组件生命周期
3.6.1 组件的生命周期(旧)
3.6.1.1 旧的生命周期3个阶段
- 挂载阶段:由 ReactDOM.render() 触发
- constructor()
- componentWillMount()
- render()
- componentDidMount() ===> 常用
- 一般在这个钩子中做一些初始化的事情,例如开启定时器、发送网络请求、订阅消息
- 更新阶段:由 this.setState() 或者父组件重新更新 render() 触发
- componentWillReceivedProps()
- shouldComponentUpdate()
- 组件是否要更新的钩子 如果不写这个函数默认返回true。
- 如果写了这个函数,必须要写返回值,返回值必须是布尔值
- componentWillUpdate()
- render()
- componentDidUpdate()
- 卸载阶段:由 ReactDOM.unmountComponentAtNode() 触发
- componentWillUnmount() ===> 常用
- 一般做一些收尾的事情,例如:关闭定时器、取消订阅消息
- componentWillUnmount() ===> 常用
3.6.1.2 旧的生命周期图例
- 挂载时的生命周期的流程:
constructor
=>componentWillMount
=>render
=>componentDidMount
=>componentWillUnmount
- setState() 正常更新的生命周期流程:
setState()
=>shouldComponentUpdate
=>componentWillUpdate
=>render
=>componentDidUpdate
=>componentWillUnmount
- forceUpdate() 的生命周期流程:
forceUpdate()
=>componentWillUpdate
=>render
=>componentDidUpdate
=>componentWillUnmount
.
- 强制更新:组件的状态不做任何更改,强制更新组件
- 阀门(shouldComponentUpdate)即使关了,强制更新也可以使用 - 父组件重新render的生命周期流程:
componentWillReceiveProps
=>shouldComponentUpdate
=>componentWillUpdate
=>render
=>componentDidUpdate
3.6.2 组件的生命周期(新)
3.6.2.1 新的生命周期3个阶段
- 挂载阶段:由 ReactDOM.render() 触发
- constructor()
- getReceivedStateFromProps(props, state)
- 前边必须加上static关键字
- 此方法适用于罕见的用例(几乎不用)
- 如果state的值都取决于props,可以使用这个函数
- 必须要有返回值,null或者state对象
- render()
- componentDidMount() ==> 常用
- 一般在这个钩子中做一些初始化的事情,例如开启定时器、发送网络请求、订阅消息
- 更新阶段:由 this.setState() 或者父组件重新 render() 触发
- getDerivedStateFromProps()
- shouldComponentUpdate()
- getSnapshotBeforeUpdate()
- 如果写这个钩子,必须同时写componentDidUpdate()这个钩子
- 在更新之前获取快照,此用法并不常见
- render()
- componentDidUpdate()
- 可以接收三个参数:prevProps, prevState, snapshotValue
- 卸载阶段:由 ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount ==> 常用
- 一般做一些收尾的事情,例如:关闭定时器、取消订阅消息
- componentWillUnmount ==> 常用
3.6.2.2 新的生命周期的图例
3.6.2.3 getSnapshotBeforeUpdate()应用例子
<!DOCTYPE html>
<html lang="en">
<head>
<title>getSnapshotBeforeUpdate应用</title>
<style>
.list{
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto
}
.news{ height: 30px; }
</style>
</head>
<body>
<div id='test'><div>
<script>
// 需求:div中不断接收到新的新闻,滚动条
// 但是点击某一条新闻时,固定页面在那个新闻处
class NewsList extends React.Component{
state = {newsArr: []}; // 初始化状态,一个新闻的数组
// 开启定时器,每隔1s添加一个新闻
componentDidMount(){
setInterval(()=>{
const {newsArr} = this.state;
const news = '新闻' + (newsArr.length + 1)
this.setState({newsArr: [news, ...newsArr]})
}, 1000)
}
// 每次更新前,需要确定当前的scrollHeight
getSnapshotBeforeUpdate(){
return this.myRef.scrollHeight;
}
// 在每次新加入新闻后,需要确定div的scrollTop的值使其位置不发生变化
componentDidUpdate(prevProps, prevState, height){
this.myRef.scrollTop += this.myRef.scrollHeight - height
}
render(){
return (
<div ref={c=>this.myRef=c} className='list'>
{
this.state.newsArr.map((value, index)=>{
return <li key={index} className='news'>{value}</li>
})
}
</div>
)
}
}
ReactDOM.render(</NewsList>, document.getElementById('test'))
</script>
</body>
3.6.3 新、旧生命周期的对比和小结
- 重要的钩子
- render():初始化渲染或更新渲染调用
- componentDidMount():开启监听, 发送ajax请求
- componentWillUnmount():做一些收尾工作, 如: 清理定时器
- 新的生命周期中添加的钩子
- getRetrivedStateFromProps()
- getSnapshotBeforeUpdate()
- 从旧的生命周期中删除的钩子
- componentWillMount()
- componentWillUpdate()
- componentWillReceiveProps()
- 注意:现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
3.7 虚拟DOM与DOM Diff算法
3.7.1 经典问题
- 虚拟DOM中key的作用:
- 单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
- 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
- (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到到页面
- a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 用index作为key可能会引发的问题?
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 - 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。 - 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果确定只是简单的展示数据,用index也是可以的。