在React中从列表中删除一个项目的详细教程

882 阅读6分钟

在React中,从列表中删除一个项目是一项常见的任务。在这里,我想向你简要地展示一下这是如何工作的。每次你想修改React中的东西,比如说你想删除一个项目的列表,你都必须使用React的状态管理。我们将在这里使用React的useState Hook,为了保持第一个例子的简单,然而,你也可以使用React的useReducer Hook,你将在后面看到。

我们将从React中一个典型的列表开始,我们为每个渲染的列表项提供一个稳定的关键属性

import React from 'react';

const list = [
  {
    id: 'a',
    firstname: 'Robin',
    lastname: 'Wieruch',
    year: 1988,
  },
  {
    id: 'b',
    firstname: 'Dave',
    lastname: 'Davidds',
    year: 1990,
  },
];

const App = () => {
  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span>{item.firstname}</span>
          <span>{item.lastname}</span>
          <span>{item.year}</span>
        </li>
      ))}
    </ul>
  );
};

export default App;

到目前为止,这个列表只是一个JavaScript变量,还不是有状态的。为了修改它,在这种情况下,为了从其中删除一个项目,我们需要用React的状态和useState Hook使列表有状态。

const initialList = [
  {
    id: 'a',
    firstname: 'Robin',
    lastname: 'Wieruch',
    year: 1988,
  },
  {
    id: 'b',
    firstname: 'Dave',
    lastname: 'Davidds',
    year: 1990,
  },
];

const App = () => {
  const [list, setList] = React.useState(initialList);

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span>{item.firstname}</span>
          <span>{item.lastname}</span>
          <span>{item.year}</span>
        </li>
      ))}
    </ul>
  );
};

现在我们有了一个有状态的列表,我们能够改变它。让我们添加一个带有处理函数的按钮,处理列表中每个项目的点击事件。在这种情况下,这个按钮应该是用来删除一个项目的。

const App = () => {
  const [list, setList] = React.useState(initialList);

  function handleRemove() {
    // remove item
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span>{item.firstname}</span>
          <span>{item.lastname}</span>
          <span>{item.year}</span>
          <button type="button" onClick={handleRemove}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
};

由于我们是在一个映射的列表中,我们需要想办法把我们想从列表中删除的具体项目,或者项目的标识符传递给处理函数。最直接的方法是使用一个内联处理程序,将项目或项目标识符作为参数悄悄输入。

const App = () => {
  const [list, setList] = React.useState(initialList);

  function handleRemove(id) {
    console.log(id);
    // remove item
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span>{item.firstname}</span>
          <span>{item.lastname}</span>
          <span>{item.year}</span>
          <button type="button" onClick={() => handleRemove(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
};

唯一缺少的是在点击按钮时从列表中删除特定的项目。我们将通过用一个过滤函数来修改当前的有状态列表来做到这一点。

const App = () => {
  const [list, setList] = React.useState(initialList);

  function handleRemove(id) {
    const newList = list.filter((item) => item.id !== id);

    setList(newList);
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span>{item.firstname}</span>
          <span>{item.lastname}</span>
          <span>{item.year}</span>
          <button type="button" onClick={() => handleRemove(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
};

我们不改变列表,而是将其作为不可变的数据结构,因此根据旧的列表和过滤条件创建一个新的列表。这是因为过滤函数并不修改列表,而只是返回一个新的列表。

现在,当我们来自React的useState Hook的state updater函数被调用时,没有项目的列表被设置为新的状态,组件重新渲染,只显示剩余的项目。这就是关于在React中从数组中删除一个条目的所有知识。但还有更多...

例如,在我们的案例中,一切都发生在一个组件中。如果你想从一个子组件的列表中删除一个项目,会发生什么?让我们继续把组件分割成多个组件。我们将需要一个回调处理程序,将功能作为去结构化的 道具来传递,以便删除一个项目。

const App = () => {
  const [list, setList] = React.useState(initialList);

  function handleRemove(id) {
    const newList = list.filter((item) => item.id !== id);

    setList(newList);
  }

  return <List list={list} onRemove={handleRemove} />;
};

const List = ({ list, onRemove }) => (
  <ul>
    {list.map((item) => (
      <Item key={item.id} item={item} onRemove={onRemove} />
    ))}
  </ul>
);

const Item = ({ item, onRemove }) => (
  <li>
    <span>{item.firstname}</span>
    <span>{item.lastname}</span>
    <span>{item.year}</span>
    <button type="button" onClick={() => onRemove(item.id)}>
      Remove
    </button>
  </li>
);

这就是了。你能够从一个子组件中删除一个项目,而列表则作为状态在父组件的某个地方被管理。如果你想在List组件中作为状态管理列表,而不是在App组件中管理它,你就必须解除状态

现在,我们将继续把React的useState与React的useReducer Hook交换。在React中,reducer钩子可以用于复杂的状态和复杂的状态转换。目前我们的状态不是这样的,但将来可能会对你的特定情况感兴趣。让我们从定义一个用于管理有状态列表的reducer函数开始。

const listReducer = (state, action) => {
  switch (action.type) {
    case 'REMOVE_ITEM':
      return state.filter((item) => item.id !== action.id);
    default:
      throw new Error();
  }
};

从本质上讲,还原器函数接受一个状态和动作作为输入,并根据这些信息返回一个新的状态作为输出。此外,它还为每个动作类型设置了一个分支。在这种情况下,只有一个动作类型,因此只有一个分支来删除一个项目。从列表中删除项目的实际逻辑已经从我们的处理函数转移到了这个还原器中。

接下来,我们将用一个useReducer钩子来交换组件的useState钩子。这个钩子将状态和一个调度函数作为数组返回,我们可以通过数组重构方便地再次访问。然后在我们的处理函数中通过传递一个适当的动作来使用该调度函数。

const App = () => {
  const [list, dispatchList] = React.useReducer(
    listReducer,
    initialList
  );

  function handleRemove(id) {
    dispatchList({ type: 'REMOVE_ITEM', id });
  }

  return <List list={list} onRemove={handleRemove} />;
};

这就是使用useReducer而不是useState的原因。这两个状态钩子在React中都很有用,所以你应该根据你的需要决定你是需要useReducer还是useState钩子

最后但同样重要的是,你的状态可能并不总是只有列表。通常你会有一个更复杂的状态对象,而列表只是这个对象的一个属性。那么你将如何从这个对象的列表中删除一个项目呢?让我们先用React的useState Hook再看一下这个例子。假设在列表旁边有一个布尔标志,可以用条件渲染的方式显示或隐藏列表。

const App = () => {
  const [listData, setListData] = React.useState({
    list: initialList,
    isShowList: true,
  });

  function handleRemove(id) {
    // this doesn't work yet
    const newList = list.filter((item) => item.id !== id);

    // this doesn't work yet
    setList(newList);
  }

  if (!listData.isShowList) {
    return null;
  }

  return <List list={listData.list} onRemove={handleRemove} />;
};

我们以一个复杂的状态对象开始,该对象将列表作为其属性之一。无论我们想在哪里使用列表(或布尔标志),我们都需要先从对象中访问该属性。唯一缺少的是修复处理函数,因为它不能再只在列表上操作,而是需要考虑到对象。

const App = () => {
  const [listData, setListData] = React.useState({
    list: initialList,
    isShowList: true,
  });

  function handleRemove(id) {
    const newList = listData.list.filter((item) => item.id !== id);

    setListData({ ...listData, list: newList });
  }

  if (!listData.isShowList) {
    return null;
  }

  return <List list={listData.list} onRemove={handleRemove} />;
};

同样,我们从对象那里访问列表属性,根据传入的标识符过滤列表。然后,我们必须再次用复杂的状态对象更新状态。我们可以明确地设置新的列表和布尔标志--它没有改变--但是在这种情况下,我们使用JavaScript的传播操作符,将状态对象中的所有键/值对传播到新的状态对象中,同时用新的列表覆盖列表属性。让我们把同样的技术应用于带有reducer函数的例子。

const listReducer = (state, action) => {
  switch (action.type) {
    case 'REMOVE_ITEM':
      return {
        ...state,
        list: state.list.filter((item) => item.id !== action.id),
      };
    default:
      throw new Error();
  }
};

const App = () => {
  const [listData, dispatchListData] = React.useReducer(listReducer, {
    list: initialList,
    isShowList: true,
  });

  function handleRemove(id) {
    dispatchListData({ type: 'REMOVE_ITEM', id });
  }

  if (!listData.isShowList) {
    return null;
  }

  return <List list={listData.list} onRemove={handleRemove} />;
};

就这样了。与之前的版本类似,我们只是将所有的变化应用于以列表为属性的复杂状态对象,而不是直接使用列表作为状态。从列表中删除项目的过程保持不变。


所有显示的在React中从列表中删除一个项目的例子都可以在这个GitHub仓库中看到。如果你对如何在React中从列表中删除项目有任何反馈,请与我联系。