前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你不知道的React Ref

你不知道的React Ref

作者头像
FE情报局
发布2022-12-05 10:15:51
2.2K0
发布2022-12-05 10:15:51
举报
文章被收录于专栏:FE情报局

怎样使用React Ref属性

在我们平时使用React的时候,对于React中的Ref的属性,相信大家使用的频率是很低的。说实话,真正了解React Ref属性的人少之又少,我都不确定自己是否真正的了解了所有的内容,毕竟它不是一个经常能够被人使用的属性,而且在过去一段时间,它本身的API在不断修改。那么在本教程中,我将尽可能的向大家介绍React中的Ref

1 Why React Hook ?

这些内容相信已经被大家写烂了,毕竟好处多大家才会使用,这里简单总结三点

  • 逻辑更易复用
  • 清爽的代码风格
  • 易拓展的组件

2 How to use React Refs

过去,在class component中,React Ref经常与DOM保持紧密关联,但是自从出现了React Hook以后,Ref的使用也不再变得只是与Dom相关的Api,而是可以表示对任何内容的引用(DOM节点,JavaScript值等)

那么接下来,我们先看看不带DOM的Ref,然后我们再结合DOM了解其如何使用

2.1 除去Dom元素看Ref

首先看以下栗子🌰:

代码语言:javascript
复制
function Counter() {
  const [count, setCount] = useState(0);
 
  function onClick() {
    const newCount = count + 1;
    setCount(newCount);
  }
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}

这是一个简单的递增函数组件,接下来我们将引用了React.useRef这个API,这是React为函数式组件使用Ref时提供的最新API。简单来说,useRef Hook向我们返回一个可变对象,该对象在React组件的生命周期内保持不变。具体来说就是返回的对象具有当前属性,该属性可以保存我们任何可以修改的值

代码语言:javascript
复制
function Counter() {
  const hasClickedButton = useRef(false);
 
  const [count, setCount] = useState(0);
 
  function onClick() {
    const newCount = count + 1;
 
    setCount(newCount);
 
    hasClickedButton.current = true;
    console.log("hasClickedButton",hasClickedButton)
  }
 
  console.log('Has clicked button? ' + hasClickedButton.current);
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}

控制台打印结果:

可以看出,实际上useRef Hook返回值是一个对象,其具有两个特点:

  • 具有current属性,该属性是可变的
  • 它可以在组件生命周期中随时更改

其实单看上面代码并不能明确单独Ref的改变是否会引起页面的重新渲染,看看下面这个栗子🌰:

代码语言:javascript
复制
function Counter() {
  const hasClickedButton = useRef(false);
 
  const [count, setCount] = useState(0);
 
  function onClick() {
    // const newCount = count + 1;
 
    // setCount(newCount);
 
    hasClickedButton.current = true;
  }
  console.log('Has clicked button? ' + hasClickedButton.current);
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}

可以看出,单独的ref重新赋值,并不会使组件重新渲染,无论何时需要,我们都可以将ref的当前属性重新分配给新值,他的存在仅仅相当于一个状态,那么就有一个疑问了,我们引用他的作用是什么呢?

2.2 Ref的更新机制

代码语言:javascript
复制
function ComponentWithRefInstanceVariable() {
  const [count, setCount] = useState(0);
 
  function onClick() {
    setCount(count + 1);
  }
 
  const isFirstRender = useRef(true);
 
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
    }
  });
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
      <p>{isFirstRender.current ? 'First render.' : 'Re-render.'}</p>
    </div>
  );
}

这段代码向我们展示了将ref的current属性设置为false是不会触发重新渲染的。利用这一特性,我们可以创建一个useEffect挂钩,该挂钩仅在每次组件更新时都运行其逻辑,而不在初始渲染时运行。这肯定是每个React开发人员在某个时候都需要的功能,但是React的useEffect Hook没有提供此功能

代码语言:javascript
复制
function ComponentWithRef() {
  const [count, setCount] = useState(0);
 
  function onClick() {
    setCount(count + 1);
  }
 
  const isFirstRender = useRef(true);
 
  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
    } else {
      console.log("re-render");
    }
  });
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}

可以知道,每当需要跟踪React组件中的状态而该状态不应该触发组件的重新渲染时,都可以使用React的useRef Hooks为其创建一个实例变量。

2.3 React UseRef && Dom

接下来让我们回归到最原始的Ref使用:Dom。通常,每当必须与HTML元素进行交互时,我们都会选择使用React的ref。React本质上是声明性的,但是有时您需要从HTML元素读取值,与HTML元素的API交互,甚至必须将值写入HTML元素。对于这些罕见的情况,您必须使用React的refs以强制性而非声明性的方式与DOM进行交互。

下面这个🌰将简单的展示Ref与Dom的交互:

代码语言:javascript
复制
function App() {
  return (
    <ComponentWithDomApi
      label="Label"
      isFocus
    />
  );
}
 
function Test({ label, isFocus }) {
  const refs = useRef();
 
  useEffect(() => {
    if (isFocus) {
    console.log(ref)
      refs.current.focus();)
    }
  }, [isFocus]);
 
  return (
    <label>
      {label}: <input type="text"  ref={refs} />
     
     {/* ref对象提供给HTML元素作为ref HTML属性。React为我们自动将此HTML元素的DOM节点分配给ref对象。 */}
     
    </label>
  );
}

通过ref获取dom的width:

代码语言:javascript
复制
function Test() {
    const [text, setText] = useState('Some text ...');
   
    function handleOnChange(event) {
      setText(event.target.value);
    }
   
    const ref =useRef();
   
    useEffect(() => {
      const { width } = ref.current.getBoundingClientRect();
      document.title = `Width:${width}`;
      console.log("ref",ref.current.getBoundingClientRect().width)
    }, [text]);
   
    return (
      <div>
        <input type="text" value={text} onChange={handleOnChange} />
        <div>
          <span ref={ref}>{text}</span>
        </div>
      </div>
    );
  }

使用Ref管理Button的状态是一个不错的选择,但是注意,是按钮的状态,而不是组件的状态。

让我们考虑一个真实的场景。表单已经完成,提交按钮需要从默认的禁用状态启用提交状态。仅为了执行此操作而重新渲染我的整个表单将会执行以下步骤:

  • 将当前的所有表单值保存在state中
  • 使用这些当前值再次重新渲染整个表单
  • 保持子组件中可能存在的任何其他状态,例如验证消息等
  • 重置可能正在发生的过度动画

但如果使用Ref控制按钮以切换其disabled属性,就简单的多了:

代码语言:javascript
复制
buttonRef.current.setAttribute("disabled", true);
// or 
buttonRef.current.removeAttribute("disabled");

2.4 Ref && Callback

通过上述两个🌰可以看到Ref与Dom妙不可言的关系,但实际上我们还可以通过使用回调函数来实现我们想要的一系列操作。

代码语言:javascript
复制
function Test() {
  const [text, setText] = useState('Some text ...');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = (node) => {
    if (!node) return;
 
    const { width } = node.getBoundingClientRect();
 
    document.title = `Width:${width}`;
    console.log("node",node) // => <span>Some text ...<span>
  };
 
  return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
  }

回调ref就是一个可用于JSX中HTML元素的ref属性的函数。该函数可以访问DOM节点,并且只要在HTML元素的ref属性上使用该函数,就会触发该函数。本质上,它的作用与以前的副作用相同,但是这次回调ref本身通知我们它已附加到HTML元素

我们还可以通过使用Ref对Dom进行样式的读写,但是不建议使用,故不做解释

2.5 几种适合Ref的场景

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

2.6 案例

2.6.1 将Ref作为容器存储变量:

代码语言:javascript
复制
function Test() {
  const [timer, setTimer] = useState(0);
  const intervalRef = useRef(null);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setTimer(pre => pre + 1)
    }, 1000)
  }, [])
  return (
    <div>
      HookTimer - {timer}
      <br />
      <button
        onClick={() => {
          clearInterval(intervalRef.current)
        }}
      >Clear Hook Timer</button>
    </div>
  )

2.6.2 计时器

代码语言:javascript
复制
const useFn = (fn, time) => useEffect(
    () => {
        const tick = setInterval(fn);
        return () => clearInterval(tick);
    },
    [fn, time]
);
代码语言:javascript
复制
useFn(() => setCounter(counter => counter + 1));

会发现和我们预期的“每秒计数加1”不同,这个定时器执行频率会变得非常诡异。因为你传入的fn每一次都在变化,每一次都导致useEffect销毁前一个定时器,打开一个新的定时器,所以简而言之,如果1秒之内没有重新渲染,定时器会被执行,而如果有新的渲染,定时器会重头再来,这让频率变得不稳定。

代码语言:javascript
复制
function Test() {
  const [counter, setCounter] = useState(0);

  const useFn = (fn, time) => {
    const callback = useRef(fn);
    callback.current = fn;
    useEffect(() => {
      const tick = setTimeout(callback.current);
      return () => clearTimeout(tick);
    }, [time]);
  };
  useFn(() => setCounter((counter) => counter + 1), 1000);
  return <div>计时器:{counter}</div>;
}

2.6.3 自动获焦

代码语言:javascript
复制
function FocusInput() {
   const inputRef = useRef(null)
  useEffect(() => {
    inputRef.current && inputRef.current.focus()
  }, [])

  return (
    <div>
      <input ref={inputRef} type="text" />
    </div>
  )
}

至此,向大家介绍了整个react ref的使用方式以及对应的场景,如果大家还有什么问题,欢迎提出!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FE情报局 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 怎样使用React Ref属性
    • 1 Why React Hook ?
      • 2 How to use React Refs
        • 2.1 除去Dom元素看Ref
        • 2.2 Ref的更新机制
        • 2.3 React UseRef && Dom
        • 2.4 Ref && Callback
      • 2.5 几种适合Ref的场景
        • 2.6 案例
          • 2.6.1 将Ref作为容器存储变量:
          • 2.6.2 计时器
          • 2.6.3 自动获焦
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档