Published on

React ref相关

Authors
  • avatar
    Name
    李丹秋
    Twitter

使用Ref引用值

function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue })
  return ref
}

useRef理论上可以使用useState实现 如果你的组件需要存储一些值,但不影响渲染逻辑,请选择ref

React Ref 操纵Dom

import { forwardRef, useRef } from 'react';

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

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

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

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

最简单的方案是声明一个ref对象,将其挂载到对应的Dom元素上。这样就可以通过ref.current的方式获取到dom元素。 但是我们的本意只是想要调用focus方法,现在确直接暴露了dom,可以进行其他操作,可以用useImperativeHandle这个hooks,将子组件想要暴露的方法,直接传递给ref。

import {
  forwardRef, 
  useRef, 
  useImperativeHandle
} from 'react';

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

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

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

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

FlushSync

const newTodo = { id: nextId++, text: text };
    setText('');
    setTodos([ ...todos, newTodo]);
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });

上面代码中,我们给列表中添加一个新的todo项,setTodos之后,想要直接跳转到列表的最后一项,但是根据我们前面掌握的react渲染机制,这显然是不行的,每一次操作触发的渲染,都是一个批处理队列,setTodos所做的改变并不能直接生效。

import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

export default function TodoList() {
  const listRef = useRef(null);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(
    initialTodos
  );

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    flushSync(() => {
      setText('');
      setTodos([ ...todos, newTodo]);      
    });
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  });
}

可以使用flushSync这个方法,来实现dom的事实更新。