持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情
useRef
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变
使用场景
- 获取dom元素进行操作
- 保存一个数据,这个对象在整个生命周期中可以保存不变
import React, { memo, useRef, useEffect } from 'react'
const App = memo(() => {
const titleRef = useRef()
useEffect(() => {
console.log(titleRef.current)
}, [])
return (
<div>
<h2 ref={ titleRef }>hello world</h2>
</div>
)
})
export default App
useImperativeHandle
useImperativeHandle这个hook的功能和vue中子组件的defineExpose功能很像
useImperativeHandle决定了父组件可以对子组件进行那些操作,那些操作是不被允许的
import React, { memo, useRef, useImperativeHandle, forwardRef } from 'react'
const Child = memo(forwardRef((props, ref) => {
const inputEl = useRef()
// useImperativeHandle 在父组件和子组件之间进行了一层拦截
// 参数一 - 父组件传入到子组件的ref对象
// 参数二 - 一个需要返回对象的回调函数
// - 所返回的对象,即是父组件可以操作的所有方法
// - 也就是说所返回的对象会作为inputEl的current属性值
useImperativeHandle(ref, () => ({
focus() {
inputEl.current.focus()
},
setValue(v) {
inputEl.current.value = v
}
}))
return <input ref={inputEl} />
}))
const App = memo(() => {
const inputEl = useRef()
function handle() {
// 如果父组件所执行的操作并不被允许,那么会静默失败
inputEl.current.setValue('Klaus')
}
return (
<div>
<Child ref={inputEl} />
<button onClick={ () => handle() }>handle</button>
</div>
)
})
export default App
useLayoutEffect
useLayoutEffect和useEffect的功能很像
useEffect 是在 组件被挂载后回调, 也就是说useEffect这个hook 会在dom挂载完毕后再被回调
useLayoutEffect 是在 虚拟DOM创建完毕 虚拟DOM被挂载到真实DOM之前 被回调
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect
官方更推荐优先使用useEffect, 只有当useEffect出现问题的时候,再去使用useLayoutEffect
useEffect
import React, { memo, useEffect, useState } from 'react'
const App = memo(() => {
const [count, setCount] = useState(100)
// 因为界面先渲染成0之后,再重新改变界面 将内容渲染成一个随机数
// 所以界面会出现闪动效果
useEffect(() => {
if (count === 0) {
setCount(Math.random() + 100)
}
}, [count])
return (
<>
<div>{ count }</div>
<button onClick={ () => setCount(0) }>+1</button>
</>
)
})
export default App
useLayoutEffect
import React, { memo, useLayoutEffect, useState } from 'react'
const App = memo(() => {
const [count, setCount] = useState(100)
// 此时在界面渲染之前已经将count修改成了真正所需要渲染的那个随机数
// 所以此时界面就不会出现闪烁的情况
useLayoutEffect(() => {
if (count === 0) {
setCount(Math.random() + 100)
}
}, [count])
return (
<>
<div>{ count }</div>
<button onClick={ () => setCount(0) }>+1</button>
</>
)
})
export default App
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性
需求: 在组件被挂载或者被销毁的时候,打印对应的日志信息
import React, { memo, useState, useEffect } from 'react'
const Child = memo(() => {
useEffect(() => {
console.log('组件被挂载')
return () => {
console.log('组件被销毁')
}
}, [])
return (
<h2>Child</h2>
)
})
const App = memo(() => {
const [isShow, setIsShow] = useState(true)
useEffect(() => {
console.log('组件被挂载')
return () => {
console.log('组件被销毁')
}
}, [])
return (
<>
<h2>App</h2>
<button onClick={() => setIsShow(!isShow)}>change</button>
{isShow && <Child />}
</>
)
})
export default App
可以看到每个组件的useEffect操作其实都是重复的,所以我们需要对这部分代码进行抽取
对于类组件,我们抽取公共代码的方式是使用HOC
而对于函数组件,我们抽取公共代码的方式是使用hook
import React, { memo, useState, useEffect } from 'react'
// hook本质上就是使用use开头的函数
function useLog(cName) {
useEffect(() => {
console.log(cName + '组件被挂载')
return () => {
console.log(cName +'组件被销毁')
}
}, [cName])
}
const Child = memo(() => {
useLog('Child')
return (
<h2>Child</h2>
)
})
const App = memo(() => {
const [isShow, setIsShow] = useState(true)
useLog('App')
return (
<>
<h2>App</h2>
<button onClick={() => setIsShow(!isShow)}>change</button>
{isShow && <Child />}
</>
)
})
export default App
示例
共享context
import { useContext } from 'react'
import { TokenContext, UserInfoContext } from "../context";
function useUserContext() {
const token = useContext(TokenContext)
const userInfo = useContext(UserInfoContext)
// hook的返回值 一般是数组,(也可以返回其它的数据结构)
return [userInfo, token]
}
export default useUserContext
获取滚动位置
import { useState, useEffect } from 'react'
function useScrollPosition() {
// 对应的数据需要在组件渲染完成后才可以获取,且这些数据是会实时修改的
// 所以将对应的数据使用useState来进行定义
const [scrollX, setScrollX] = useState(0)
const [scrollY, setScrollY] = useState(0)
// dom监听是组件渲染之外的额外操作,所以是组件的副作用
useEffect(() => {
function handler() {
setScrollX(window.scrollX)
setScrollY(window.scrollY)
}
// 监听页面滚到 监听window 和 document都是可以的
// window先滚动,随后会传递给document
window.addEventListener('scroll', handler)
return () => {
window.removeEventListener('scroll', handler)
}
}, [])
return [scrollX, scrollY]
}
export default useScrollPosition
localStorage数据存储
import { useEffect, useState } from "react"
/*
需求: localStorage和state保持同步
1. 取出localStorage中的值 并作为state返回
2. 当外界修改state中的值的时候,自动更新localStorage
*/
function useLocalState(key) {
// 如果useState中的初始值逻辑过于复杂
// 可以将一个函数作为参数传递给useState
// 对应的函数会立即执行, 并将函数的返回值作为useState的初始值
const [data, setData] = useState(() => {
// localStorage.getItem(undefined) -> error
// localStorage.getItem('') -> error
// localStorage.getItem(null) -> null
const item = localStorage.getItem(key)
if (!item) return ''
return JSON.parse(item)
})
useEffect(() => {
// JSON.stringify('') -> '""'
// JSON.stringify(undefined) -> undefined -> 注意返回的不是字符串,而是undefined
// JSON.stringify(null) -> 'null'
localStorage.setItem(key, JSON.stringify(data))
}, [key, data])
return [data, setData]
}
export default useLocalState