当前位置: 首页 > Web前端 > HTML

新React文档:不要滥用Ref~

时间:2023-03-28 11:52:43 HTML

大家好,我是Kason。新的React文档有一个非常有趣的细节:引入了useRef和useEffect两个API,文档中的章节叫做EscapeHatches(逃生舱口)。显然,在正常航行期间不需要逃生舱,只有在遇到危险时才需要。如果开发者过分依赖这两个API,则可能是误用。在新的React文档:Don'tmisuseeffect中,我们谈到了useEffect的正确使用场景。今天,我们就来说说Ref.的??使用场景。欢迎加入人类优质前端框架群,为什么是逃生舱?先思考一个问题:ref和effect为什么要归入逃生舱?这是因为两者都在React无法控制的因素下运行。Effect处理副作用。例如:修改useEffect中的document.title。document.title在React中不属于state,React无法感知它的变化,所以归类到effect中。同样,使DOM成为焦点需要调用element.focus(),直接执行DOMAPI也不在React的控制范围内。尽管它们是React无法控制的因素,但为了保证应用程序的健壮性,React应该尽量防止它们失控。Ref失控对于Ref,什么是失控?首先我们看一下没有失控的情况:执行ref.current的focus、blur等方法执行ref.current.scrollIntoView将元素滚动到视野中执行ref.current.getBoundingClientRect测量DOM大小在这些情况下,虽然我们操作了DOM,但所有涉及的因素都不在React的控制范围内,因此它并不是失控的。但是下面的情况:执行ref.current.remove移除DOM执行ref.current.appendChild插入子节点也是操作DOM,但是这些都是React控制范围内的因素,通过ref进行这些操作是不受控制的.例如下面是React文档中的一个例子:点击按钮1后,会插入/移除P个节点,点击按钮2后,会调用DOMAPI移除P个节点:exportdefaultfunctionCounter(){const[show,setShow]=useState(true);constref=useRef(null);return(

{setShow(!show);}}>使用setState切换{ref.current.remove();}}>从DOM中移除{show&&Helloworld

}
);}button1通过React控件移除P节点。Button2直接操作DOM移除P节点。如果混合使用这两种移除P节点的方法,那么先点击按钮1再点击按钮2会报错:这是由于使用Ref操作DOM导致的失控情况造成的。如何限制失控现在问题来了,既然叫失控,那就是不受React的控制(React不能限制开发者使用DOMAPI对吧?),那么怎么限制失控?在React中,组件可以分为:高层组件低层组件低层组件是指那些基于DOM封装的组件,比如下面的组件,直接基于输入节点封装:functionMyInput(props){return;}在底层组件中,ref可以直接指向DOM,如:functionMyInput(props){constref=useRef(null);return;}高阶组件指的是那些基于低级组件封装的组件,比如下面这个Form组件,它是基于Input组件封装的:functionForm(){return(<>)}高级组件不可能直接将ref指向DOM。这个限制会控制单个组件内ref失控的范围,不会出现跨组件的ref失控。以文档中的例子为例,如果我们要点击Form组件中的按钮,操作输入焦点:functionMyInput(props){return;}functionForm(){constinputRef=useRef(null);函数handleClick(){inputRef.current.focus();}return(<>输入焦点);}点击后会报错:这是因为Form组件中将ref传递给MyInput失败,inputRef.current没有指向输入节点。原因是为了控制单个组件内ref的失控范围,React默认不支持跨组件传递ref。人为取消限制如果一定要取消这个限制,可以使用forwardRefAPI显式传递ref:constMyInput=forwardRef((props,ref)=>{return;});functionForm(){constinputRef=useRef(null);函数handleClick(){inputRef.current.focus();}return(<>focustheinput);}使用forwardRef后(forward表示从这里通过),可以将ref传过去成分。在示例中,我们将inputRef从Form传递到MyInput并将其与输入相关联。在实践中,可能有同学会觉得forwardRefAPI有点多余。但是从ref失控的角度来看,forwardRef的用意很明显:既然开发者手动调用forwardRef来打破防止ref失控的限制,他就应该知道自己在做什么,应该承担相应的风险。同时,有了forwardRef的存在,ref相关的错误发生后更容易定位错误。useImperativeHandle除了限制ref跨组件传递之外,还有一个防止ref失控的措施,就是useImperativeHandle,它的逻辑是这样的:既然ref失控是因为使用了DOM方法,那应该不被使用(如appendChild),那么我可以限制只存在可以在ref中使用的方法。使用useImperativeHandle修改我们的MyInput组件:},}));返回;});现在,Form组件只能通过inputRef.current获取如下数据结构:{focus(){realInputRef.当前.focus();},}防止开发者在通过ref获取DOM后执行不应该使用的API,导致ref失控。总结一下正常情况,Ref很少被使用,它作为逃生舱存在。为了防止ref因误用/滥用而失控,React默认限制ref跨组件传递。为了摆脱这个限制,可以使用forwardRef。为了减少ref对DOM的滥用,可以使用useImperativeHandle来限制ref传递的数据结构。