react 中ref的使用

196 阅读3分钟

作用:

  1. 通过将ref属性绑定到DOM节点或者组件中,来获取DOM节点,从而操作DOM
  2. 在组件重新渲染时,保留变量的引用值

通过ref获取DOM节点

获取本组件的DOM节点

步骤一:引入hook

import { useRef } from 'react';

步骤二:在你的组件中使用它声明一个 ref

const myRef = useRef(null);

步骤三:将 ref 作为 ref 属性值传递给想要获取的 DOM 节点的 JSX 标签

<div ref={myRef}>

useRef Hook 返回一个对象,该对象有一个名为 current 的属性。最初,myRef.currentnull。当 React 为这个 <div> 创建一个 DOM 节点时,React 会把对该节点的引用放入 myRef.current。然后,你可以从 事件处理器 访问此 DOM 节点,并使用在其上定义的内置浏览器 API

示例一:组件内部只有一个ref

import { useRef } from 'react';

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

  return (
    <>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>
        聚焦输入框
      </button>
    </>
  );
}

示例二:使用ref回调管理ref列表

import { useRef, useState } from "react";

export default function CatFriends() {
  const itemsRef = useRef(null);
  const [catList, setCatList] = useState(setupCatList);

  function scrollToCat(cat) {
    const map = getMap();
    const node = map.get(cat);
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  function getMap() {
    if (!itemsRef.current) {
      // 首次运行时初始化 Map。
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToCat(catList[0])}>Tom</button>
        <button onClick={() => scrollToCat(catList[5])}>Maru</button>
        <button onClick={() => scrollToCat(catList[9])}>Jellylorum</button>
      </nav>
      <div>
        <ul>
          {catList.map((cat) => (
            <li
              key={cat}
              ref={(node) => {
                const map = getMap();
                if (node) {
                  map.set(cat, node);
                } else {
                  map.delete(cat);
                }
              }}
            >
              <img src={cat} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catList = [];
  for (let i = 0; i < 10; i++) {
    catList.push("https://loremflickr.com/320/240/cat?lock=" + i);
  }

  return catList;
}

获取子组件的DOM节点

借用forwardRef

import { forwardRef, useRef } from 'react';

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

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

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>
        聚焦输入框
      </button>
    </>
  );
}

使用ref回调来管理多个ref

import { forwardRef, useRef } from "react"

const Son = forwardRef((props, ref) => {
  const list = [1, 2, 3, 4]
  return <>
    <ul>
      {list.map(item => <li key={item} index={item}
        ref={node => {
          ref.current = ref.current || new Map()
          ref.current.set(item, node)
        }
      }>{item}</li>)}
    </ul>
  </>
})
export default function App(){
  const myRef = useRef(null)

  const inputRef = useRef(null)
  const clickBtn = index => {
    const currentRef = inputRef.current.get(index)
    console.log('当前dom:', currentRef)
    currentRef.style.border = '1px solid red'
  }
  return <>
    <input ref={myRef}/>
    <button onClick={() => myRef.current.focus()}>使输入框聚焦</button>
    <button onClick={() => clickBtn(1)}>获取第一个节点</button>
    <button onClick={() => clickBtn(2)}>获取第二个节点</button>
    <button onClick={() => clickBtn(3)}>获取第三个节点</button>


    <Son ref={inputRef}/>
  </>
}

只将子组件dom的部分api暴露给父组件(useImperativeHandle)

作用:防止父组件对子组件做一些意外的修改

import { forwardRef, useImperativeHandle, useRef } from "react"

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

  </>
})
export default function App(){
  const inputRef = useRef(null)

  return <>
    <button onClick={() => {
      inputRef.current.focus()
      console.log(inputRef.current);
    }}>使输入框聚焦</button>

    <Son ref={inputRef}/>
  </>
}

通过Ref来保存变量的值

使用时机:当你希望组件“记住”某些信息,但又不想让这些信息 触发新的渲染 时,你可以使用 ref 使用方式:

// 可以传入任何类型的参数
const ref = useRef(0);

与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!

ref与state的对比:
refstate
useRef(initialValue)返回 { current: initialValue }useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue])
更改时不会触发重新渲染更改时触发重新渲染。
可变 —— 你可以在渲染过程之外修改和更新 current 的值。“不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。
你不应在渲染期间读取(或写入) current 值。你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照