javascript 如何在地图中使用 useRef 反应目标 DOM

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/54940399/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 10:28:34  来源:igfitidea点击:

How target DOM with react useRef in map

javascriptreactjsreact-hooks

提问by BrownBe

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

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

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>
}

How can I achieve this?

我怎样才能做到这一点?

回答by skyboyer

useRefis just partially similar to React's ref(just structure of object with only field of current).

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

useRefhook is aiming on storing some data between renders and changing that data does not trigger re-rendering(unlike useStatedoes).

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

Also just gentle reminder: better avoid initialize hooks in loops or if. It's first rule of hooks.

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

Having this in mind we:

考虑到这一点,我们:

  1. create array and keep it between renders by useRef
  2. we initialize each array's element by createRef()
  3. we can refer to list by using .currentnotation

    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>)
    }
    
  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>)
    }
    

This was we can safely modify array(say by changing it's length). But don't forget that mutating data stored by useRefdoes not trigger re-render. So to make changing length to re-render we need to involve useState.

这是我们可以安全地修改数组(比如通过改变它的长度)。但是不要忘记存储的变异数据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} />
  </>)
}

Also don't try to access refs.current[0].currentat first rendering - it will raise an error.

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

Say

      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>)

So you either guard it as

所以你要么保护它

      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>)

or access it in useEffecthook. Reason: refs are bound after element is rendered so during rendering is running for the first time it is not initialized yet.

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

回答by beqa

I'll expand on skyboyer's answera bit. For performance optimization (and to avoid potential weird bugs), you might prefer to use useMemoinstead of useRef. Because useMemo accepts a callback as an argument instead of a value, React.createRefwill only be initialized once, after the first render. Inside the callback you can return an array of createRefvalues and use the array appropriately.

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

Initialization:

初始化:

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

Empty array here (as a second argument) tells React to only initialize refs once. If ref count changes you may need to pass [x.length]as "a deps array" and create refs dynamically: Array.from({ length: x.length }).map(() => createRef()),

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

Usage:

用法:

  refs[i+1 % 3].current.focus();

回答by dotconnor

If you know the length of the array ahead of time, to which you do in your example you can simply create an array of refs and then assign each one by their index:

如果你提前知道数组的长度,你在你的例子中做的,你可以简单地创建一个 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>
  )
}