一 前言
React hooks是react16.8 以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。本章节笔者将介绍目前 React 提供的所有 hooks ,介绍其功能类型和基本使用方法。
创作不易,希望屏幕前的你能给笔者赏个赞,以此鼓励我继续创作前端硬文。🌹🌹🌹
二 hooks 之执行副作用
2.1 useEffect
React hooks也提供了 api ,用于弥补函数组件没有生命周期的缺陷。其本质主要是运用了 hooks 里面的 useEffect , useLayoutEffect,还有 useInsertionEffect。其中最常用的就是 useEffect 。我们首先来看一下 useEffect 的使用。
useEffect 基础介绍:
useEffect(()=>{
return destory
},dep)
useEffect 第一个参数 callback, 返回的 destory , destory 作为下一次callback执行之前调用,用于清除上一次 callback 产生的副作用。
第二个参数作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次callback 返回的 destory ,和执行新的 effect 第一个参数 callback 。
对于 useEffect 执行, React 处理逻辑是采用异步调用 ,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。
useEffect 基础用法:
/* 模拟数据交互 */
function getUserInfo(a){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
name:a,
age:16,
})
},500)
})
}
const Demo = ({
a }) => {
const [ userMessage , setUserMessage ] :any= useState({
})
const div= useRef()
const [number, setNumber] = useState(0)
/* 模拟事件监听处理函数 */
const handleResize =()=>{
}
/* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/
useEffect(()=>{
/* 请求数据 */
getUserInfo(a).then(res=>{
setUserMessage(res)
})
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 操作dom */
console.log(div.current) /* div */
/* 事件监听等 */
window.addEventListener('resize', handleResize)
/* 此函数用于清除副作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
/* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */
},[ a ,number ])
return (<div ref={
div} >
<span>{
userMessage.name }</span>
<span>{
userMessage.age }</span>
<div onClick={
()=> setNumber(1) } >{
number }</div>
</div>)
}
如上在 useEffect 中做的功能如下:
- ① 请求数据。
- ② 设置定时器,延时器等。
- ③ 操作 dom , 在 React Native 中可以通过 ref 获取元素位置信息等内容。
- ④ 注册事件监听器, 事件绑定,在 React Native 中可以注册 NativeEventEmitter 。
- ⑤ 还可以清除定时器,延时器,解绑事件监听器等。
2.2 useLayoutEffect
useLayoutEffect 基础介绍:
useLayoutEffect 和 useEffect 不同的地方是采用了同步执行,那么和useEffect有什么区别呢?
① 首先 useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前,这样可以方便修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,如果修改 DOM 布局放在 useEffect ,那 useEffect 执行是在浏览器绘制视图之后,接下来又改 DOM ,就可能会导致浏览器再次回流和重绘。而且由于两次绘制,视图上可能会造成闪现突兀的效果。
② useLayoutEffect callback 中代码执行会阻塞浏览器绘制。
useEffect 基础用法:
const DemoUseLayoutEffect = () => {
const target = useRef()
useLayoutEffect(() => {
/*我们需要在dom绘制之前,移动dom到制定位置*/
const {
x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
animate(target.current,{
x,y })
}, []);
return (
<div >
<span ref={
target } className="animate"></span>
</div>
)
}
2.3 useInsertionEffect
useInsertionEffect 基础介绍:
useInsertionEffect 是在 React v18 新添加的 hooks ,它的用法和 useEffect 和 useLayoutEffect 一样。那么这个 hooks 用于什么呢?
在介绍 useInsertionEffect 用途之前,先看一下 useInsertionEffect 的执行时机。
React.useEffect(()=>{
console.log('useEffect 执行')
},[])
React.useLayoutEffect(()=>{
console.log('useLayoutEffect 执行')
},[])
React.useInsertionEffect(()=>{
console.log('useInsertionEffect 执行')
},[])
打印: useInsertionEffect 执行 -> useLayoutEffect 执行 -> useEffect 执行
可以看到 useInsertionEffect 的执行时机要比 useLayoutEffect 提前,useLayoutEffect 执行的时候 DOM 已经更新了,但是在 useInsertionEffect 的执行的时候,DOM 还没有更新。本质上 useInsertionEffect 主要是解决 CSS-in-JS 在渲染中注入样式的性能问题。这个 hooks 主要是应用于这个场景,在其他场景下 React 不期望用这个 hooks 。
useInsertionEffect 模拟使用:
export default function Index(){
React.useInsertionEffect(()=>{
/* 动态创建 style 标签插入到 head 中 */
const style = document.createElement('style')
style.innerHTML = `
.css-in-js{
color: red;
font-size: 20px;
}
`
document.head.appendChild(style)
},[])
return <div className="css-in-js" > hello , useInsertionEffect </div>
}
如上模拟了 useInsertionEffect 的使用。
三 hooks 之状态获取与传递
3.1 useContext
useContext 基础介绍
可以使用 useContext ,来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式创建的 ,也可以父级上下文 context 传递的 ( 参数为 context )。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value 值。
const contextValue = useContext(context)
useContext 接受一个参数,一般都是 context 对象,返回值为 context 对象内部保存的 value 值。
useContext 基础用法:
/* 用useContext方式 */
const DemoContext = ()=> {
const value:any = useContext(Context)
/* my name is alien */
return <div> my name is {
value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
return <Context.Consumer>
{
/* my name is alien */}
{
(value)=> <div> my name is {
value.name }</div> }
</Context.Consumer>
}
export default ()=>{
return <div>
<Context.Provider value={
{
name:'alien' , age:18 }} >
<DemoContext />
<DemoContext1 />
</Context.Provider>
</div>
}
3.2 useRef
useRef 基础介绍:
useRef 可以用来获取元素,缓存状态,接受一个状态 initState 作为初始值,返回一个 ref 对象 cur, cur 上有一个 current 属性就是 ref 对象需要获取的内容。
const cur = React.useRef(initState)
console.log(cur.current)
useRef 基础用法:
useRef 获取 DOM 元素,在 React Native 中虽然没有 DOM 元素,但是也能够获取组件的节点信息( fiber 信息 )。
const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* <div >表单组件</div> dom 节点 */
console.log(dom.current)
}
return <div>
{
/* ref 标记当前dom节点 */}
<div ref={
dom} >表单组件</div>
<button onClick={
()=>handerSubmit()} >提交</button>
</div>
}
如上通过 useRef 来获取 DOM 节点。
useRef 保存状态, 可以利用 useRef 返回的 ref 对象来保存状态,只要当前组件不被销毁,那么状态就会一直存在。
const status = useRef(false)
/* 改变状态 */
const handleChangeStatus = () => {
status.current = true
}
3.3 useImperativeHandle
useImperativeHandle 基础介绍:
useImperativeHandle 可以配合 forwardRef 自定义暴露给父组件的实例值。这个很有用,我们知道,对于子组件,如果是 class 类组件,我们可以通过 ref 获取类组件的实例,但是在子组件是函数组件的情况,如果我们不能直接通过 ref 的,那么此时 useImperativeHandle 和 forwardRef 配合就能达到效果。
useImperativeHandle 接受三个参数:
- ① 第一个参数ref: 接受 forWardRef 传递过来的 ref。
- ② 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的 ref 对象。
- ③ 第三个参数 deps : 依赖项 deps ,依赖项更改形成新的 ref 对象。
useImperativeHandle 基础用法:
我们来模拟给场景,用useImperativeHandle,使得父组件能让子组件中的input自动赋值并聚焦。
function Son (props,ref) {
console.log(props)
const inputRef = useRef(null)
const [ inputValue , setInputValue ] = useState('')
useImperativeHandle(ref,()=>{
const handleRefs = {
/* 声明方法用于聚焦input框 */
onFocus(){
inputRef.current.focus()
},
/* 声明方法用于改变input的值 */
onChangeValue(value){
setInputValue(value)
}
}
return handleRefs
},[])
return <div>
<input
placeholder="请输入内容"
ref={
inputRef}
value={
inputValue}
/>
</div>
}
const ForwarSon = forwardRef(Son)
class Index extends React.Component{
inputRef = null
handerClick(){
const {
onFocus , onChangeValue } =this.cur
onFocus()
onChangeValue('let us learn React!')
}
render(){
return <div style={
{
marginTop:'50px' }} >
<ForwarSon ref={
node => (this.inputRef = node)} />
<button onClick={
this.handerClick.bind(this)} >操控子组件</button>
</div>
}
}
效果:
总结
在下一篇文章中,我们将继续介绍 React Hooks 。