据我所知,我可以像这样为单个元素使用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>

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


当前回答

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

其他回答

如果我理解正确的话,useEffect应该只用于副作用,因此我选择使用useMemo。

const App = props => {
    const itemsRef = useMemo(() => Array(props.items.length).fill().map(() => createRef()), [props.items]);

    return props.items.map((item, i) => (
        <div 
            key={i} 
            ref={itemsRef[i]} 
            style={{ width: `${(i + 1) * 100}px` }}>
        ...
        </div>
    ));
};

然后如果你想操纵物品/使用副作用,你可以这样做:

useEffect(() => {
    itemsRef.map(e => e.current).forEach((e, i) => { ... });
}, [itemsRef.length])

最简单和最有效的方法是根本不使用useRef。只需使用一个回调ref,在每次渲染时创建一个新的ref数组。

function useArrayRef() {
  const refs = []
  return [refs, el => el && refs.push(el)]
}

Demo

<div id="root"></div> <script type="text/babel" defer> const { useEffect, useState } = React function useArrayRef() { const refs = [] return [refs, el => el && refs.push(el)] } const App = () => { const [elements, ref] = useArrayRef() const [third, setThird] = useState(false) useEffect(() => { console.log(elements) }, [third]) return ( <div> <div ref={ref}> <button ref={ref} onClick={() => setThird(!third)}>toggle third div</button> </div> <div ref={ref}>another div</div> { third && <div ref={ref}>third div</div>} </div> ); } ReactDOM.render(<App />, document.getElementById("root")); </script> <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script> <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

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

通过将子元素移动到单独的组件中,可以避免数组引用与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'));

我们不能使用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();
}