“这是我参与更文挑战的第4天,活动详情查看: 更文挑战”
前言
React为类组件提供了一系列的生命周期钩子函数,有constructor
、getDerivedStateFromProps
、render
、componentDidMount
、shouldComponentUpdate
、getSnapshotBeforeUpdate
、componentDidUpdate
、componentWillUnmount
。但是在函数组件中无法使用些钩子函数,故得使用React Hook模拟这些钩子函数。
1、模拟constructor
在函数式组件中是没有constructor
的概念,函数组件不需要构造函数,回顾一下类组件需要在constructor
函数中实现那些任务。
- 在其他语句之前前调用
super(props)
,否则this.props
为undefined; - 通过给
this.state
赋值对象来初始化内部state; - 为事件处理函数绑定实例,否在函数中无法使用
this
。
在函数组件中组件props可以通过函数的参数传入,利用 ES6 解构赋值的功能,来获取组件的参数数据,并可以给参数数据设置默认值。
import React from 'react';
const Index = (props) =>{
const {title = 'hellow React'} = props;
return(
<div>{title}</div>
)
}
export default Index;
复制代码
通过useState
这个Hook来初始化state,const [state, setState] = useState(initialState)
,其中initialState
就是一个state的初始值。
假如state的初始值需要经过一系列复杂的计算才能得到,可以传一个函数给useState
。
import React, { useState } from 'react';
const HelloWorld = (props) =>{
const {num} = props;
const getAamout = (num) =>{
return num + 1;
}
const [amount,serAmount] = useState(() =>{return getAamout(num)})
return(
<div>{amount}</div>
)
}
export default HelloWorld;
复制代码
state的初始值在组件挂载时才会用到,为了避免组件更新时再次调用getAamout
函数获取amount
初始值,故把getAamout
函数包裹在() =>{return getAamout(num)}
中,再传递给useState
。
2、模拟getDerivedStateFromProps
getDerivedStateFromProps
钩子函数的作用是用组件的props派生出一个新的state,且这个state受props完全控制。
例如用num
的prop派生出一个amount
的state,先要把num
的prop上一轮的值存在一个prevNum
的state变量中以便比较。
import React, { useState } from 'react';
const HelloWorld = (props) =>{
const {num} = props;
const [prevNum, setPrevNum] = useState(null);
const [amount,serAmount] = useState(0);
if (num !== prevNum) {
serAmount(num+1);
setPrevNum(num);
}
return(
<div>{amount}</div>
)
}
export default HelloWorld;
复制代码
组件更新时,都会执行if (num !== prevNum)
,若num
和prevNum
不相等,就会执行serAmount(num+1)
更新amount
这个state,且执行setPrevNum(num)
把当前num
这个prop存到prevNum
这个state中。
以上就是用 React Hook 模拟了getDerivedStateFromProps
钩子函数的过程。
3、模拟render
render
这是函数组件体本身。
4、模拟componentDidMount
用useEffect
来模拟,因为componentDidMount
钩子函数在组件的生命周期中只执行一次,那么要执行只运行一次的useEffect
(仅在组件挂载时执行),可以传递一个空数组([]
)作为第二个参数给useEffect
,而第一个参数接收一个函数,在函数中执行和componentDidMount
钩子函数中执行的事项。
import React, {useState,useEffect } from 'react';
const HelloWorld = (props) =>{
const [amount,serAmount] = useState(0);
useEffect(() =>{
console.log('相当组件执行componentDidMount钩子函数')
console.log(amount)
}, [] )
return(
<div>{amount}</div>
)
}
export default HelloWorld;
复制代码
6、模拟shouldComponentUpdate
shouldComponentUpdate
钩子函数作用是在组件更新阶段,通过对比前后的state和props是否有变化,若有变化,在函数最后return true
让组件更新,若是没有变化,在函数最后return false
让组件不更新,这是一个用来优化性能的钩子函数。在函数组件中有三种方法可以实现shouldComponentUpdate
钩子函数的功能,下面一一来介绍。
6.1 用React.memo模拟
React.memo
包裹一个组件来对它的props进行比较,而且这个组件只能是函数组件。当组件的props发生变化才会去更新这个组件。
React.memo
仅检查包裹的组件的props变更,不比较组件的state,若组件的state发生变更,无法阻止组件更新。
import React, { useEffect } from 'react';
const HelloWorld = React.memo((props) => {
const { num } = props;
useEffect(() => {
console.log('更新')
})
return (
<div>{num}</div>
)
})
export default HelloWorld;
复制代码
其中对props的比较只是浅比较,如果要进行深比较,可以自定义的比较函数通过第二个参数传入React.memo
来实现。比较函数接收变化前后的props作为参数。
import React, {useEffect } from 'react';
const HelloWorld = React.memo((props) =>{
const {num} = props;
useEffect(() =>{
console.log('更新')
})
return(
<div>{num}</div>
)
},(prevProps, nextProps) =>{
//更新
return false
//不更新
return true
})
export default HelloWorld;
复制代码
当比较函数返回true
则不更新,返回false
则更新,这跟shouldComponentUpdate
钩子函数的返回是相反的,要注意一下。
6.2 用useMemo模拟
useMemo(() => fn, deps)
复制代码
useMemo
的第一参数是个函数,第二参数是数组,里面是要监听的数据,当数据发生改变时,重新执行第一参数。另外useMemo
返回的是第一参数执行后返回的值。
若在第一参数函数中返回一个组件,那这个组件的更新由第二参数数组中的数据来控制,这样也间接实现了shouldComponentUpdate
钩子函数。
但是由于在useMemo
中不能使用useState
定义组件的state,所以还是只能比较props来决定是否更新组件。
import React from 'react';
const HelloWorld = (props) =>{
const {num} = props;
return (
<div>{num}</div>
)
}
export default HelloWorld;
复制代码
import React,{useMemo,useState} from 'react';
import HelloWorld from './HelloWorld';
const Index = (props) =>{
const [num,setNum] = useState(1)
const Hello = useMemo(()=>{
return (
<HelloWorld num={num}></HelloWorld>
)
},[num])
return (
<div>
{Hello}
</div>
)
}
export default Index;
复制代码
上面的HelloWorld组件,只有在num
这个prop改变时才会更新。
使用
useMemo
包裹一个组件时,组件中有用到的props,一定添加到第二参数的数组中,否则当props更新了,组件中props还是旧数据。
6.3 用useCallback模拟
useCallback
和useMemo
的区别是。useCallback
返回的是一个函数,useMemo
返回的是一个值。
useCallback
的第一个参数是个函数,useCallback
返回的就是这个函数。第二参数是数组,里面是要监听的数据,当数据发生改变时,才会重新返回第一参数。
若在第一参数函数是一个函数组件,那这个组件的更新由第二参数数组中的数据来控制,这样也间接实现了shouldComponentUpdate
钩子函数。
import React from 'react';
const HelloWorld = (props) =>{
const {num} = props;
return (
<div>{num}</div>
)
}
export default HelloWorld;
复制代码
import React,{useCallback,useState} from 'react';
import HelloWorld from './HelloWorld';
const Index = (props) =>{
const [num,setNum] = useState(1)
const Hello = useCallback(()=>{
return (
<HelloWorld num={num}></HelloWorld>
)
},[num])
return (
<div>
{Hello()}
</div>
)
}
export default Index;
复制代码
上述分别用useMemo
和useCallback
来处理HelloWorld组件,返回Hello
。唯一不同的是用useCallback
处理时,要这样{Hello()}
使用HelloWorld组件。
useCallback(fn, deps)
相当于useMemo(() => fn, deps)
。
使用
useCallback
包裹一个组件时,组件中有用到的props,一定添加到第二参数的数组中,否则当props更新了,组件中props还是旧数据。
7、模拟getSnapshotBeforeUpdate
官方解释:目前还没有
getSnapshotBeforeUpdate
钩子函数的 Hook 等价写法,但很快会被添加。
8、模拟componentDidUpdate
用useEffect
来模拟,useEffect
第一参数接受一个函数,第二参数是个数组,把要监听的props或state添加进去。当监听的props或state发生变化时,useEffect
的第一参数就会执行。可以把componentDidUpdate
钩子函数中要执行的代码,放在useEffect
的第一参数函数中执行。
import React,{useState} from 'react';
const HelloWorld = (props) =>{
const [num,setNum]=useState(1);
const changNum = () =>{
setNum(2);
}
useEffect(() =>{
console.log('num数据发生变化')
},[num])
return (
<div onClick={changNum}>{num}</div>
)
}
export default HelloWorld;
复制代码
9、模拟componentWillUnmount
在useEffect
的第一参数函数中可以返回一个函数,当组件卸载时会执行这个函数。那么可以把componentWillUnmount
钩子函数中要执行的代码,放在useEffect
的第一参数函数中返回的函数中执行,以此来模拟componentWillUnmount
钩子函数。
import React,{useState} from 'react';
const HelloWorld = (props) =>{
const [num,setNum]=useState(1);
useEffect(() =>{
const timer = setinterval(() =>{
console.log('定时器')
},3000)
return () =>{
clearInterval(timer);//组件卸载时候会清除定时器
}
},[])
return (
<div>{num}</div>
)
}
export default HelloWorld;
复制代码