How target DOM with react useRef in map

Bro*_*nBe 13 javascript reactjs react-hooks

I looking for a solution about get an array of DOM elements with react useRef() hook.

example:

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>
}
Run Code Online (Sandbox Code Playgroud)

How can I achieve this?

sky*_*yer 16

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

useRef挂钩旨在在渲染之间存储一些数据,并且更改数据不会触发重新渲染(与之不同useState)。

还要提醒您:最好避免在循环或中初始化钩子if。这是钩子第一法则

考虑到这一点,我们:

  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>)
    }
    
    Run Code Online (Sandbox Code Playgroud)

这是我们可以安全地修改数组(例如,通过更改其长度)。但是请不要忘记,存储由突变的数据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} />
  </>)
}
Run Code Online (Sandbox Code Playgroud)

另外,请勿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>)
Run Code Online (Sandbox Code Playgroud)

所以你要么守卫

      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>)
Run Code Online (Sandbox Code Playgroud)

或通过useEffect钩子访问它。原因:refs是在元素渲染之后绑定的,因此在渲染第一次运行时尚未初始化。

  • @Shofol 的问题是它将创建一个 ref,然后用它填充 useRef 数组。因此,如果您尝试引用这些项目中的任何一项,它们都将引用一个实体。`const refs = useRef([...new Array(3)].map(() =&gt; React.createRef()));` 是我发现的更好的选择。 (3认同)
  • 您是老板,非常感谢您提出的建议!useRef(elts.map(React.createRef))确实是我想要的,目的是防止功能组件丢掉我的引用。 (2认同)
  • 非常感谢您的解释,@skyboyer。我已经使用这样的方式使其动态化 - ```const refs = useRef( new Array(2).fill(React.createRef())); ``。这会导致问题吗? (2认同)

beq*_*eqa 14

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

初始化:

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

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

用法:

  refs[i+1 % 3].current.focus();
Run Code Online (Sandbox Code Playgroud)

  • 您能否详细说明为什么“useMemo”受到青睐以及您指的是什么错误? (3认同)
  • 我收到:“警告:不要在 useEffect(...)、useMemo(...) 或其他内置 Hook 中调用 Hook” (3认同)

小智 5

获取父引用并操纵孩子的

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

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


tuf*_*dos 5

您可以将每个映射项分隔为组件,而不是使用引用数组或类似的东西。当你分开它们时,你可以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>
}
Run Code Online (Sandbox Code Playgroud)


dot*_*nor 2

如果您提前知道数组的长度(在示例中这样做),您可以简单地创建一个引用数组,然后按索引分配每个引用:

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>
  )
}
Run Code Online (Sandbox Code Playgroud)

  • `React Hook“useRef”无法在回调内调用。React Hooks 必须在 React 函数组件或自定义 React Hook 函数中调用`知道为什么吗? (4认同)
  • @LirenYeo 由于 [Hooks 规则](https://reactjs.org/docs/hooks-rules.html#explanation),特别是 **React 依赖于调用 Hooks 的顺序。** 调用的 Hook回调会导致调用序列不稳定。 (2认同)
  • 所以它应该是 `const items = useRef(Array.from({length: 2}, () =&gt; React.createRef()))` (2认同)