8、useLayoutEffect
语法:
useLayoutEffect(() => { doSomething });
与 useEffect
Hooks 类似,都是执行副作用操作。但是它是在所有 DOM 更新完成后触发。可以用来执行一些与布局相关的副作用,比如获取 DOM 元素宽高,窗口滚动距离等等。
进行副作用操作时尽量优先选择 useEffect,以免阻止视觉更新。与 DOM 无关的副作用操作请使用
useEffect
。
用法
用法与 useEffect 类似。
Example.js
import React, { useRef, useState, useLayoutEffect } from 'react'; export default () => { const divRef = useRef(null); const [height, setHeight] = useState(100); useLayoutEffect(() => { // DOM 更新完成后打印出 div 的高度 console.log('useLayoutEffect: ', divRef.current.clientHeight); }) return <> <div ref={ divRef } style={{ background: 'red', height: height }}>Hello</div> <button onClick={ () => setHeight(height + 50) }>改变 div 高度</button> </>
注意:
- useLayoutEffect 相比 useEffect,通过同步执行状态更新可解决一些特性场景下的页面闪烁问题。
- useEffect 可以满足百分之99的场景,而且 useLayoutEffect 会阻塞渲染,请谨慎使用。
- useEffect 在全部渲染完毕后才会执行
- useLayoutEffect 会在 浏览器 layout 之后,painting 之前执行
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
- 可以使用它来读取 DOM 布局并同步触发重渲染
- 在浏览器执行绘制之前 useLayoutEffect 内部的更新计划将被同步刷新
- 尽可能使用标准的 useEffect 以避免阻塞视图更新
9、forwardRef
因为函数组件没有实例,所以函数组件无法像类组件一样可以接收 ref 属性
function Parent() { return ( <> // <Child ref={xxx} /> 这样是不行的 <Child /> <button>+</button> </> ) }
- forwardRef 可以在父组件中操作子组件的 ref 对象
- forwardRef 可以将父组件中的 ref 对象转发到子组件中的 dom 元素上
- 子组件接受 props 和 ref 作为参数
function Child(props,ref){ return ( <input type="text" ref={ref}/> ) } Child = React.forwardRef(Child); function Parent(){ let [number,setNumber] = useState(0); // 在使用类组件的时候,创建 ref 返回一个对象,该对象的 current 属性值为空 // 只有当它被赋给某个元素的 ref 属性时,才会有值 // 所以父组件(类组件)创建一个 ref 对象,然后传递给子组件(类组件),子组件内部有元素使用了 // 那么父组件就可以操作子组件中的某个元素 // 但是函数组件无法接收 ref 属性 <Child ref={xxx} /> 这样是不行的 // 所以就需要用到 forwardRef 进行转发 const inputRef = useRef();//{current:''} function getFocus(){ inputRef.current.value = 'focus'; inputRef.current.focus(); } return ( <> <Child ref={inputRef}/> <button onClick={()=>setNumber({number:number+1})}>+</button> <button onClick={getFocus}>获得焦点</button> </> ) }
10、useImperativeHandle
useImperativeHandle
可以让你在使用 ref 时,自定义暴露给父组件的实例值,不能让父组件想干嘛就干嘛- 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
- 父组件可以使用操作子组件中的多个 ref
import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react'; function Child(props,parentRef){ // 子组件内部自己创建 ref let focusRef = useRef(); let inputRef = useRef(); useImperativeHandle(parentRef,()=>( // 这个函数会返回一个对象 // 该对象会作为父组件 current 属性的值 // 通过这种方式,父组件可以使用操作子组件中的多个 ref return { focusRef, inputRef, name:'计数器', focus(){ focusRef.current.focus(); }, changeText(text){ inputRef.current.value = text; } } }); return ( <> <input ref={focusRef}/> <input ref={inputRef}/> </> ) } Child = forwardRef(Child); function Parent(){ const parentRef = useRef();//{current:''} function getFocus(){ parentRef.current.focus(); // 因为子组件中没有定义这个属性,实现了保护,所以这里的代码无效 parentRef.current.addNumber(666); parentRef.current.changeText('<script>alert(1)</script>'); console.log(parentRef.current.name); } return ( <> <ForwardChild ref={parentRef}/> <button onClick={getFocus}>获得焦点</button> </> ) }
官网介绍forwardRef与useImperativeHandle结合使用