【前端学习】React学习笔记-事件、生命周期、虚拟DOMdiffing
跟着尚硅谷的天禹老师学习React
看视频可以直接点击 b站视频地址
React中的事件处理
补充ref
上面的ref在React官网中提到不要被过度使用,在一些情况下可以使用其他方法来获取数据,比如可以通过event.target.value来操作,或者当触发事件的节点恰好是我们要操作的节点时,都可以从event里面拿到数据。或者受控组件也并不需要ref拿数据。
非受控组件
页面中所有输入类DOM的值“现用现取”的组件就叫非受控组件
<div id="container"></div>
<script>// 创建组件class Login extends React.Component{handleSubmit = (event) => {event.preventDefault()const { username, password} = thisalert(`用户名为${username.value},密码为${password.value}`)} render(){return (<form action="#" onSubmit={this.handleSubmit}>用户名:<input ref={(currentNode)=>{this.username = currentNode}} type="text" />密码:<input ref={(currentNode)=>{this.password = currentNode}} type="password" /><button>登录</button></form>)}}// 渲染组件ReactDOM.render(<Login/>,document.getElementById('container'))
</script>
受控组件
随着输入保存到状态中,可以叫作受控组件。
原生也提供了受控组件的写法,onchange事件。
这样做似乎可以减少写ref,但是state变多了。
<div id="container"></div>
<script>// 创建组件class Login extends React.Component{// 初始化状态state = {username:'',password:''}// 表单提交的回调handleSubmit = (event) => {event.preventDefault()const { username, password} = this.state // 这里直接从state里面取了alert(`用户名为${username},密码为${password}`) // 这里把.value给去掉了} // 保存用户名到状态中saveUsername = (event) => {this.setState({username:event.target.value})}// 保存密码到状态中savePassword = (event) => {this.setState({password:event.target.value})}render(){return (<form action="#" onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveUsername} type="text" />密码:<input onChange={this.savePassword} type="password" /><button>登录</button></form>)}}// 渲染组件ReactDOM.render(<Login/>,document.getElementById('container'))
</script>
组件生命周期
高阶函数与函数柯里化
上面的代码,如果遇到很多表单项,那就要写很多的事件处理函数,这样编码效率就太低了。
- 高阶函数:如果一个函数符合下面两个规范中的任意一个,那么这个函数就是高阶函数
- 若A函数,接受的参数是一个函数,那么A就可以称为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称为高阶函数
- 函数柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编写形式。
<div id="container"></div>
<script>// 创建组件class Login extends React.Component{// 初始化状态state = {username:'',password:''}// 表单提交的回调handleSubmit = (event) => {event.preventDefault()const { username, password} = this.state // 这里直接从state里面取了alert(`用户名为${username},密码为${password}`) // 这里把.value给去掉了} // 保存表单数据到状态中,返回一个函数saveFormData(dataType){return (event) => {this.setState({[dataType]:event.target.value // 变量作为key,用中括号})}}render(){return (<form action="#" onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveFormData('username')} type="text" />密码:<input onChange={this.saveFormData('password')} type="password" /><button>登录</button></form>)}}// 渲染组件ReactDOM.render(<Login/>,document.getElementById('container'))
</script>
上面的saveFormData就是一个高阶函数。
常见的高阶函数:
- Promise,new Promise(resolve,reject)
- setTimeout,setTimeout(callback,delay)
- 数组遍历器,map、reduce等等等等
柯里化编程:
function sum(a){return (b)=>{return (c)=>{return a+b+c}}
}
console.log(sum(1)(2)(3))
但我们写的saveFormData实际上也是一个柯里化的函数,对于上面的场景,最合适的就是柯里化编程。
不用柯里化的写法
saveFormData(dataType,value){this.setState({[dataType]:value // 变量作为key,用中括号})}render(){return (<form action="#" onSubmit={this.handleSubmit}>用户名:<input onChange={(event)=>{this.saveFormData('username',event.target.value)}} type="text" />密码:<input onChange={(event)=>{this.saveFormData('password',event.target.value)}} type="password" /><button>登录</button></form>)}
生命周期
概念
生命周期回调函数、生命周期钩子函数 、生命周期函数 、生命周期钩子。
- 组件从创建到死亡会经历一系列的特定阶段
- React组建中包含一系列钩子函数
- 我们在定义组件时,会在特定的钩子中做特定的工作
<div id="container"></div>
<script>// 实现渐变透明度功能// 创建组件// 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子class Life extends React.Component{state = {opacity:1}}death = () => {// 卸载组件ReactDOM.unmountComponentAtNode(document.getElementById('container')))}// 挂载后的生命周期,在初次render后执行,有点像Vue的mountedcomponentDidMount(){// 获取定时器idthis.timer = setInterval(()=>{let { opacity } = this.stateopacity -= 0.1if(opacity <= 0){opacity = 1}this.setState({opacity})},200)}// 组件将要卸载的生命周期,有点像Vue的beforeDestroycomponentWillUnmount(){// 清除定时器clearInterval(this.timer)}// render调用的时机:初始化渲染、状态更新后render(){// 如果直接写在render中,将触发一个无限的递归调用// 定时器->setState->render->定时器// setInterval(()=>{// let { opacity } = this.state// opacity -= 0.1// if(opacity <= 0){// opacity = 1// }// this.setState({opacity})// },200)return (<div><h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2><button onClick={this.death}>不活了</button></div>)}}// 渲染组件ReactDOM.render(<Life/>,document.getElementById('container'))
</script>
旧版生命周期流程图
图截取自视频中
左侧是挂载右侧是更新
<div id="container"></div>
<script>class Father extends React.Component{state = {carName:'奔驰'}render(){return (<div><div>Father组件</div><button onClick={this.changeCar}>换车</button><Son carName={this.carName} /></div>)}}class Son extends React.Component{/*挂载时的三个狗子*/// contructor// 组件将要被挂载时触发的钩子componentWillMount(){console.log('componentWillMount')}// render// 组件挂载完毕的钩子componentDidMount(){console.log('componentDidMount')}// 组件将要卸载的钩子componentWillUnmount(){console.log('componentWillUnmount')}/*更新时的钩子*/// 组件将要接收【新】的propscomponentWillReceiveProps(){// 注意这里在第一次传入props不会触发这个钩子console.log('componentWillReceiveProps')}// 控制setState以后是否更新的“阀门”shouldComponentUpdate(){console.log('shouldComponentUpdate')return true // 这里如果不写布尔类型的返回值就会报错,写false就会阻塞调后面的钩子}// 组件将要更新的钩子componentWillUpdate(){console.log('componentWillUpdate')}// render// 组件更新后的钩子componentDidUpdate(){console.log('componentDidUpdate')}render(){console.log('render')return (<div><div>Son组件</div><div>{this.props.carName}</div></div>)}}// 渲染组件ReactDOM.render(<A/>,document.getElementById('container'))
</script>
总结生命周期
- 初始化阶段:由ReactDOM.render()触发 初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()【常用】 一般在这里初始化,例如:开启定时器、订阅消息、发送网络请求
- 更新阶段:由组件内部this.setState()或父组件render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()【一定会用】
- componentDidUpdate()
- 卸载组件:由ReactDOM.unmountComponentNode触发
- componentWillUnmount()【常用】一般在这里做一些收尾的事情,例如:关闭定时器、取消订阅消息。
新旧生命周期对比
新生命周期
新版本也可以兼容旧钩子,只是会报Warning。
componentWillMount、componentWillReceiveProps、componentWillUpdate前面都要加上“UNSAFE_”
也就是除了componentWillUnmount以外,所有带Will的都要加。
这三个声明周期被React官方认为是不安全的,因为目前React官方正在做“异步渲染更新”。UNSAFE前缀并非只安全性有问题,而是在未来版本有可能会出现bug,尤其是在退出异步渲染之后。同时目前也为这三个钩子开启了弃用警告。
- componentWillReceiveProps => getDerivedStateProps
- render和DidUpdate之间插入了一个getSnapshotBeforeUpdate
- “删三增二”,这两个新的好像也很少用。
getDerivedStateErrorProps
这个生命周期用于一个十分罕见的用例:state的值在任何时候都取决于props。
派生状态会导致代码冗余,并使组件难以维护。确保在以下替代方案中不能实现的情况下在使用这个方法:
- 如果需要执行副作用(比如数据提取或者动画)以响应props中的更改,推荐使用componentDidUpdate
- 如果只想在prop更改时冲i性能计算某些数据,使用memoization helper来代替。
- 如果想在prop更改时“重置”某些state,考虑使用完全受控或使用key使组件完全不受控。
要注意:因为是静态方法,所以无法访问组件实例,如果需要可以通过提取组件props的纯函数和class外的状态,在这个方法和其他class方法之间重用代码。
实现滚动条停留在当前页面,不被挤下去:
// 从props中派生出状态static getDerivedStateFromPros(props){ // 接收props作为参数console.log('getDerivedStateFromPros')return props //// return null // 需要返回一个state对象 要和state里面的key-value对应上// 利用返回值可以修改状态 返回值会替代状态对象}
getSnapshotBeforeUpdate
这个生命周期在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息(比如滚动位置)。此生命周期的任何返回值将作为参数传递给componentDidUpdate()
应该返回一个snapshot或者null。此生命周期并不常用。
<div id="container"></div>
<script>class NewsList extends React.Component{state = {newsArr:[]}componentDidMount(){setInterval(()=>{// 获取原状态const { newsArr } = this.state// 模拟一条新闻const news = `新闻${newsArr.length + 1}`// 更新状态this.setState({newsArr:[news,...newsArr]})},1000)}getSnapshotBeforeUpdate(){return this.refs.list.scrollHeight} componentDidUpdate(oldProps,oldState,height){// 接收height值 实现滚动条停留在当前页面,不被挤下去this.refs.list.scrollTop += this.refs.list.scrollTop - height}render(){return (<div class="list" ref="list">{this.state.newsArr.map((news,index)=>{return <div className="news" key={index} >{ news }</div>})}</div>)}}ReactDOM.render(<NewsList/>,document.getElementById('container'))
</script>
<style>.list{width:200px;height:150px;background-color:skyblue;overflow:auto;}.news{height:30px;}
</style>
下面这段代码可以忽略。
<div id="container"></div>
<script>class Father extends React.Component{state = {carName:'奔驰'}render(){return (<div><div>Father组件</div><button onClick={this.changeCar}>换车</button><Son carName={this.carName} /></div>)}}class Son extends React.Component{/*挂载时的三个狗子*/// contructor// 从props中派生出状态static getDerivedStateFromPros(props,state){ // 接收props作为参数console.log('getDerivedStateFromPros')return props //// return null // 需要返回一个state对象 要和state里面的key-value对应上// 利用返回值可以修改状态 返回值会替代状态对象}// render// 这个生命周期在最近一次渲染输出(提交到DOM节点)之前调用,接收三个参数getSnapshotBeforeUpdate(oldProps,oldState,snapshotValue){}// 组件挂载完毕的钩子componentDidMount(){console.log('componentDidMount')}// 组件将要卸载的钩子componentWillUnmount(){console.log('componentWillUnmount')}/*更新时的钩子*/// 控制setState以后是否更新的“阀门”shouldComponentUpdate(){console.log('shouldComponentUpdate')return true // 这里如果不写布尔类型的返回值就会报错,写false就会阻塞调后面的钩子}// render// 注意这个钩子有两个参数// 组件更新后的钩子 接收两个参数 一个旧的props,一个旧的statecomponentDidUpdate(oldProps,oldState){console.log('componentDidUpdate')}render(){console.log('render')return (<div><div>Son组件</div><div>{this.props.carName}</div></div>)}}// 渲染组件ReactDOM.render(<A/>,document.getElementById('container'))
</script>
生命周期总结
- 初始化阶段:由React.render()触发——初次渲染
- contructor()
- getDerivedStateFromProps()
- render() 【最重要】
- componentDidMount()【常用】
- 更新阶段:由组件内部this.setState() 或父组件重新render触发
- getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- 卸载组件:由React.unmountComponentAtNode触发
- componentWiilUnmount() 【常用】
React的diffing算法
现在这段代码,如果每次React都会渲染的话,则当在input中输入时,input会在1s内刷新,不能完成输入。显然React并没有重新渲染这一个组件的全部,而是把span标签中的文字重新渲染了。
<div id="container"></div>
<script>class Time extends React.Component{state = {date:new Date()}componentDidMount(){setInterval(()=>{this.setState({date:new Date()})},1000)}render(){return (<div><h1>hello</h1><input type="text" /><span>现在是:{this.state.date.toTimeString()}</span></div>)}}
ReactDOM.render(<Time/>,document.getElementById('container'))
</script>
经典面试题
- React/Vue中的key有什么作用?(key的内部原理是什么)
- 为什么遍历列表时,key最好不要用index
- 虚拟DOM中key的作用:
- 简单地说,key时虚拟DOM对象的标识,在更新视图时key起着极其重要的作用
- 详细地说,当状态中的数据发生改变时,React会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM中相同的key:
a.若虚拟DOM中内容没变,直接使用之前的真实DOM
b.若虚拟DOM中的内容改变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM - 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,然后渲染到页面
- 旧虚拟DOM中找到了与新虚拟DOM中相同的key:
- 用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
会产生没有必要的真实DOM更新,即不会影响页面效果,但效率低 - 如果结构中还包输入类的DOM:
会产生错误的DOM更新,即页面会有问题 - 注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
- 开发中如何选择key?:
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
- 如果确定只是见到那的展示数据,用index也是可以的。
- 后端不给key那就只能打他了
<div id="container"></div>
<script>class Person extends React.Component{/*使用index(索引值)作为key初始数据:{id:1,name:'zhang',age:18}{id:2,name:'li',age:19}初始化虚拟DOM:<li key=0>zhang--18<input type="text"/></li><li key=1>li--19<input type="text"/></li>更新后的数据:{id:3,name:'wang',age:20}{id:1,name:'zhang',age:18}{id:2,name:'li',age:19}更新后的虚拟DOM:<li key=0>wang--20<input type="text"/></li><li key=1>zhang--18<input type="text"/></li><li key=2>li--19<input type="text"/></li>结果:触发了三次视图更新,如果是很多数据,这是非常消耗效率的包含输入类组件时,添加新的数据后,直接错误,原来zhang的输入信息被添加到了wang的节点上,li的输入信息消失了*//*使用id(数据唯一标识)作为key初始数据:{id:1,name:'zhang',age:18}{id:2,name:'li',age:19}初始化虚拟DOM:<li key=1>zhang--18<input type="text"/></li><li key=2>li--19<input type="text"/></li>更新后的数据:{id:3,name:'wang',age:20}{id:1,name:'zhang',age:18}{id:2,name:'li',age:19}更新后的虚拟DOM:<li key=3>wang--20<input type="text"/></li><li key=1>zhang--18<input type="text"/></li><li key=2>li--19<input type="text"/></li>结果:只触发了一次视图更新*/state = {persons:{id:1,name:'zhang',age:18,id:2,name:'li',age:19}}add = () => {const {persons} = this.stateconst p = {id:persons.length+1,name:'wang',age:20}this.setState({persons:[p,...persons]})}componentDidMount(){}render(){return (<div><h1>使用index(索引值)来作为key</h1><button onClick={this.addWang}>添加一个小王</button><ul>{this.state.persons.map((person,index)=>{return <li key={index}>{person.name}--{person.age} <input type="text"/></li>})}</ul><h1>使用id(数据唯一标识)来作为key</h1><button onClick={this.addWang}>添加一个小王</button><ul>{this.state.persons.map((person,index)=>{return <li key={person.id}>{person.name}--{person.age} <input type="text"/></li>})}</ul></div>)}}
ReactDOM.render(<Person/>,document.getElementById('container'))
</script>
可以理解一下,当添加了一个新的person后,新key为1,发现内容全变了,然后是key为2,内容也完全变了,key为3的是新的依然要重新渲染。如果处理合适的话,其实只需要渲染的是那个新添加的person。
【前端学习】React学习笔记-事件、生命周期、虚拟DOMdiffing相关推荐
- day4 vue 学习笔记 组件 生命周期 数据共享 数组常用方法
系列文章目录 day1学习vue2笔记 vue指令 day2 学习vue2 笔记 过滤器 侦听器 计算属性 axios day3 vue2 学习笔记 vue组件 day4 vue 学习笔记 组件 生命 ...
- React 重温之 组件生命周期
生命周期 任何事物都不会凭空产生,也不会无故消亡.一个事物从产生到消亡经理的各个阶段,我们称之为 生命周期. 具体到我们的前端组件上来,一个组件的生命周期可以大体分为创建.更新.销毁这个三个阶段. 本 ...
- React新旧版本生命周期对比
React新旧版本生命周期对比 ❶ 过时生命周期: ① componentWillMount ② componentWillReceiveProps ③ componentWillUpdate ❷ 即 ...
- react学习笔记(8)生命周期回顾与再认识
生命周期 生命周期是一个组件从创建到销毁的过程. 当组建实例被创建并且插入到DOM中,需要调用的函数,就是生命周期函数. 也就是说,组件加载完成前后.组件更新数据.组件销毁,所触发的一系列的方法. 1 ...
- IOS学习笔记——ViewController生命周期详解
在我之前的学习笔记中讨论过ViewController,过了这么久,对它也有了新的认识和体会,ViewController是我们在开发过程中碰到最多的朋友,今天就来好好认识一下它.ViewContro ...
- android学习笔记---36_Activity生命周期
36_Activity生命周期 ----------------------------- 1.Activity生命周期,用于activity在运行时候受到一些突然事件的影响 ,例如:正在使用一个 ...
- Gavin小黑屋——Vue 学习笔记 :生命周期特点(先渲染HTML标签再渲染数据)
Vue基础 生命周期特点(先渲染HTML标签再渲染数据) 目录 Vue基础 生命周期特点(先渲染HTML标签再渲染数据) 一.Vue生命周期 Vue 的生命周期总共分为8个阶段:创建前/后,载 ...
- 小程序入门学习05--幻灯片、页面生命周期
幻灯片 swiper <view class=''> <!-- swiper幻灯片轮播 默认生成像素150px,image为240 --> <!-- indicator- ...
- Android学习之管理Activity的生命周期
Activity生命周期 activity第一次启动的时候,它来到系统的前台,开始与用户交互.在此期间,Android系统调用了Activity生命周期中一系列的方法.如果用户执行启动了另一个acti ...
- Vue学习日记之vue实例生命周期
在vue实例开始创建.运行到销毁的过程,就是vue的生命周期 vue生命周期中发生(存在)的事件(这些事件可以用函数来表示),我们称之为生命周期钩子. 即:生命周期钩子=生命周期函数=生命周期事件 ...
最新文章
- 安装OpenCV:OpenCV 3.0、OpenCV 2.4.8、OpenCV 2.4.9 +VS 开发环境配置
- Fabric--使用多通道
- java文件全是数字编码_批量将Java源代码文件的编码从GBK转为UTF-8
- Codeforces Good Bye 2015 A. New Year and Days 水题
- 蔚来事故启示录:被夸大的和被误导的自动驾驶
- 车辆抵押贷款风险分析
- python所有软件-一款Python黑客打造的勒索软件,让所有国产杀毒软件升起无力感!...
- Unity 打包微信
- Kiwi Syslog日志服务器的安装及配置使用
- Python使用struct处理二进制(pack和unpack用法)
- 微信小程序实现拍照功能
- jks文件转换成ctr,key文件
- Mybatis 解决数据库字段名和实体类属性名不一致问题
- tpc ds安装教程 linux,TPC-DS测试hadoop 安装步骤
- 计算机应用基础项目化教程答案熊云,计算机应用基础习题与上机实验指导
- 什么叫做社交电商,社交电商怎么做?
- html表格筛选,js实现表格筛选功能
- Java —— 初识集合
- 计算机网络安全与防范答辩,最新计算机网络与安全管理专业毕业论文答辩稿演讲自述范文...
- 一文读懂高速互联的阻抗及反射
热门文章
- Vue绑定事件,双向数据绑定,只是循环没那么简单
- 正交实验空白列的理解
- Flink教程(02)- Flink入门
- 基于mdm9206 threadx_os的adc操作相关API介绍
- mysql 远程连接--Host'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server
- linearlayout 设置layout_marginRight居右不能生效的解决方法
- 聪明来自学习和生活--西奥多·鲍威尔
- Python开发一个短网址生成器
- 蕾哈娜携手LVMH推出高端時尚品牌FENTY
- 15.以太坊智能合约是什么?