首页 > 其他分享 >前端进阶系列——理解 React Ref

前端进阶系列——理解 React Ref

时间:2023-10-11 11:44:23浏览次数:33  
标签:const 进阶 dom Ref React 组件 ref

前端进阶系列——理解 React Ref

秦书羽 秦书羽 杭州@朝夕光年  
Ref 是 Reference(引用) 的缩写。

一、前言

在 React 中通常遵循 “自上而下” 的 “单向数据流”。父组件和子组件的通讯只能通过 Props。如果要修改一个子组件,我们要修改 Props,让 React 重新渲染子组件。

但是有时候,我们需要用数据流之外的方式来修改子组件,例如:获取焦点、视频开始播放等。

Ref 提供了这个方式,让我们可以直接操作子元素。

二、什么是Ref?—— “命令式” 操作组件

Props 是单向数据流,以 “声明式” 渲染组件;Ref 则是以 “命令式” 操作组件。

下面举例来体会声明式命令式的区别,实现下图功能:

 

2.1 以声明式的方式:

  1. 声明一个 focused 的 state
  2. 作为 Props 传给子组件 <input focused={focused} />
  3. 点击按钮时:修改 focused 为 true
function App() { 
  const [focused, setFocused] = useState(false); 
  return ( 
    <div> 
      <button onClick={() => setFocused(true)}>开始输入</button> 
      <input focused={focused} placeholder="我是输入框" /> 
    </div> 
  ); 
} 

但是 input 组件并没有 focused 参数。因此我们需要操作dom,命令式调用dom.focus()来获取焦点。

2.2 使用 Ref,以命令式的方式:

  1. 声明一个 inputRef,用于接受 inputDom
  2. 把 inputRef 传递给 input 进行设置,<input ref={inputRef} />
  3. 点击按钮时:操作dom,主动调用 focus()
function App() { 
  const inputRef = React.useRef(); 
 
  function handleClick() { 
    // 按钮点击时,命令式的调用dom.focus方法 
    inputRef.current && inputRef.current.focus(); 
  }
 
  return ( 
    <div className="App"> 
      <button onClick={handleClick}>开始输入</button> 
      <input ref={inputRef} placeholder="我是输入框" /> 
    </div> 
  ); 
} 

这就是命令式,打破了 Props 的单向数据流,直接操作子元素。

2.3 Ref 使用场景

重要提示:因为命令式破坏了原先的数据流,所以请不要滥用 Ref。

可以使用 Props 完成的,建议优先使用声明式的Props。例如:我们写一个“对话框组件“,最好使用 isOpen 属性控制开关,而不是暴露 close() 和 open() 方法。

总的来说,Ref 通常有三类场景:

  • 处理 focus、视频播放 等
  • 操作 dom 进行的动画
  • 集成第三方的 dom 库

三、Ref 各类使用姿势

3.1 回调式的 Ref

Ref 还可以传入一个函数,开发者可以在这个函数里面保存 dom 的引用,更自由地设置引用。

回调 Ref实现上一节的例子:

function App() { 
  let inputElement = null; 
 
  /** Ref的回调函数,保存node的引用 */ 
  function setElement (node) { 
    inputElement = node; 
  } 
 
  function handleClick() { 
    // 直接使用引用 
    inputElement && inputElement.focus(); 
  } 
 
  return ( 
    <div className="App"> 
      <button onClick={handleClick}>开始输入</button> 
      {/* 传入回调函数 */} 
      <input ref={setElement} /> 
    </div> 
  ); 
} 

Tips:上面的例子,当组件发生更新时:

  • setElement 会执行两次。第一次参数传入 null:setElement(null),清空旧的引用。第二次传入 dom 元素:setElement(newDom)。
  • 因为对于函数组件而言,在每次渲染时会创建一个新的函数实例。所以第一次清空旧的 Ref,第二次在新实例下的设置引用。两次调用其实是针对不同的 inputElement 对象。
  • 旧的函数实例,调用一次 setElement(null) 正好可以帮我们释放一些引用,防止泄露。

3.2 Ref 转发

“Ref 转发” 就是让组件接收 Ref,然后向下传递给子组件。一般场景不常用,在写一些通用组件的时候,会用到。

3.2.1 React.forwardRef 使用

React 提供了 forwardRef,让我们可以做到转发。

例如我们要封装一个公共的 Button 组件。节选自 Antd,伪代码:

const InternalButton = (props, ref) => { 
    return <button 
      className="common-button" 
      ref={ref} 
    > 
        ... 
    </button> 
} 
 
const Button = React.forwardRef(InternalButton); 
export default Button; 

使用时:

const ref = React.useRef(); 
<Button ref={ref}>Click me!</Button>; 

此时,获取到的 ref 就是组件内部真实的 button。

PS:组件的第二个参数 ref 只在forwardRef 定义组件时存在。常规 props 里没有此参数。

3.2.2 useImperativeHandle 使用

当然,除了 dom 元素之外,Ref 还可以指向其他对象。

Ref 是命令式的编程,有时对于一些复杂的场景,我们希望自定义 Ref 里的命令。

此时useImperativeHandle登场。举例:

const FancyButton = forwardRef((props, ref) => { 
  const internalRef = useRef(); 
  useImperativeHandle(ref, () => ({ 
    click: () => { 
      // ...更多你想要的处理逻辑 
      internalRef.current.click(); 
    } 
  })); 
  return <button ref={internalRef} ... />; 
}); 

在本例中,渲染 <FancyButton ref={buttonRef} /> 的父组件可以调用 buttonRef.current.click()

3.3 Ref 的一些魔法

我们可以用 Ref 完成很多奇思妙想的 “魔法”。

3.3.1 魔法1:记录先前的状态 —— 用 Ref 实现 usePrevious

以前用过类组件的同学,切换到函数组件。总会有疑问:previousValue 怎么实现?

function Counter() { 
  const [count, setCount] = useState(0);
  const prevCountRef = useRef(); 
  useEffect(() => { 
    prevCountRef.current = count; 
  }, [count]);
  return ( 
    <h1> 
      Now: {count}, before: {prevCountRef.current} 
      <button onClick={() => setCount((count) => count + 1)}>Increment</button> 
    </h1> 
  ); 
}
保存上次的值

当然,我们可以用这个“魔法”,封装一个 hook —— usePrevious

const usePrevious = value => { 
    const ref = useRef(); 
    useEffect(()=> { 
        ref.current = value; 
    }); 
    return ref.current; 
} 

使用它:

const [count, setCount] = useState(0); 
const prevCount = usePrevious(count); 

3.3.2 魔法2:动态获取 dom 的宽高

可以用 Ref 获取 dom 引用,获取 offsetWidthoffsetHeight

function App() { 
  const ref = useRef(null); 
 
  useEffect(() => { 
    console.log("width", ref.current.offsetWidth); 
  }, []); 
 
  return <div ref={ref}>Hello</div>; 
}

四、总结

综上所述:

  • 声明式:React 推荐的单向数据流,使用 Props
  • 命令式:React Ref(引用)

请尽量使用声明式来完成我们的组件,当 Props 做不到时,我们再使用 Ref。不要滥用!不要滥用!

五、相关资料

参考资料 :

相关阅读 :

标签:const,进阶,dom,Ref,React,组件,ref
From: https://www.cnblogs.com/sexintercourse/p/17756711.html

相关文章

  • React跨路由组件动画
    我们是袋鼠云数栈UED团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。本文作者:佳岚回顾传统React动画对于普通的React动画,我们大多使用官方推荐的react-transition-group,其提供了四个基本组件Transition、CSSTr......
  • window.location.href中文参数
    1.window.location.href=url+"&name="+encodeURI(encodeURI(name));name是中文,客户端编码两次,服务器端只需要解码一次name=java.net.URLDecoder.decode(name,"UTF-8");2.window.location.href=url+"&name="+encod......
  • 盘点KendoReact五大功能,让JavaScript数据网格构建更轻松!
    在本文中,我们将为大家分享KendoReact DataGrid中最受欢迎的五大功能:性能、数据组织、列和行交互、编辑自定义以及导出。有了这些功能,开发者大可不必从头开始构建JavaScript数据网格了!KendoUI是带有jQuery、Angular、React和Vue库的JavaScriptUI组件的最终集合,无论选择哪种Jav......
  • Unittest测试框架基础及进阶
    需求假设领导让你开发一个接口测试框架。领导提出了一些新的需求,你如何实现?支持用例优先级、标签,支持通过优先级或标签筛选用例支持用例负责人、迭代,及通过负责人或迭代筛选用例支持多环境配置支持超时及重试机制,防止不稳定用例并发执行用例以提高用例回归效率Unitt......
  • Pytest测试框架基础及进阶
    Pytest测试框架基础Pytest测试框架介绍Pytest是Python一款三方测试框架,用于编写和运行单元测试、集成测试和功能测试。Pytest测试框架具有简单、灵活、易于扩展等特点,被广泛应用于Python项目的测试工作中。Pytest主要特点:简单易用:Pytest测试框架的API简单易用,可以快速编写测......
  • Python并发及网络编程进阶
    案例引入假如你们一家已上市的电商公司,在元旦来临前夕,领导需要你模拟用户,通过接口生成10万笔新订单。你该如何处理?方案探索串行:多个任务逐个执行的过程,上个任务执行完成前,阻塞下一个任务执行。并发:多个任务交替执行的过程,这些任务可能在同一时间段内执行,但是它们的执行时......
  • FIrefox不能登陆简书、segmentfault等网站
    简书、segmentfault这些网站的登录按钮显示灰色不能点击原因是FIrefox的开启了增强型跟踪保护,关闭该选项即可。EnhancedTrackingProtectioninFirefoxfordesktop:当您处于严格增强跟踪保护状态时,您可能会在某些网站上遇到损坏。这是因为跟踪器隐藏在某些内容中。例如,网......
  • Python函数式编程进阶
    函数式编程函数式编程是一种基于函数的编程范式,它通过编写函数来描述程序的行为。函数被视为一等公民,可以作为参数、返回值和变量来使用。函数式编程通常使用高阶函数、不可变数据和递归等技术来描述程序的行为。命令式编程:基于指令的编程范式,它通过编写一系列指令来描述程......
  • 如何编写难以维护的 React 代码?耦合通用组件与业务逻辑
    在众多项目中,React代码的维护经常变得棘手。其中一个常见问题是:将业务逻辑直接嵌入通用组件中,导致通用组件与业务逻辑紧密耦合,使其失去“通用性”。这种做法使通用组件过于依赖具体业务逻辑,导致代码难以维护和扩展。示例:屎山是如何逐步堆积的让我们看一个例子:我们在业务组件Pag......
  • MySQL进阶篇:第三章_SQL性能分析
    MySQL进阶篇:第三章_SQL性能分析SQL执行频率MySQL客户端连接成功后,通过show[session|global]status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:--session是查看当前会话;--global是查询全局数据;SHOW......