资讯详情

React新文档:不要滥用Ref哦~

React新文档有个很有意思的细节:useRefuseEffect这两个API文档所在章节的介绍称为Escape Hatches(逃生舱)。

显然,正常航行时是不需要逃生舱的,只有在遇到危险时会用到。

如果开发者过于依赖这两个API,可能是误用。

在React新文件:不要滥用effect我们在哦中谈到useEffect正确使用场景。

今天,我们来谈谈Ref使用场景。

为什么是逃生舱?

先想一个问题:为什么refeffect被归类到中?

这是因为两者都在操作

effect中处理的是。比如:在useEffect中修改了document.title

document.title不属于React中的状态,React无法感知他的变化,因此被归类为effect中。

同样,需要调用element.focus(),直接执行DOM API也是不受React控制的。

虽然他们是,但是,为了保证应用的强度,React尽量防止他们失控。

失控的Ref

对于Ref,什么是失控?

首先来看的情况:

  • 执行ref.currentfocusblur等方法

  • 执行ref.current.scrollIntoView使element滚进视野

  • 执行ref.current.getBoundingClientRect测量DOM尺寸

在这种情况下,虽然我们已经操作了DOM,但都涉及到,所以不算失控。

但以下情况:

  • 执行ref.current.remove移除DOM

  • 执行ref.current.appendChild插入子节点

同样是操作DOM,但这些属于,通过ref执行这些操作是失控的。

例如,下面是React文档中的例子[1]

点击后插入/删除 P节点,点击后会调用DOM API移除P节点:

exportdefaultfunctionCounter(){ const[show,setShow]=useState(true); constref=useRef(null);  return( <div> <button onClick={()=>{ setShow(!show); }}> TogglewithsetState </button> <button onClick={()=>{ ref.current.remove(); }}> RemovefromtheDOM </button> {show&&<pref={ref}>Helloworld</p>} </div> ); }

通过React去除P节点的控制方法。

直接操作DOM移除P节点。

如果这两种如果混合,先点击再点击就会报错:

fab0f30fa33ce64516c5499cb80d37f8.png

这就是导致的。

如何限制失控?

现在问题来了,既然叫了,那就是React没法控制的(React开发限制开发者不能使用DOM API嗯?),如何限制失控?

React组件可分为:

  • 高阶组件

  • 低阶组件

指那些,例如,以下组件直接基于input节点封装:

functionMyInput(props){ return<input{...props}/>; }

中,可直接将ref指向DOM的,比如:

functionMyInput(props){ constref=useRef(null); return<inputref={ref}{...props}/>; }

指那些,比如下面的Form组件,基于Input组件封装:

functionForm(){ return( <> <MyInput/> </> ) }

无法直接将ref指向DOM,这个限制就要到了在单个组件中控制范围,会出现跨越组件的

以文档中的示例[2]为例,如果我们想在Form组件中点击按钮,操作input聚焦:

function MyInput(props) {
  return <input {...props} />;
}

function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        input聚焦
      </button>
    </>
  );
}

点击后,会报错:

这是因为在Form组件中向MyInput传递ref失败了,inputRef.current并没有指向input节点。

究其原因,就是上面说的

人为取消限制

如果一定要取消这个限制,可以使用forwardRef API显式传递ref

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

使用forwardRefforward在这里是的意思)后,就能跨组件传递ref

在例子中,我们将inputRefForm跨组件传递到MyInput中,并与input产生关联。

在实践中,一些同学可能觉得forwardRef这一API有些多此一举。

但从的角度看,forwardRef的意图就很明显了:既然开发者手动调用forwardRef破除,那他应该知道自己在做什么,也应该自己承担相应的风险。

同时,有了forwardRef的存在,发生后也更容易定位错误。

useImperativeHandle

除了外,还有一种,那就是useImperativeHandle,他的逻辑是这样的:

既然是由于(比如appendChild),那我可以限制

useImperativeHandle修改我们的MyInput组件:

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

现在,Form组件中通过inputRef.current只能取到如下数据结构:

{
  focus() {
    realInputRef.current.focus();
  },
}

就杜绝了的情况。

总结

正常情况,Ref的使用比较少,他是作为而存在的。

为了防止错用/滥用导致ref失控React限制

为了破除这种限制,可以使用forwardRef

为了减少refDOM的滥用,可以使用useImperativeHandle限制ref传递的数据结构。

参考资料

[1]

React文档中的例子: https://codesandbox.io/s/sandpack-project-forked-s33q3c

[2]

文档中的示例: https://codesandbox.io/s/sandpack-project-forked-7zqgmd

标签: s33三极管

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台