如何在地图中使用 useRef react目标 DOM

IT技术 javascript reactjs react-hooks
2021-04-15 08:19:44

我正在寻找有关使用 reactuseRef()钩子获取 DOM 元素数组的解决方案

例子:

const Component = () => 
{

  // In `items`, I would like to get an array of DOM element
  let items = useRef(null);

  return <ul>
    {['left', 'right'].map((el, i) =>
      <li key={i} ref={items} children={el} />
    )}
  </ul>
}

我怎样才能做到这一点?

6个回答

useRef只是部分类似于 React ref(只是对象的结构,只有 字段current)。

useRefhook 的目标是在渲染之间存储一些数据,并且更改该数据不会触发重新渲染(不像useState那样)。

也只是温和的提醒:最好避免在循环或if. 是 hooks第一条规则

考虑到这一点,我们:

  1. 创建数组并将其保留在渲染之间 useRef

  2. 我们初始化每个数组的元素 createRef()

  3. 我们可以使用.current符号来引用列表

    const Component = () => {
    
      let refs = useRef([React.createRef(), React.createRef()]);
    
      useEffect(() => {
        refs.current[0].current.focus()
      }, []);
    
      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}><input ref={refs.current[i]} value={el} /></li>
        )}
      </ul>)
    }
    

这样我们就可以安全地修改数组(比如通过改变它的长度)。但是不要忘记存储的变异数据useRef不会触发重新渲染。因此,要更改长度以重新渲染,我们需要涉及useState.

const Component = () => {

  const [length, setLength] = useState(2);
  const refs = useRef([React.createRef(), React.createRef()]);

  function updateLength({ target: { value }}) {
    setLength(value);
    refs.current = refs.current.splice(0, value);
    for(let i = 0; i< value; i++) {
      refs.current[i] = refs.current[i] || React.createRef();
    }
    refs.current = refs.current.map((item) => item || React.createRef());
  }

  useEffect(() => {
   refs.current[refs.current.length - 1].current.focus()
  }, [length]);

  return (<>
    <ul>
    {refs.current.map((el, i) =>
      <li key={i}><input ref={refs.current[i]} value={i} /></li>
    )}
  </ul>
  <input value={refs.current.length} type="number" onChange={updateLength} />
  </>)
}

也不要尝试refs.current[0].current在第一次渲染时访问- 它会引发错误。

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

所以你要么保护它

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

或在useEffect钩子中访问它原因:refs 在元素渲染后被绑定,所以在渲染过程中第一次运行它尚未初始化。

你是老板,非常感谢你的安排! useRef(elts.map(React.createRef))正是我一直在寻找的,以保持该功能module从扔我的裁判了。
2021-05-26 08:19:44
漂亮👏👏👏
2021-06-08 08:19:44
非常感谢您的解释,@skyboyer。我使用这样的方式使它动态 - const refs = useRef( new Array(2).fill(React.createRef())); 这会导致问题吗?
2021-06-09 08:19:44
任何人都知道如何在typescript版本中实现这一点?
2021-06-15 08:19:44
@Shofol 的问题是它会创建一个单一的 ref,然后用它填充 useRef 数组..所以,如果你尝试引用这些项目中的任何一个,它们都将引用一个实体。const refs = useRef([...new Array(3)].map(() => React.createRef()));是我发现的更好的选择。
2021-06-21 08:19:44

我会稍微扩展一下skyboyer的回答对于性能优化(并避免潜在的莫名其妙的错误),你可能更愿意使用useMemo来代替useRef因为 useMemo 接受回调作为参数而不是值,所以React.createRef只会在第一次渲染后初始化一次。在回调中,您可以返回一个createRef值数组并适当地使用该数组。

初始化:

  const refs= useMemo(
    () => Array.from({ length: 3 }).map(() => createRef()),
    []
  );

这里的空数组(作为第二个参数)告诉 React 只初始化 refs 一次。如果引用计数发生变化,您可能需要将其[x.length]作为“deps 数组”传递并动态创建引用:Array.from({ length: x.length }).map(() => createRef()),

用法:

  refs[i+1 % 3].current.focus();
你能提供一些细节为什么useMemo是首选以及你的意思是什么错误?
2021-05-22 08:19:44
我得到:“警告:不要在 useEffect(...)、useMemo(...) 或其他内置 Hooks 中调用 Hooks”
2021-06-16 08:19:44

获取父引用并操纵孩子的

const Component = () => {
  const ulRef = useRef(null);

  useEffect(() => {
    ulRef.current.children[0].focus();
  }, []);

  return (
    <ul ref={ulRef}>
      {['left', 'right'].map((el, i) => (
        <li key={i}>
          <input value={el} />
        </li>
      ))}
    </ul>
  );
};

我以这种方式工作,我认为这比其他建议的答案更简单。

有趣的想法
2021-06-13 08:19:44

您可以将每个地图项与组件分开,而不是使用 refs 数组或类似的东西。当你把它们分开时,你可以useRef独立使用s:

const DATA = [
  { id: 0, name: "John" },
  { id: 1, name: "Doe" }
];

//using array of refs or something like that:
function Component() {
  const items = useRef(Array(DATA.length).fill(createRef()));
  return (
    <ul>
      {DATA.map((item, i) => (
        <li key={item.id} ref={items[i]}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}


//seperate each map item to component:
function Component() {
  return (
    <ul>
      {DATA.map((item, i) => (
        <MapItemComponent key={item.id} data={item}/>
      ))}
    </ul>
  );
}

function MapItemComponent({data}){
  const itemRef = useRef();
  return <li ref={itemRef}>
    {data.name}
  </li>
}

添加@StephenOstermiller
2021-05-31 08:19:44
感谢更新,看起来好多了。
2021-06-04 08:19:44

如果你提前知道数组的长度,你在你的例子中做的,你可以简单地创建一个 refs 数组,然后通过它们的索引分配每个数组:

const Component = () => {
  const items = Array.from({length: 2}, a => useRef(null));
  return (
    <ul>
      {['left', 'right'].map((el, i)) => (
        <li key={el} ref={items[i]}>{el}</li>
      )}
    </ul>
  )
}
所以应该是 const items = useRef(Array.from({length: 2}, () => React.createRef()))
2021-05-22 08:19:44
@LirenYeo 由于Hooks 规则,特别是React 依赖于调用 Hooks 的顺序。 在回调中调用的 Hook 会导致调用序列不稳定。
2021-05-23 08:19:44
React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function 知道为什么吗?
2021-06-10 08:19:44