「本文已参与低调务实优秀中国好青年前端社群的写作活动」。
写在前面
在最近看了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也是可以的。