useRef
useRef 可以绑定dom
export const App = () => {
const divRef = useRef(null)
const handleClick = () => {
console.dir(divRef.current.style.backgroundColor)
divRef.current.style.backgroundColor = 'red'
}
return (
<div ref={divRef} onClick={handleClick}>App</div>
)
}
useRef 可以用来存储值,useRef.current的改变,不会导致组件渲染
export const App = () => {
console.log('App')
const divRef = useRef(1)
const handleClick = () => {
divRef.current++
console.log(divRef.current)
}
return (
<div onClick={handleClick}>App{divRef.current}
</div>
)
}
注意事项
- 组件在重新渲染的时候,useRef的值不会被重新初始化。
- 改变 ref.current 属性时,React 不会重新渲染组件。React 不知道它何时会发生改变,因为 ref 是一个普通的 JavaScript 对象。
useRef的值不能作为useEffect等其他hooks的依赖项,因为它并不是一个响应式状态。- useRef不能直接获取子组件的实例,需要使用
forwardRef。
forwardRef 父组件传递,子组件接收
const Child = forwardRef((props, ref) => {
return (
<div ref={ref}>Child</div>
)
})
export const App = () => {
const childRef = useRef(null)
const handleClick = () => {
console.log(childRef.current)
}
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>+1</button>
</div>
)
}
useImperativeHandle 自定义子组件暴露给父组件的 属性 方法,dom
useImperativeHandle第二个参数,可以是 空, 空数组, 依赖项 用法和useEffect一样
类似vue3中的definexpose
1️⃣ forwardRef 的本质
forwardRef 是为了让父组件可以通过 ref 访问子组件内部的某个对象。
默认情况下,如果你直接写:
<div ref={ref}>Child</div>
那么父组件 childRef.current 就会拿到 这个 div 的 DOM 元素。
2️⃣ useImperativeHandle 的作用
useImperativeHandle 可以让你自定义暴露给父组件的对象:
useImperativeHandle(ref, () => ({
name: 'Child',
handleClick,
count,
}))
这时候,childRef.current 不再是原来的 DOM 元素,而是你返回的这个对象:
{
name: 'Child',
handleClick: function,
count: 0
}
3️⃣ 为什么“拿不到 DOM”
你的原代码里:
<div ref={ref} onClick={handleClick}>Child{count}</div>
这里 ref 被 useImperativeHandle 覆盖了:
useImperativeHandle返回的对象会覆盖 ref 的默认值。- 所以
childRef.current不再是 div,而是你自定义的对象。 - 如果你想在
useImperativeHandle里同时保留 DOM,需要手动加上:
const divRef = useRef(null)
useImperativeHandle(ref, () => ({
name: 'Child',
handleClick,
count,
div: divRef.current // 手动暴露 DOM
}))
return (
<div ref={divRef} onClick={handleClick}>
Child{count}
</div>
)
然后父组件访问 DOM 就是:
childRef.current.div
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
const divRef = useRef(null)
useImperativeHandle(ref, () => ({
name: 'Child',
handleClick,
count,
dom:divRef.current
}))
return (
<div ref={divRef} onClick={handleClick}>
Child{count}
</div>
)
})
export const App = () => {
const childRef = useRef(null)
const handleClick = () => {
console.dir(childRef.current.dom)
}
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>获取子组件</button>
</div>
)
}
✅ 总结
forwardRef+ 直接ref→ 拿 DOMuseImperativeHandle→ ref 被覆盖,拿到的是你返回的对象- 想要同时拿对象和 DOM → 在
useImperativeHandle返回对象里手动加一个 DOM 引用
useRef实际例子
export const App = () => {
let time = useRef(null)
const [count, setCount] = useState(0)
function handleClickstart(){
time.current = setInterval(() => {
setCount(prev => prev + 1)
}, 1000)
}
function handleClickstop(){
clearInterval(time.current)
}
return (
<div >
<div onClick={handleClickstart}>App{count}</div>
<button onClick={handleClickstop}>停止</button>
</div>
)
}