Говорят, что в ReactJS использование индекса массива в качестве ключа не работает должным образом, но я не могу заставить его работать не так хорошо? - PullRequest
0 голосов
/ 18 февраля 2020

Я не могу заставить его работать не очень хорошо, если я использую индекс в качестве ключа. Единственный способ, если я изменю массив и , использую индекс в качестве ключа. Но так как документы говорят, что не нужно изменять состояние (массив), поэтому, если это так, я не могу заставить его работать не так, как утверждают документы. Как я могу показать, что он может сломаться?

function App() {
  const [arr, setArr] = React.useState(["A","B","C"]);

  function toggleSortOrder() {
    let newArr = [...arr];
    newArr.reverse();
    console.log(JSON.stringify(newArr));
    setArr(newArr);
  }

  return (
    <div>
      <ul>
        { arr.map((e, i) => <li key={i}>{ e }</li>) }
      </ul>
      <button onClick={toggleSortOrder} >Toggle Sort Order</button>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

Я могу разорвать его, если изменю состояние, которое, по словам документации, не должно выполняться:

function App() {
  const [arr, setArr] = React.useState(["A","B","C"]);

  function toggleSortOrder() {
    arr.reverse();
    console.log(JSON.stringify(arr));
    setArr(arr);
  }

  return (
    <div>
      <ul>
        { arr.map((e, i) => <li key={i}>{ e }</li>) }
      </ul>
      <button onClick={toggleSortOrder} >Toggle Sort Order</button>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

Но я даже не могу сломать его, если я изменяю состояние и использую индекс в качестве ключа, если это компонент класса:

class App extends React.Component {
  state = { arr: ["A","B","C"] };

  toggleSortOrder() {
    this.state.arr.reverse();
    console.log(JSON.stringify(this.state.arr));
    this.setState({ arr: this.state.arr });
  }

  render() {
    return (
      <div>
        <ul>
          { this.state.arr.map((e, i) => <li key={i}>{ e }</li>) }
        </ul>
        <button onClick={this.toggleSortOrder.bind(this)} >Toggle Sort Order</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

Ответы [ 2 ]

2 голосов
/ 18 февраля 2020

Ключи в основном о производительности. Скажите, реагируют, какой старый компонент соответствует какому новому, что, в свою очередь, помогает быстро определить минимальный набор изменений, необходимых для обновления домена. См. документацию по сверке.

Так что смысл, в котором ваш код "ломается", заключается в том, что реагирующий думает, что ему нужно отредактировать текст внутри каждого из <li>, когда он был возможно переставить их вместо. Вы действительно не заметите никаких проблем с производительностью своего игрушечного примера, но вы могли бы создать другие примеры, где это имеет гораздо большее значение. Вот тот, на котором я рендерил 20 000 строк и перемещал один ряд в конец. Производительность совсем не велика, но лучше, если ключи используются правильно, а не на основе индекса.

const exampleData = [];
for (let i = 0; i < 20000; i++) {
  exampleData[i] = {
    key: i,
    name: "Element " + i,
  }
}

function App() {
  const [useKeysWell, setUseKeysWell] = React.useState(false);
  const [data, setData] = React.useState(exampleData);

  function move() {
    setData(prev => {
      let newData = [...prev.slice(1), prev[0]];
      return newData;
    });
  }

  const before = Date.now();
  setTimeout(() => {
    console.log('reconciling and committing', Date.now() - before, 'ms (approx)');
  }, 0)

  return (
    <div>
      <button onClick={move}>Move one</button>
      <button onClick={() => setUseKeysWell(prev => !prev)}>Use keys {useKeysWell ? "worse" : "better"} </button>
      <ul key={useKeysWell}>
        { data.map((e, i) => <li key={useKeysWell ? e.key : i}>{ e.name }</li>) }
      </ul>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

С более сложным кодом вы также можете вызвать проблемы, если у вас есть компоненты с перехватчиками жизненного цикла, которые вы ожидаете запустить, но из-за неправильных ключей компоненты не монтируются / не монтируются / не обновляются так, как вы ожидаете.

0 голосов
/ 18 февраля 2020

Реальность такова, что с помощью индекса функции map будет работать, это не рекомендуется, потому что это анти-паттерн :

Если изменение массива путем добавления или удаления элемента, или изменения количества элементов в массиве, React будет предполагать, что элемент DOM представляет тот же компонент, что и раньше. Значение использования индекса может быть непредсказуемым, например, два элемента массива с одинаковым идентификатором.

Поэтому рекомендуется:

  • Если ваши элементы массива содержат уникальный идентификатор, или вы можете сформировать уникальный идентификатор на основе структуры, сделайте это.

  • Используйте глобальный индекс, такой как счетчик, для создания динамических c идентификаторов ... для пример:

arr.map(elem => {
    counter++;
    return (
        <li key={elem + counter}>{ elem }</li>);
    );
});

Таким образом вы гарантируете, что key имеет уникальный идентификатор.

...