据我所知,我可以像这样为单个元素使用refs:

const { useRef, useState, useEffect } = React; const App = () => { const elRef = useRef(); const [elWidth, setElWidth] = useState(); useEffect(() => { setElWidth(elRef.current.offsetWidth); }, []); return ( <div> <div ref={elRef} style={{ width: "100px" }}> Width is: {elWidth} </div> </div> ); }; ReactDOM.render( <App />, document.getElementById("root") ); <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"></div>

我如何实现这个元素数组?显然不是这样的:(我知道,即使我没有尝试:)

const { useRef, useState, useEffect } = React; const App = () => { const elRef = useRef(); const [elWidth, setElWidth] = useState(); useEffect(() => { setElWidth(elRef.current.offsetWidth); }, []); return ( <div> {[1, 2, 3].map(el => ( <div ref={elRef} style={{ width: `${el * 100}px` }}> Width is: {elWidth} </div> ))} </div> ); }; ReactDOM.render( <App />, document.getElementById("root") ); <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"></div>

我见过这个,所以才会这样。但是,我仍然不知道如何在这个简单的案例中实现这个建议。


当前回答

您可以使用数组(或对象)来跟踪所有的引用,并使用方法向数组添加ref。

注意:如果你添加和删除引用,你必须在每个渲染周期清空数组。

import React, { useRef } from "react";

const MyComponent = () => {
   // intialize as en empty array
   const refs = useRefs([]); // or an {}
   // Make it empty at every render cycle as we will get the full list of it at the end of the render cycle
   refs.current = []; // or an {}

   // since it is an array we need to method to add the refs
   const addToRefs = el => {
     if (el && !refs.current.includes(el)) {
       refs.current.push(el);
     }
    };
    return (
     <div className="App">
       {[1,2,3,4].map(val => (
         <div key={val} ref={addToRefs}>
           {val}
         </div>
       ))}
     </div>
   );

}

工作示例 https://codesandbox.io/s/serene-hermann-kqpsu

其他回答

ref最初只是一个{current: null}对象。useRef在组件渲染之间保持对该对象的引用。当前值主要用于组件引用,但可以保存任何内容。

在某些时候应该有一个refs数组。如果数组的长度可能在不同的渲染之间变化,数组应该相应地缩放:

const arrLength = arr.length;
const [elRefs, setElRefs] = React.useState([]);

React.useEffect(() => {
  // add or remove refs
  setElRefs((elRefs) =>
    Array(arrLength)
      .fill()
      .map((_, i) => elRefs[i] || createRef()),
  );
}, [arrLength]);

return (
  <div>
    {arr.map((el, i) => (
      <div ref={elRefs[i]} style={...}>
        ...
      </div>
    ))}
  </div>
);

这段代码可以通过展开useEffect并将useState替换为useRef来优化,但应该注意的是,在呈现函数中执行副作用通常被认为是一种糟糕的做法:

const arrLength = arr.length;
const elRefs = React.useRef([]);

if (elRefs.current.length !== arrLength) {
  // add or remove refs
  elRefs.current = Array(arrLength)
    .fill()
    .map((_, i) => elRefs.current[i] || createRef());
}

return (
  <div>
    {arr.map((el, i) => (
      <div ref={elRefs.current[i]} style={...}>
        ...
      </div>
    ))}
  </div>
);

我们不能使用state,因为我们需要在render方法被调用之前ref是可用的。 我们不能任意多次调用useRef,但可以只调用一次:

假设arr是一个道具数组:

const refs = useRef([]);
// free any refs that we're not using anymore
refs.current = refs.current.slice(0, arr.length);
// initialize any new refs
for (let step = refs.current.length; step < arr.length; step++) {
    refs.current[step] = createRef();
}

通过将子元素移动到单独的组件中,可以避免数组引用与useEffect结合带来的复杂性。这还有其他优点,主要是可读性强,易于维护。

const { useRef, useState, useEffect } = React;

const ListComponent = ({ el }) => {
  const elRef = useRef();
  const [elWidth, setElWidth] = useState();

  useEffect(() => {
    setElWidth(elRef.current.offsetWidth);
  }, []);

  return (
    <div ref={elRef} style={{ width: `${el * 100}px` }}>
      Width is: {elWidth}
    </div>
  );
};

const App = () => {

  return (
    <div>
      {[1, 2, 3].map((el, i) => (
        <ListComponent key={i} el={el} />
      ))}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
import { createRef } from "react";

const MyComponent = () => {
  const arrayOfElements = Array.from({ length: 10 }).map((_, idx) => idx + 1);
  const refs = arrayOfElements.map(() => createRef(null));

  const onCLick = (index) => {
    ref[index]?.current?.click();
  };

  return (
    <div>
      <h1>Defaults Elements</h1>
      {arrayOfElements.map((element, index) => (
        <div key={index} ref={refs[index]}>
          Default Elemnt {element}
        </div>
      ))}

      <h2>Elements Handlers</h2>
      {arrayOfElements.map((_, index) => (
        <button key={index} onClick={() => onCLick(index)}>
          Element {index + 1} Handler
        </button>
      ))}
    </div>
  );
};

更新

新React文档显示了一个使用地图的推荐方法。

点击这里查看测试版(2022年12月)


有两种方法

对多个当前元素使用一个引用

const inputRef = useRef([]);

inputRef.current[idx].focus();

<input
  ref={el => inputRef.current[idx] = el}
/>

const {useRef} = React; const App = () => { const list = [...Array(8).keys()]; const inputRef = useRef([]); const handler = idx => e => { const next = inputRef.current[idx + 1]; if (next) { next.focus() } }; return ( <div className="App"> <div className="input_boxes"> {list.map(x => ( <div> <input key={x} ref={el => inputRef.current[x] = el} onChange={handler(x)} type="number" className="otp_box" /> </div> ))} </div> </div> ); } ReactDOM.render(<App />, document.getElementById("root")); <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

使用ref的数组 正如上面的帖子所说,不建议这样做,因为官方指南(以及内部棉绒检查)不允许它通过。 不要在循环、条件或嵌套函数中调用hook。相反,总是在React函数的顶层使用Hooks。通过遵循此规则,您可以确保在每次组件呈现时以相同的顺序调用hook。 然而,由于这不是我们目前的情况,下面的演示仍然可以工作,只是不推荐。

const inputRef = list.map(x => useRef(null));

inputRef[idx].current.focus();

<input
  ref={inputRef[idx]}
/>

const {useRef} = React; const App = () => { const list = [...Array(8).keys()]; const inputRef = list.map(x => useRef(null)); const handler = idx => () => { const next = inputRef[idx + 1]; if (next) { next.current.focus(); } }; return ( <div className="App"> <div className="input_boxes"> {list.map(x => ( <div> <input key={x} ref={inputRef[x]} onChange={handler(x)} type="number" className="otp_box" /> </div> ))} </div> </div> ); } ReactDOM.render(<App />, document.getElementById("root")); <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>