据我所知,我可以像这样为单个元素使用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来记住ref列表:

import { RefObject, useRef } from 'react';

type RefObjects<T> = RefObject<T>[];

function convertLengthToRefs<T>(
  length: number,
  initialValue: T | null,
): RefObjects<T> {
  return Array.from(new Array(length)).map<RefObject<T>>(() => ({
    current: initialValue,
  }));
}

export function useRefs<T>(length: number, initialValue: T | null = null) {
  const refs = useRef<RefObjects<T>>(convertLengthToRefs(length, initialValue));

  return refs.current;
}

这是一个演示:

const dataList = [1, 2, 3, 4];

const Component: React.FC = () => {
  const refs = useRefs<HTMLLIElement>(dataList.length, null);

  useEffect(() => {
    refs.forEach((item) => {
      console.log(item.current?.getBoundingClientRect());
    });
  }, []);

  return (
    <ul>
      {dataList.map((item, index) => (
        <li key={item} ref={refs[index]}>
          {item}
        </li>
      ))}
    </ul>
  );
};


其他回答

您可以使用父元素来获得一堆子元素。

在我的情况下,我试图得到一串输入在我的表单元素,然后我得到的形式元素,并使用它来处理所有的输入。

就像这样:

function Foo() {
    const fields = useRef<HTMLFormElement>(null);

    function handlePopUp(e) {
      e.preventDefault();
    
      Array.from(fields.current)
        .forEach((input: HTMLInputElement | HTMLTextAreaElement) => {
          input.value = '';
        });
    }

    return (
    <form onSubmit={(e) => handlePopUp(e)} ref={fields}>

      <input
        placeholder="Nome"
        required
        id="name"
        type="text"
        name="name"
      />
      <input
        placeholder="E-mail"
        required
        id="email"
        type="email"
        name="email"
      />
      <input
        placeholder="Assunto"
        required
        id="subject"
        type="text"
        name="subject"
      />
      <textarea
        cols={120}
        placeholder="Descrição"
        required
        id="description"
        name="description"
      />

      <button type="submit" disabled={state.submitting}>enviar</button>
    </form>  
    );
}

假设您的数组包含非原语,您可以使用WeakMap作为Ref的值。

function MyComp(props) {
    const itemsRef = React.useRef(new WeakMap())

    // access an item's ref using itemsRef.get(someItem)

    render (
        <ul>
            {props.items.map(item => (
                <li ref={el => itemsRef.current.set(item, el)}>
                    {item.label}
                </li>
            )}
        </ul>
    )
}

我使用useRef钩子创建我想独立控制的数据面板。首先我初始化useRef来存储一个数组:

import React, { useRef } from "react";

const arr = [1, 2, 3];

const refs = useRef([])

在初始化数组时,我们观察到它实际上是这样的:

//refs = {current: []}

然后我们应用map函数来创建面板,使用我们将引用的div标签,将当前元素添加到我们的引用中。带有一个按钮的当前数组:

arr.map((item, index) => {
  <div key={index} ref={(element) => {refs.current[index] = element}}>
    {item}
    <a
      href="#"
      onClick={(e) => {
        e.preventDefault();
        onClick(index)
      }}
    >
      Review
    </a>
})

最后一个函数接收按下按钮的索引,我们可以控制我们想要显示的面板

const onClick = (index) => {
  console.log(index)
  console.log(refs.current[index])
}

最后,完整的代码是这样的

import React, { useRef } from "react";

const arr = [1, 2, 3];

const refs = useRef([])
//refs = {current: []}

const onClick = (index) => {
  console.log(index)
  console.log(refs.current[index])
}

const MyPage = () => {
   const content = arr.map((item, index) => {
     <div key={index} ref={(element) => {refs.current[index] = element}}>
       {item}
       <a
         href="#"
         onClick={(e) => {
           e.preventDefault();
           onClick(index)
         }}
       >
         Review
       </a>
   })
   return content
}

export default MyPage

这对我很管用!希望这些知识对你有用。

我们不能使用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应该只用于副作用,因此我选择使用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])