web-dev-qa-db-ja.com

フックを持つ要素の配列に複数の参照をどのように使用できますか?

私が理解している限り、次のような単一の要素に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>

私は this を見てきました。したがって this です。しかし、この単純なケースにその提案を実装する方法については、まだ混乱しています。

26
devserkan

Refは最初は{ current: null }オブジェクト。 useRefは、コンポーネントレンダー間でこのオブジェクトへの参照を保持します。 current値は、主にコンポーネントの参照を目的としていますが、何でも保持できます。

ある時点で参照の配列があるはずです。配列の長さがレンダリング間で異なる場合、配列はそれに応じてスケーリングする必要があります。

  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をアンラップしてuseStateuseRefに置き換えることで最適化できますが、render関数で副作用を発生させることは一般的に悪い習慣と見なされます。

  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>
  );
16
Estus Flask

あなたが ループ内でフックを使用することはできません として、配列が時間の経過とともに変化するときにそれを機能させるための解決策があります。

配列は小道具から来ていると思います:

const App = props => {
    const itemsRef = useRef([]);
    // you can access the elements with itemsRef.current[n]

    useEffect(() => {
       itemsRef.current = itemsRef.current.slice(0, props.items.length);
    }, [props.items]);

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

単純な理由でループ内でuseRefを使用しないでください。使用されるフックの順序が重要です!

ドキュメントは言う

ループ、条件、またはネストされた関数内でフックを呼び出さないでください。代わりに、常にReact関数のトップレベルでフックを使用してください。このルールに従うことで、コンポーネントがレンダリングされるたびにフックが同じ順序で呼び出されるようになります。これにより、Reactは、複数のuseState呼び出しとuseEffect呼び出しの間でフックの状態を正しく保持できます。 (ご興味があれば、以下で詳しく説明します。)

しかし、それが動的配列に明らかに適用されることを考慮してください...しかし、静的配列を使用している場合(常に同じ量のコンポーネントをレンダリングする場合)、それについてあまり気にしないで、何をしているのかに注意し、それを活用してください????

3
NoriSte

2つの方法があります

  1. refの配列を使用
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>
  1. 1つのrefを複数のcurrent要素とともに使用する
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>
1
keikai

配列(またはオブジェクト)を使用してすべての参照を追跡し、メソッドを使用して配列に参照を追加できます。

注:参照を追加および削除する場合は、レンダリングサイクルごとに配列を空にする必要があります。

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

1
Neo

Renderメソッドが呼び出される前にrefが利用可能である必要があるため、stateは使用できません。 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();
}
0
Greg