(非)受控组件-生命周期
一、受控组件
text,password,checkbox,radio,select
表单项中的值它受到state数据来进行控制,并且还要通过onChange事件来对state数据进行修改
react中表单数所的收集,它没有像vue中的v-model
- text\password
第一种写法
import React, { Component } from 'react'
class App extends Component {
state = {
name: '',
pass: ''
}
setName = e => {
this.setState({ name: e.target.value })
}
setPass = e => {
this.setState({ pass: e.target.value })
}
loginHandle = () => {
console.log(this.state)
}
render() {
const { name, pass} = this.state
return (
<div>
<div>
账号:
{/*
受控组件 value => this.state中的数据
onChange事件来修改this.state中的数据
注:如果你只是用一下state中的数据来给表单项设置值 defaultValue
*/}
{/* <input type='text' defaultValue='aaa' /> */}
<input type='text' value={name} onChange={this.setName} />
</div>
<div>
密码:
<input type='text' value={pass} onChange={this.setPass} />
</div>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
这种写法可以看出setNum和setPass很相似,可以优化成下面的方式
import React, { Component } from 'react'
class App extends Component {
state = {
uname: '',
pass: ''
}
setFieldValue = e => {
// key它是一个变量,把变量的值给当前state对象中的key要用[]
const key = e.target.name
const value = e.target.value
this.setState({ [key]: value })
}
loginHandle = () => {
console.log(this.state)
}
render() {
const { uname, pass } = this.state
return (
<div>
<div>
账号:
<input type='text' name='uname' value={uname} onChange={this.setFieldValue} />
</div>
<div>
密码:
<input type='text' name='pass' value={pass} onChange={this.setFieldValue} />
</div>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
- checkout\radio\select
import React, { Component } from 'react'
class App extends Component {
lessons = ['html', 'css', 'js', 'vue', 'react', '鸿蒙']
state = {
uname: '',
isChecked: false,
selectedLessons: [],
sex: '1',
city: 0
}
setUname = e => {
const value = e.target.value
this.setState({ uname: value })
}
setChecked = e => {
let isChecked = e.target.checked
let selectedLessons = []
if (isChecked) {
selectedLessons = this.lessons
}
this.setState({ isChecked, selectedLessons })
}
// 选中
selectedLessonHandle = item => e => {
const checked = e.target.checked
// 勾选中时才添加
if (checked) {
this.setState(state => ({
selectedLessons: [...state.selectedLessons, item]
}))
} else {
this.setState(state => ({
selectedLessons: state.selectedLessons.filter(val => val != item)
}))
}
}
setSex = e => {
this.setState({ sex: e.target.value })
}
setCity = e => {
// console.log(typeof e.target.value)
this.setState({ city: Number(e.target.value) })
}
loginHandle = () => {
console.log(this.state)
}
render() {
const { uname, isChecked, selectedLessons, sex, city } = this.state
return (
<div>
<div>
账号:
<input type='text' value={uname} onChange={this.setUname} />
</div>
<div>
<input type='checkbox' checked={isChecked} onChange={this.setChecked} />
</div>
<div>
<ul>
{this.lessons.map((item, index) => (
<li key={index}>
<input
type='checkbox'
value={item}
checked={selectedLessons.includes(item)}
onChange={this.selectedLessonHandle(item)}
/>
<span>{item}</span>
</li>
))}
</ul>
</div>
<div>
<label>
<input type='radio' value='1' checked={sex === '1'} onChange={this.setSex} />男
</label>
<label>
<input type='radio' value='2' checked={sex === '2'} onChange={this.setSex} />女
</label>
</div>
<div>
<select value={city} onChange={this.setCity}>
<option value={0}>请选择</option>
<option value={1}>北京</option>
<option value={2}>上海</option>
</select>
</div>
<br />
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
二、非受控组件
没有和state数据源进行关联的表单项而是借助ref属性,使用元素DOM方式获取表单项值
调用步骤:
调用 React.createRef() 方法创建ref对象
将创建好的 ref 对象添加到文本框中
通过ref对象获取到文本框的值
非受控组件:value值和state属性没有关系
非受控组件,在后台或表单项比较多的情况下,一般不用,多用它来获取dom
通过表单项中的ref属性来获取它对应的dom对象来完成对数据的收集
你也可以通过原生js来获取dom,完成数据的收集
你也可以通过事件对象来获取dom,完成数据的收集
在类组件中,得到ref对象,可以通过 createRef方法来得到ref对象,还可以通过回调函数得到
import React, { Component, createRef } from 'react'
class App extends Component {
unameRef = createRef()
passRef = createRef()
loginHandle = () => {
console.log(this.unameRef.current.value)
console.log(this.passRef.current.value)
}
render() {
return (
<div>
<h3>用户管理</h3>
<div>
账号:
<input ref={this.unameRef} />
</div>
<div>
密码:
<input ref={this.passRef} />
</div>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
回调函数得到ref,不建议使用,推荐使用createRef
import React, { Component } from 'react'
class App extends Component {
loginHandle = () => {
console.log(this.unameRef.value)
console.log(this.passRef.value)
}
render() {
return (
<div>
<h3>用户管理</h3>
<div>
账号:
{/* 回调函数的方式,不太建议大家去使用,而是推荐用createRef */}
<input ref={dom => (this.unameRef = dom)} />
</div>
<div>
密码:
<input ref={dom => (this.passRef = dom)} />
</div>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
通过createRef的方式可以优化,可以对表单进行封装
import React, { Component, createRef } from 'react'
class App extends Component {
loginFormRef = {
unameRef: createRef(),
passRef: createRef()
}
loginHandle = () => {
const data = Object.keys(this.loginFormRef).reduce((prev, key) => {
prev[key.slice(0, -3)] = this.loginFormRef[key].current.value
return prev
}, {})
console.log(data)
}
render() {
const { unameRef, passRef } = this.loginFormRef
return (
<div>
<h3>用户管理</h3>
<div>
账号:
<input ref={unameRef} />
</div>
<div>
密码:
<input ref={passRef} />
</div>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
- 批量获取ref的dom对象
import React, { Component, createRef } from 'react'
class App extends Component {
state = {
arr: [1, 2, 3]
}
liDom = []
getLiDom = el => {
if (el) {
this.liDom.push(el)
}
}
del = (val, index) => {
this.setState(state => ({ arr: state.arr.filter(item => item !== val) }))
// 异步宏任务 -- 类似nextTick
setTimeout(() => {
this.liDom.splice(index, 1)
console.log(this.liDom.length)
}, 100)
}
loginHandle = () => {
console.log(this.liDom)
}
render() {
const { arr } = this.state
return (
<div>
<ul>
{arr.map((item, index) => (
<li ref={this.getLiDom} key={item}>
<span>{item}</span>
<button onClick={() => this.del(item, index)}>====</button>
</li>
))}
</ul>
<div>
<button onClick={this.loginHandle}>进入系统</button>
</div>
</div>
)
}
}
export default App
在框架中,能用微任务先用微任务,不能用再用宏任务
微任务有这几个:Promise ,MutationObserver ,requestIdleCallback 获取浏览空闲时间
三、生命周期
函数组件无生命周期,生命周期只有类组件才拥有。
生命周期函数指在某一时刻组件会自动调用并执行的函数。React每个类组件都包含生命周期方法,以便于在运行过程中特定的阶段执行这些方法。例如:我们希望在第一次将其呈现到DOM时设置一个计时器Clock。这在React中称为“安装”。我们也想在删除由产生的DOM时清除该计时器Clock。这在React中称为“卸载”。
参考:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
完整的生命周期图
- constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其它内容前,调用super(props),用来将父组件传来的props绑定到继承类中。只调用一次
- static getDerivedStateFromProps(nextProps,
prevState)
此方法是react16.3之后新增,会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
此方法适用于罕见的用例,即当前组件的 state 的值在任何时候都取决于 props传入。
import React, { Component } from 'react'
class Child extends Component {
// 挂载时
// 如果你在当前组件中使用了构造函数,则一定要调用父类构造函数
// 它只在初始化时执行,所以一般可以用它来完成初始化数据工作
// 父组件优先于子组件先执行它
constructor(props) {
super(props)
this.state = { a: 100 }
console.log('Child -- constructor')
}
// 副作用处理方法
// 它可以接受父组件传过来的新的props数据,还有当前的state数据,然后可以把props数据给映射到当前的state中
// 此方法不能和react过时方法一起使用
// 要用此方法必须要提前声明好state属性
// 它还必须是一个静态方法,静态方法中不能使用this
// 它还必须要有一个返回值 可以是一个对象或null
// + 如果它返回值是一个对象,则它会和state数据进行合并,如果state中没有key新添加,如果有则覆盖
// + 如果它返回值是一个null,表示没有副作作
// jsx插件 gdsfp
// nextProps 新的props数据
// prevState state数据
// 父组件优先于子组件先执行它
static getDerivedStateFromProps(nextProps, prevState) {
// console.log(nextProps, prevState)
console.log('Child -- getDerivedStateFromProps')
// return null
// 把当前的对象和state对象进行合并处理
// return { title: 'abc' }
if (!prevState.num) {
return { ...prevState, ...nextProps }
}
return null
}
render() {
const { num } = this.state
return (
<div>
<h3>Child组件 -- {num}</h3>
<input
type='number'
value={num}
onChange={e => {
this.setState({
num: Number(e.target.value)
})
}}
/>
</div>
)
}
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
num: 100
}
console.log('App -- constructor')
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('App -- getDerivedStateFromProps')
return null
}
render() {
return (
<div>
<h1>App组件</h1>
<hr />
<Child num={this.state.num} />
</div>
)
}
}
export default App
- render()
render()方法是必需的,它主要负责组件的渲染,会被重复调用若干次
- componentDidMount
它会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
import React, { Component } from 'react'
class Child extends Component {
constructor(props) {
super(props)
this.state = { a: 100 }
console.log('Child -- constructor')
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('Child -- getDerivedStateFromProps')
if (!prevState.num) {
return { ...prevState, ...nextProps }
}
return null
}
// 挂载完成时,它只会执行1次
// 在此处写异步请求或获取dom
// cdm
// 先子组件后父组件
componentDidMount() {
console.log('Child -- componentDidMount')
}
// 用于视图渲染 执行N次
// 父组件优先于子组件先执行它
render() {
console.log('Child -- render')
const { num } = this.state
return (
<div>
<h3>Child组件 -- {num}</h3>
<input
type='number'
value={num}
onChange={e => {
this.setState({
num: Number(e.target.value)
})
}}
/>
</div>
)
}
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
num: 100
}
console.log('App -- constructor')
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('App -- getDerivedStateFromProps')
return null
}
componentDidMount() {
console.log('App -- componentDidMount')
}
render() {
console.log('App -- render')
return (
<div>
<h1>App组件</h1>
<hr />
<Child num={this.state.num} />
</div>
)
}
}
export default App
- shouldComponentUpdate(nextProps, nextState)
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true则组件继续渲染,为false则当前组件不会渲染。首次渲染或使用 forceUpdate() 时不会调用该方法。此方法仅作为性能优化的方式而存在。你也可以考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
当**this.setState()**修改了state中的数据后,当前组件将重新渲染,同时也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)
import React, { Component } from 'react'
// 引用类型,可以通过生命周期方法shouldComponentUpdate方法来手动进行判断完成优化
// shouldComponentUpdate true则继续渲染,false阻止渲染
// 可以利用第3方库来完成对对象的深比较
// yarn add lodash
// 尽可能按需加载lodash中的方法
// 完整引入
// import _ from 'lodash'
// isEqual如果两个对象中的所有的值相同返回true,否则false
// 对象深比较会用到递归,性能也不是太好,facebook提供一个库,可以实现更优解
// 不可变数据 immutable/immer
import isEqual from 'lodash/isEqual'
class Child extends Component {
state = {}
static getDerivedStateFromProps(nextProps, prevState) {
// console.log('Child --- getDerivedStateFromProps')
return null
}
// 优化的生命周期
// 返回值 true/false
// nextProps 最新的props数据
// this.props 修改前的props数据
// nextState 最新的state数据
// this.state 修改前的state数据
// scu
// 先父后子
// 如果父传过来的props数据发生了改变,才让子组件更新
shouldComponentUpdate(nextProps, nextState) {
// console.log('Child --- shouldComponentUpdate')
// console.log(this.props, nextProps)
// return true
// if (this.props.num.value === nextProps.num.value) {
// return false
// }
// return true
// console.log()
return !isEqual(this.props, nextProps)
}
render() {
console.log('Child -- render')
return (
<div>
<h3>Child组件 -- {this.props.num.value}</h3>
</div>
)
}
}
class App extends Component {
state = {
count: 200,
num: {
value: 1
}
}
// static getDerivedStateFromProps(nextProps, prevState) {
// console.log('App --- getDerivedStateFromProps')
// return null
// }
// shouldComponentUpdate(nextProps, nextState) {
// console.log('App --- shouldComponentUpdate')
// return true
// }
render() {
console.log('App -- render')
return (
<div>
<h1>App组件 -- {this.state.num.value}</h1>
<button
onClick={e => {
this.setState({
count: this.state.count + 1,
// num: { value: Date.now() }
num: { value: 1 }
})
}}
>
累加一下
</button>
<hr />
<Child num={this.state.num} count={this.state.count} />
</div>
)
}
}
export default App
- getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息,此生命周期的任何返回值将作为参数传递给componentDidUpdate()
import React, { Component } from 'react'
import isEqual from 'lodash/isEqual'
class Child extends Component {
state = {}
// static getDerivedStateFromProps(nextProps, prevState) {
// console.log('Child --- getDerivedStateFromProps')
// return null
// }
// 优化的生命周期
// shouldComponentUpdate(nextProps, nextState) {
// console.log('Child --- shouldComponentUpdate')
// return !isEqual(this.props, nextProps)
// }
// 更新之前快照,可以在此生命周期中获取dom或提前处理一些数据传给componentDidUpdate
// 它的返回值会在componentDidUpdate的第3个参数中获取到
// 如果你要写了它,则一定要有componentDidUpdate
// gsbu
// prevProps 更新之前的props
// this.props 更新之后的props
// prevState 更新之前的state
// this.state 更新之后的state
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('Child --- getSnapshotBeforeUpdate')
return 1000
}
// prevProps 更新之前的props
// this.props 更新之后的props
// prevState 更新之前的state
// this.state 更新之后的state
// snapshot getSnapshotBeforeUpdate的返回值
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('Child --- componentDidUpdate', snapshot)
}
render() {
console.log('Child -- render')
return (
<div>
<h3>Child组件 -- {this.props.num.value}</h3>
</div>
)
}
}
class App extends Component {
state = {
count: 200,
num: {
value: 1
}
}
// static getDerivedStateFromProps(nextProps, prevState) {
// console.log('App --- getDerivedStateFromProps')
// return null
// }
// shouldComponentUpdate(nextProps, nextState) {
// console.log('App --- shouldComponentUpdate')
// return true
// }
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('App --- getSnapshotBeforeUpdate')
return 2000
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('App --- componentDidUpdate', snapshot)
}
render() {
console.log('App -- render')
return (
<div>
<h1>App组件 -- {this.state.num.value}</h1>
<button
onClick={e => {
this.setState({
count: this.state.count + 1,
num: { value: Date.now() }
// num: { value: 1 }
})
}}
>
累加一下
</button>
<hr />
<Child num={this.state.num} count={this.state.count} />
</div>
)
}
}
export default App
- componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作
import React, { Component } from 'react'
class Child extends Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log(Date.now())
}, 1000)
}
// 销毁阶段
// 把当前组件中一个资源清空
//cwun
componentWillUnmount() {
clearInterval(this.timer)
console.log('child -- componentWillUnmount')
}
render() {
return (
<div>
<h3>Child组件</h3>
</div>
)
}
}
class App extends Component {
state = {
count: 200
}
render() {
return (
<div>
<h1>App组件 -- {this.state.count}</h1>
<button
onClick={e => {
this.setState({
count: this.state.count + 1
})
}}
>
累加一下
</button>
<hr />
{this.state.count >= 203 ? null : <Child />}
</div>
)
}
}
export default App
四、优化子组件渲染
import React, { Component, PureComponent } from 'react'
// PureComponent 它可以优化子组件渲染,如果子组件中的使用父组件中的数据没有发生改变或子组件没有使用父组件传过来的数据,此时如果父组件发生了重新渲染,子组件不渲染 -- 减少渲染次数 -- 优化
class Child extends PureComponent {
render() {
console.log('Child -- render')
return (
<div>
<h3>Child组件 -- {this.props.num}</h3>
</div>
)
}
}
class App extends Component {
state = {
num: 1
}
render() {
console.log('App -- render')
return (
<div>
<h1>App组件 -- {this.state.num}</h1>
<button
onClick={e => {
this.setState({
// num: Date.now()
num: 1
})
}}
>
累加一下
</button>
<hr />
<Child num={this.state.num} />
</div>
)
}
}
export default App
import React, { Component, PureComponent } from 'react'
// PureComponent 它可以优化子组件渲染,如果子组件中的使用父组件中的数据没有发生改变或子组件没有使用父组件传过来的数据,此时如果父组件发生了重新渲染,子组件不渲染 -- 减少渲染次数 -- 优化
// PureComponent 它只以对基础数据类型进行判断,进行优化处理,如是是引用类型则无法进行优化
class Child extends PureComponent {
render() {
console.log('Child -- render')
return (
<div>
<h3>Child组件 -- {this.props.num.value}</h3>
</div>
)
}
}
class App extends Component {
state = {
num: {
value: 1
}
}
render() {
console.log('App -- render')
return (
<div>
<h1>App组件 -- {this.state.num.value}</h1>
<button
onClick={e => {
this.setState({
// num: { value: Date.now() }
num: { value: 1 }
})
}}
>
累加一下
</button>
<hr />
<Child num={this.state.num} />
</div>
)
}
}
export default App
五、补充
因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记