React中的Ref、UseRef、React.forwardRef和useImperativeHandle

  • 类组件中的 Ref

    • -What-

      • 在 React类组件中,ref 是用来 获取组件 或者 DOM元素的引用 的一种机制。通过 Ref ,可以在函数组件中直接访问子组件的实例或者访问 DOM 元素。
  • useRef

    • -What-

      • React 中的 useRef 是一个 Hook 函数,它可以用来在函数组件中创建一个可变的引用。类似于类组件中的 ref 属性,但有一些不同之处:useRef 可以用来保存任意可变值,而不仅仅是 DOM 元素的引用。useRef返回一个包含一个current属性的对象。这个current属性可以存储任意值,并且在函数组件的多次渲染之间保持不变。
    • -Why-

      • 直接操作DOM:有时需要直接访问和操作 DOM 元素
      • 测量和布局:当需要精确测量元素尺寸或位置时,ref可以提供直接访问 DOM 的方式
      • 表单处理:在处理非受控组件时,ref可以用来获取表单元素的值
      • 动画和过渡:在实现复杂的动画或过渡时,可能需要直接操作 DOM 元素
      • 性能优化:通过 ref 直接操作 DOM 有时可以避免不必要的重新渲染,从而提高性能
      • 访问子组件的属性或方法:某些情况下,需要访问子组件的方法或属性,特别是在使用第三方库时
      • 保存可变值:useRef 可以用来存储 在组件重新渲染之间保持不变的可变值,而不触发重新渲染(类似vue的计算属性)
    • -How-

      • 基本语法
        // 基本语法
        import { useRef } from 'react';
        
        function MyComponent() {
          //初始值可以是任意类型的值
          const ref = useRef(initialValue);
          // ...
        }
      • 获取 DOM 操作 DOM
        import { useRef, useEffect } from 'react';
        
        function MyComponent() {
          const inputRef = useRef();
        
          useEffect(() => {
            inputRef.current.focus();
          }, []);
        
          return (
            <input ref={inputRef} />
          );
        }
      • 存储任意值
        import { useRef } from 'react';
        function MyComponent() {
          const countRef = useRef(0);
          function handleClick() {
            countRef.current += 1;
            console.log(countRef.current);
          }
          return (
            <button onClick={handleClick}>Click me</button>
          );
        }
        
  • React.forwardRef

    • -What-

      • forwardRef 会创建一个 React 组件,这个组件能够将其接收的 ref 属性 发到其组件树下的另一个组件中。目的就是希望外层组件可以通过 ref 直接控制内层组件或元素。
    • -Why-

      • 在 React里给函数组件直接写 ref 属性时,控制台会报警告
        // 父组件
        import { useRef } from "react";
        import ComChild from "./components/HOC/ComChild1";
        export default function App() {
          const ChildRef = useRef()
          return (
            <div>
              <ComChild name='demo' ref={ChildRef} />
            </div>
          );
        }
        
        // 子组件
        export default function ComChild(props: { name: string, ref: React.MutableRefObject<undefined> }) {
            return <div>子组件1 name:{props.name}</div>;
        }
        
        

      • 函数组件的设计初衷:React 的函数组件的设计初中是为了简化组件的创建和使用,强调无状态和纯函数的概念。更适合用于展示性组件,而不涉及复杂的实例管理
      • 状态管理的不同:函数组件通常使用 React Hooks(如 useState 、useEffect) 来管理状态和副作用,而不是依赖于实例属性和方法。ref 的使用在函数组件中需要通过 useRef 来实现,这与类组件的 ref 使用方式不同。
      • 一致性和可维护性:通过React.forwardRef 明确地将 ref 转发到子组件,可以提高代码的一致性和可维护性。这样可以清晰的看到 ref 的传递路径,避免了直接在函数组件中使用 ref 可能带来的混乱。
    • -How-

      // 子组件
      import {useRef,forwardRef} from 'react'
      const ComChild = forwardRef((props, ref) => {
          return (
            <div>
                <input type="text" defaultValue={props.value} ref={ref} />
                <button onClick={() => console.log(ref.current)}>点击打印ref</button>
            </div>
          )
      })
      
      // 父组件
      const myComponent = () => {
          const childRef = useRef()
          return (
            <ComChild ref={childRef} value='这是子组件的value值' />
          )
      }
      
  • UseImperativeHandle

    • -What-

      • 该 Hook 一般配合 React.forwardRef 使用,主要用于在父组件中可以使用 ref 获取子组件暴露出来的属性和方法
    • -Why-

      • 用于自定义暴露给父组件的 ref 对象。它允许在函数组件中控制 ref 的内容,而不仅仅是将 ref 直接指向 DOM 元素或组件实例
    • 好处

      • 封装内部实现:通过 useImperativeHandle,可以控制哪些方法和属性暴露给父组件,从而封装组件的内部实现细节,提供更清晰可控的接口
      • 提高代码的可维护性:通过限制暴露的接口,可以更容易地维护和修改组件的内部实现,而不影响依赖该数组的父组件
      • 增强组件的复用性:可以定义一组特定的方法和属性,使得组件在不同的上下文种更容易复用,而不用暴露所有的内部细节
    • 总结

      • useImperativeHandle 可以在函数组件中自定义暴露给父组件的 ref 对象,从而更好地封装组件的内部实现,提高组件的可复用性和可维护性。

    • -How-

      • 配合 React.forwardRef 的使用方法
        // 子组件
        import { forwardRef, useImperativeHandle } from "react";
        const ComChild1 = forwardRef<{ handleFunction: () => void, test?: string }, { name: string }>((props, ChildRef) => {
            useImperativeHandle(ChildRef, () => ({
                handleFunction,
                test: '子节点的文本'
            }));
            const handleFunction = () => {
                console.log('父组件点击');
            }
            return <div>子组件1 name:{props.name}</div>;
        })
        export default ComChild1
        
        
        
        // 父组件
        import { useEffect, useRef, useState } from "react";
        import ComChild1 from "./components/HOC/ComChild1";
        export default function App() {
          const [show1, setShow1] = useState(true)
          const ChildRef = useRef<{ handleFunction: () => void, text?: string }>(null)
          const toggleOnOff = () => {
            setShow1(!show1)
            ChildRef.current?.handleFunction()
          }
          useEffect(() => {
            console.log(ChildRef.current, '******');
          }, [])
          return (
            <div>
              <button onClick={toggleOnOff}>toggle</button>
              <ComChild1 name='demo' ref={ChildRef} />
            </div>
          );
        }
        
        
      • 不配合 React.forwardRef 的写法
        // 父组件
        
        // 定义 ref
          const orderRef = useRef<{ open: (type: IAction, data?: Order.OrderItem) => void }>()
        // 使用子组件的方法 
          const handleAddOrder = () => {
            orderRef.current?.open('create')
          }
        // 引入子组件
          <CreateOrder mRef={orderRef} />
        
        // 子组件
        // 定义 ref类型 包含一个 open 方法 接收两个参数
        export interface IModalProp<T> {
          mRef: MutableRefObject<{ open: (type: IAction, data: T) => void } | undefined>
          update: () => void
        }
        
        const CreateOrder = (props: IModalProp<Order.OrderItem>) => {
          // 传递给父组件 这个open 方法
          useImperativeHandle(props.mRef, () => {
            return {
              open
            }
          })
          // 定义 open 方法
          const open = (type: IAction, data?: Order.OrderItem) => {
            setAction(type)
          }
          -----
          
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值