跟着尚硅谷的天禹老师学习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>

组件生命周期

高阶函数与函数柯里化

上面的代码,如果遇到很多表单项,那就要写很多的事件处理函数,这样编码效率就太低了。

  • 高阶函数:如果一个函数符合下面两个规范中的任意一个,那么这个函数就是高阶函数

    1. 若A函数,接受的参数是一个函数,那么A就可以称为高阶函数
    2. 若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就是一个高阶函数。
常见的高阶函数:

  1. Promise,new Promise(resolve,reject)
  2. setTimeout,setTimeout(callback,delay)
  3. 数组遍历器,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>

总结生命周期

  1. 初始化阶段:由ReactDOM.render()触发 初次渲染

    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount()【常用】 一般在这里初始化,例如:开启定时器、订阅消息、发送网络请求
  2. 更新阶段:由组件内部this.setState()或父组件render触发
    1. shouldComponentUpdate()
    2. componentWillUpdate()
    3. render()【一定会用】
    4. componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentNode触发
    1. componentWillUnmount()【常用】一般在这里做一些收尾的事情,例如:关闭定时器、取消订阅消息。

新旧生命周期对比

新生命周期

新版本也可以兼容旧钩子,只是会报Warning。
componentWillMount、componentWillReceiveProps、componentWillUpdate前面都要加上“UNSAFE_”
也就是除了componentWillUnmount以外,所有带Will的都要加。
这三个声明周期被React官方认为是不安全的,因为目前React官方正在做“异步渲染更新”。UNSAFE前缀并非只安全性有问题,而是在未来版本有可能会出现bug,尤其是在退出异步渲染之后。同时目前也为这三个钩子开启了弃用警告。

  1. componentWillReceiveProps => getDerivedStateProps
  2. render和DidUpdate之间插入了一个getSnapshotBeforeUpdate
  3. “删三增二”,这两个新的好像也很少用。

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>

生命周期总结

  1. 初始化阶段:由React.render()触发——初次渲染

    1. contructor()
    2. getDerivedStateFromProps()
    3. render() 【最重要】
    4. componentDidMount()【常用】
  2. 更新阶段:由组件内部this.setState() 或父组件重新render触发
    1. getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate()
  3. 卸载组件:由React.unmountComponentAtNode触发
    1. 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>

经典面试题

  1. React/Vue中的key有什么作用?(key的内部原理是什么)
  2. 为什么遍历列表时,key最好不要用index
  • 虚拟DOM中key的作用:

    1. 简单地说,key时虚拟DOM对象的标识,在更新视图时key起着极其重要的作用
    2. 详细地说,当状态中的数据发生改变时,React会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
      • 旧虚拟DOM中找到了与新虚拟DOM中相同的key:
        a.若虚拟DOM中内容没变,直接使用之前的真实DOM
        b.若虚拟DOM中的内容改变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
      • 旧虚拟DOM中未找到与新虚拟DOM相同的key
        根据数据创建新的真实DOM,然后渲染到页面
  • 用index作为key可能会引发的问题:
    1. 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
      会产生没有必要的真实DOM更新,即不会影响页面效果,但效率低
    2. 如果结构中还包输入类的DOM:
      会产生错误的DOM更新,即页面会有问题
    3. 注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
  • 开发中如何选择key?:
    1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    2. 如果确定只是见到那的展示数据,用index也是可以的。
    3. 后端不给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相关推荐

  1. day4 vue 学习笔记 组件 生命周期 数据共享 数组常用方法

    系列文章目录 day1学习vue2笔记 vue指令 day2 学习vue2 笔记 过滤器 侦听器 计算属性 axios day3 vue2 学习笔记 vue组件 day4 vue 学习笔记 组件 生命 ...

  2. React 重温之 组件生命周期

    生命周期 任何事物都不会凭空产生,也不会无故消亡.一个事物从产生到消亡经理的各个阶段,我们称之为 生命周期. 具体到我们的前端组件上来,一个组件的生命周期可以大体分为创建.更新.销毁这个三个阶段. 本 ...

  3. React新旧版本生命周期对比

    React新旧版本生命周期对比 ❶ 过时生命周期: ① componentWillMount ② componentWillReceiveProps ③ componentWillUpdate ❷ 即 ...

  4. react学习笔记(8)生命周期回顾与再认识

    生命周期 生命周期是一个组件从创建到销毁的过程. 当组建实例被创建并且插入到DOM中,需要调用的函数,就是生命周期函数. 也就是说,组件加载完成前后.组件更新数据.组件销毁,所触发的一系列的方法. 1 ...

  5. IOS学习笔记——ViewController生命周期详解

    在我之前的学习笔记中讨论过ViewController,过了这么久,对它也有了新的认识和体会,ViewController是我们在开发过程中碰到最多的朋友,今天就来好好认识一下它.ViewContro ...

  6. android学习笔记---36_Activity生命周期

    36_Activity生命周期 ----------------------------- 1.Activity生命周期,用于activity在运行时候受到一些突然事件的影响   ,例如:正在使用一个 ...

  7. Gavin小黑屋——Vue 学习笔记 :生命周期特点(先渲染HTML标签再渲染数据)

    Vue基础   生命周期特点(先渲染HTML标签再渲染数据) 目录 Vue基础   生命周期特点(先渲染HTML标签再渲染数据) 一.Vue生命周期 Vue 的生命周期总共分为8个阶段:创建前/后,载 ...

  8. 小程序入门学习05--幻灯片、页面生命周期

    幻灯片 swiper <view class=''> <!-- swiper幻灯片轮播 默认生成像素150px,image为240 --> <!-- indicator- ...

  9. Android学习之管理Activity的生命周期

    Activity生命周期 activity第一次启动的时候,它来到系统的前台,开始与用户交互.在此期间,Android系统调用了Activity生命周期中一系列的方法.如果用户执行启动了另一个acti ...

  10. Vue学习日记之vue实例生命周期

      在vue实例开始创建.运行到销毁的过程,就是vue的生命周期 vue生命周期中发生(存在)的事件(这些事件可以用函数来表示),我们称之为生命周期钩子. 即:生命周期钩子=生命周期函数=生命周期事件 ...

最新文章

  1. 安装OpenCV:OpenCV 3.0、OpenCV 2.4.8、OpenCV 2.4.9 +VS 开发环境配置
  2. Fabric--使用多通道
  3. java文件全是数字编码_批量将Java源代码文件的编码从GBK转为UTF-8
  4. Codeforces Good Bye 2015 A. New Year and Days 水题
  5. 蔚来事故启示录:被夸大的和被误导的自动驾驶
  6. 车辆抵押贷款风险分析
  7. python所有软件-一款Python黑客打造的勒索软件,让所有国产杀毒软件升起无力感!...
  8. Unity 打包微信
  9. Kiwi Syslog日志服务器的安装及配置使用
  10. Python使用struct处理二进制(pack和unpack用法)
  11. 微信小程序实现拍照功能
  12. jks文件转换成ctr,key文件
  13. Mybatis 解决数据库字段名和实体类属性名不一致问题
  14. tpc ds安装教程 linux,TPC-DS测试hadoop 安装步骤
  15. 计算机应用基础项目化教程答案熊云,计算机应用基础习题与上机实验指导
  16. 什么叫做社交电商,社交电商怎么做?
  17. html表格筛选,js实现表格筛选功能
  18. Java —— 初识集合
  19. 计算机网络安全与防范答辩,最新计算机网络与安全管理专业毕业论文答辩稿演讲自述范文...
  20. 一文读懂高速互联的阻抗及反射

热门文章

  1. Vue绑定事件,双向数据绑定,只是循环没那么简单
  2. 正交实验空白列的理解
  3. Flink教程(02)- Flink入门
  4. 基于mdm9206 threadx_os的adc操作相关API介绍
  5. mysql 远程连接--Host'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server
  6. linearlayout 设置layout_marginRight居右不能生效的解决方法
  7. 聪明来自学习和生活--西奥多·鲍威尔
  8. Python开发一个短网址生成器
  9. 蕾哈娜携手LVMH推出高端時尚品牌FENTY
  10. 15.以太坊智能合约是什么?