0

如果我使用索引作为键,我不能让它无法正常工作。唯一的方法是如果我改变数组使用索引作为键。但是由于文档说不要改变状态(数组),所以如果是这样的话,我不能让它不能很好地工作,这与文档所说的相反。我怎样才能证明它可能会破裂?

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>

4

2 回答 2

2

关键主要是关于性能。tell 反应哪个旧组件对应哪个新组件,这反过来又有助于它快速确定更新 dom 所需的最小更改集。请参阅有关对帐的文档。

<li>因此,您的代码“中断”的意义在于,当可以重新排列它们时,react 认为它需要编辑每个 's 中的文本。您不会真正注意到玩具示例的任何性能问题,但您可以构建其他更重要的示例。这是我渲染 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>

对于更复杂的代码,如果您的组件带有您期望运行的生命周期挂钩,但由于不正确的键,组件没有按照您期望的方式安装/卸载/更新,您也可能会导致问题。

于 2020-02-18T15:21:20.703 回答
0

现实情况是,使用map函数的索引会起作用,不建议这样做,因为它是一种反模式

如果通过添加或删除元素或修改数组中元素的数量来更改数组,React 将假定 DOM 元素表示与以前相同的组件。这意味着使用索引的影响可能是不可预测的,例如两个包含相同 id 的数组项。

因此建议:

  • 如果您的数组元素包含唯一 id,或者您可以根据结构形成唯一 id,请这样做。

  • 使用诸如计数器之类的全局索引来帮助创建动态 id... 例如:

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

这样您就可以确保key具有唯一标识符。

于 2020-02-18T15:20:16.853 回答