写在前面
在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。
那就让我们开始吧!
非受控组件
非受控组件(现用现取)使用ref将标签取到类的实例上
1. 绑定的类的实例上的函数会接受一个event的参数。可以使用event.preeventDefault()来阻止表单的默认提交。
<script type="text/babel"> //创建组件 class Login extends React.Component{ handleSubmit = (event)=>{ event.preventDefault() //阻止表单提交 const {username,password} = this alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`) } render(){ return( <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> 复制代码
受控组件
- 双向绑定
使用onChange事件函数调用方法然后将even.target传入状态state中
可以根据输入实时更新状态。
<script type="text/babel"> //创建组件 class Login extends React.Component{ //初始化状态 state = { username:'', //用户名 password:'' //密码 } //保存用户名到状态中 saveUsername = (event)=>{ this.setState({username:event.target.value}) } //保存密码到状态中 savePassword = (event)=>{ this.setState({password:event.target.value}) } //表单提交的回调 handleSubmit = (event)=>{ event.preventDefault() //阻止表单提交 const {username,password} = this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) } render(){ 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> 复制代码
#高阶函数_函数柯里化
解决代码重复度
原因:初始化调用render的时候如果回调函数有()就会立即调用,那么回调的也就是函数的返回值,不在是函数了,但是如果不加()又不能传参。
解决方法
- 高阶函数
- 让函数的返回值也是一个函数
这样回调的函数就是那个返回的函数了。
- 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。常见的高阶函数有:Promise、setTimeout、arr.map()等等 - 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){ return(b)=>{ return (c)=>{ return a+b+c } } } const result = sum(1)(2)(3) console.log(result); 复制代码
对象的相关知识
- 变量做为属性名需要使用[]。
不用柯里化的写法
使用箭头函数调用需要调用的函数
用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/> 复制代码
引出生命周期
挂载
传送门
zh-hans.reactjs.org/docs/react-…
卸载
- 卸载组件
death = ()=>{ //卸载组件 ReactDOM.unmountComponentAtNode(document.getElementById('test')) } 复制代码
- 书写定时器
- 不能放在render方法内部
原因:使用定时器更新状态,会导致再次调用redner方法,随后就会再次调用定时器,这样就会死循环。
- 将定时器绑定在类式组件上进行回调
缺点:必须通过点击事件才能回调定时器
目标:组件一旦挂载就自动触发定时器
3.使用componentDidMount()
在React创建组件实例对象时就会调用render方法和componentDidMount方法
//组件挂完毕 componentDidMount(){ console.log('componentDidMount'); this.timer = setInterval(() => { //获取原状态 let {opacity} = this.state //减小0.1 opacity -= 0.1 if(opacity <= 0) opacity = 1 //设置新的透明度 this.setState({opacity}) }, 200); } 复制代码
- 关闭定时器
- 在卸载了组件之后,定时器仍在运行。
解决方式
- 在death方法中卸载组件前,提前将定时器关闭。
death = ()=>{ clearInterval(this.timer) //卸载组件 ReactDOM.unmountComponentAtNode(document.getElementById('test')) } 复制代码
2.使用componentWillUnmount()
//组件将要卸载 componentWillUnmount(){ //清除定时器 clearInterval(this.timer) } 复制代码
- 生命周期概念
传送门
zh-hans.reactjs.org/docs/state-…
生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
生命周期(旧)_组件挂载流程
理解
1. 组件从创建到死亡它会经历一些特定的阶段。
2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
生命周期流程图(旧)
挂载
- 执行顺序
constructor->componentWillMount->render->componentDidMount
生命周期(日) setState流程
componentWillUnmount()
在组件将要卸载的时候调用的“钩子”
shouldComponentUpdate()
- shouldComponentUpdate()是一个“阀门”
1.在使用setState()之后调用shouldComponentUpdate()判断是否进行下一步。
//控制组件更新的“阀门” shouldComponentUpdate(){ console.log('Count---shouldComponentUpdate'); return true } 复制代码
- 如果不写,默认返回true
setState流程
shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()
生命周期(旧)_ forceUpdate流程
- 不改变状态的修改,强制更新。(不经过shouldComponentUpdate())
执行流程
forceUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()
生命周期(旧)_父组件render流程
父子组件
//父组件A class A extends React.Component{、 render(){ return( <div> <div>我是A组件</div> <B /> </div> ) } } //子组件B class B extends React.Component{ render(){ return( <div>我是B组件,接收到的车是:{this.props.carName}</div> ) } } 复制代码
- 实现结果
组件将要接收新的props的钩子
//组件将要接收新的props的钩子 componentWillReceiveProps(props){ console.log('B---componentWillReceiveProps',props); } 复制代码
componentWillReceiveProps()
- 第一次组件接受props并不会调用这个钩子。
- 父组件状态更新调用了render方法,导致子组件重新接受props。就会调用这个钩子。
执行流程
父组件render-> componentWillReceiveProps() -> shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()
总结 生命周期(旧)
一. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor() 2. componentWillMount() 3. render() 4. componentDidMount() =====> 常用 复制代码
- 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
二. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate() 2. componentWillUpdate() 3. render() =====> 必须使用的一个 4. componentDidUpdate() 复制代码
三. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用 复制代码
- 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
第43集 对比新旧生命周期
下载依赖包
- react-dom.development.js和react.development.js 版本都相同。
- cdn链接
react.docschina.org/docs/cdn-li…
- BootCDN
前端常用的js库的地址
传送门
新生命周期
新版本使用旧钩子
即将过时的钩子
传送门
react.docschina.org/docs/react-…
componentWillReceiveProps()、componentWillMount()、componentWillUpdate()这3个旧钩子都得添加UNSAFE_。不然会报错。
新旧更替
将旧版本的componentWillReceiveProps()、componentWillMount()、componentWillUpdate()更换为了getDrivedStateFormProps()。并且在调用了setState之后也会调用这个钩子。其他的父组件render、挂载时、强制更新forceUpdate()之后都会执行上面的新钩子,而不在执行之前带有Will的旧钩子。
getDerivedStateFromProps()
添加static
- 这个钩子得添加到类的原型上。
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps static getDerivedStateFromProps(props,state){ console.log('getDerivedStateFromProps',props,state); return null } 复制代码
必须返回state object 或者 null
- 返回状态对象会影响状态更新
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps static getDerivedStateFromProps(props,state){ console.log('getDerivedStateFromProps',props,state); return {count:100} } 复制代码
- 这样就无法修改这个状态值。
- 使用场景
state 的值在任何时候都取决于 props。
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps static getDerivedStateFromProps(props,state){ console.log('getDerivedStateFromProps',props,state); return null } 复制代码
传送门
react.docschina.org/docs/react-…
- 可以接收state。
static getDerivedStateFromProps(props, state) 复制代码
将props、state里面的属性可以进行对比,按需选择使用。
getSnapshotBeforeUpdate()
返回值
- 必须是返回一个值,或者null
//组件更新完毕的钩子 componentDidUpdate(preProps,preState,snapshotValue){ console.log('Count---componentDidUpdate',preProps,preState,snapshotValue); } 复制代码
- 实现效果
使用场景
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
。
getSnapshotBeforeUpdate(prevProps, prevState) 复制代码
getSnapshotBeforeUpdate举例
新闻列表
属性复习
菜鸟教程传送门
- scrollTop() 方法返回或设置匹配元素的滚动条的垂直位置。
- scrollHeight 属性是一个只读属性,它返回该元素的像素高度,高度包含内边距(padding),不包含外边距(margin)、边框(border),是一个整数,单位是像素 px。
使用定时器动态更新状态
- 使用定时器更新状态
state = {newsArr:[]} componentDidMount(){ setInterval(() => { //获取原状态 const {newsArr} = this.state //模拟一条新闻 const news = '新闻'+ (newsArr.length+1) //更新状态 this.setState({newsArr:[news,...newsArr]}) }, 1000); } 复制代码
- 使用getSnapshotBeforeUpdate()记录之前的scrollHeight
getSnapshotBeforeUpdate(){ return this.refs.list.scrollHeight } 复制代码
- 使用componentDidUpdate记录更新后的scrollHeight。
将scrollTop 不断累加前后直接的scrollHeight差值。就可以保持滑动框不变。
componentDidUpdate(preProps,preState,height){ this.refs.list.scrollTop += this.refs.list.scrollHeight - height } 复制代码
生命周期总结
- 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor() 2. getDerivedStateFromProps 3. render() 4. componentDidMount() =====> 常用 复制代码
- 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps 2. shouldComponentUpdate() 3. render() 4. getSnapshotBeforeUpdate 5. componentDidUpdate() 复制代码
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用 复制代码
- 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
重要的勾子
1. render:初始化渲染或更新渲染调用
2. componentDidMount:开启监听, 发送ajax请求
3. componentWillUnmount:做一些收尾工作, 如: 清理定时器
即将废弃的勾子
1. componentWillMount
2. componentWillReceiveProps
3. componentWillUpdate
DOM的diffing算法
复用的最小单位还是标签,如果标签中嵌套了标签,那么如果使用index就有可能会导致部分的标签被服用。但是id可以完全避免这个情况。因为一个标签一个id。
- 官网传送门
zh-hans.reactjs.org/docs/reconc…
key的作用?
经典面试题: 1). react/vue中的key有什么作用?(key的内部原理是什么?) 2). 为什么遍历列表时,key最好不要用index?
- 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key: (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM b. 旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随后渲染到到页面 复制代码
- 用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
- 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。