如何在React中更新列表中的一个项目(附代码示例)

962 阅读7分钟

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

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

import React from 'react';

const list = [
  {
    id: 'a',
    task: 'Learn React',
    isComplete: false,
  },
  {
    id: 'b',
    task: 'Learn GraphQL',
    isComplete: true,
  },
];

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

export default App;

此外,根据列表项的isComplete 布尔标志,列表项要么被打穿,要么不被打穿。我们在这里使用内联风格进行快速原型设计。

import React from 'react';

const list = [
  {
    id: 'a',
    task: 'Learn React',
    isComplete: false,
  },
  {
    id: 'b',
    task: 'Learn GraphQL',
    isComplete: true,
  },
];

const App = () => {
  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span
            style={{
              textDecoration: item.isComplete
                ? 'line-through'
                : 'none',
            }}
          >
            {item.task}
          </span>
        </li>
      ))}
    </ul>
  );
};

export default App;

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

const initialList = [
  {
    id: 'a',
    task: 'Learn React',
    isComplete: false,
  },
  {
    id: 'b',
    task: 'Learn GraphQL',
    isComplete: true,
  },
];

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

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
         <span
            style={{
              textDecoration: item.isComplete
                ? 'line-through'
                : 'none',
            }}
          >
            {item.task}
          </span>
        </li>
      ))}
    </ul>
  );
};

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

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

  function handleToggleComplete() {
    // toggle item's complete flag
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span
            style={{
              textDecoration: item.isComplete
                ? 'line-through'
                : 'none',
            }}
          >
            {item.task}
          </span>
          <button type="button" onClick={handleToggleComplete}>
            {item.isComplete ? 'Undo' : 'Done'}
          </button>
        </li>
      ))}
    </ul>
  );
};

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

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

  function handleToggleComplete(id) {
    console.log(id);
    // toggle item's complete flag
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span
            style={{
              textDecoration: item.isComplete
                ? 'line-through'
                : 'none',
            }}
          >
            {item.task}
          </span>
          <button
            type="button"
            onClick={() => handleToggleComplete(item.id)}
          >
            {item.isComplete ? 'Undo' : 'Done'}
          </button>
        </li>
      ))}
    </ul>
  );
};

唯一缺少的是在点击按钮的时候更新列表中的特定项目。我们将通过使用map函数修改当前有状态的列表来实现这一点。

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

  function handleToggleComplete(id) {
    const newList = list.map((item) => {
      if (item.id === id) {
        const updatedItem = {
          ...item,
          isComplete: !item.isComplete,
        };

        return updatedItem;
      }

      return item;
    });

    setList(newList);
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>
          <span
            style={{
              textDecoration: item.isComplete
                ? 'line-through'
                : 'none',
            }}
          >
            {item.task}
          </span>
          <button
            type="button"
            onClick={() => handleToggleComplete(item.id)}
          >
            {item.isComplete ? 'Undo' : 'Done'}
          </button>
        </li>
      ))}
    </ul>
  );
};

我们不改变列表,而是将其保持为不可改变的数据结构,因此在映射的列表基础上创建一个新的列表,我们改变每一个符合条件的项目。如果一个项目符合条件,我们就用JavaScript的spread操作符为新项目使用所有的属性,并改变我们想要修改的属性。这是因为map函数并不修改列表,而只是返回一个新的列表。

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

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

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

  function handleToggleComplete(id) {
    const newList = list.map((item) => {
      if (item.id === id) {
        const updatedItem = {
          ...item,
          isComplete: !item.isComplete,
        };

        return updatedItem;
      }

      return item;
    });

    setList(newList);
  }

  return <List list={list} onToggleComplete={handleToggleComplete} />;
};

const List = ({ list, onToggleComplete }) => (
  <ul>
    {list.map((item) => (
      <li key={item.id}>
        <span
          style={{
            textDecoration: item.isComplete ? 'line-through' : 'none',
          }}
        >
          {item.task}
        </span>
        <button
          type="button"
          onClick={() => onToggleComplete(item.id)}
        >
          {item.isComplete ? 'Undo' : 'Done'}
        </button>
      </li>
    ))}
  </ul>
);

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

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

const listReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_ITEM':
      return state.map((item) => {
        if (item.id === action.id) {
          const updatedItem = {
            ...item,
            isComplete: !item.isComplete,
          };

          return updatedItem;
        }

        return item;
      });
    default:
      throw new Error();
  }
};

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

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

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

  function handleToggleComplete(id) {
    dispatchList({ type: 'UPDATE_ITEM', id });
  }

  return <List list={list} onToggleComplete={handleToggleComplete} />;
};

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

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

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

  function handleToggleComplete(id) {
    // this doesn't work yet
    const newList = list.map((item) => {
      if (item.id === id) {
        const updatedItem = {
          ...item,
          isComplete: !item.isComplete,
        };

        return updatedItem;
      }

      return item;
    });

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

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

  return (
    <List
      list={listData.list}
      onToggleComplete={handleToggleComplete}
    />
  );
};

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

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

  function handleToggleComplete(id) {
    const newList = listData.list.map((item) => {
      if (item.id === id) {
        const updatedItem = {
          ...item,
          isComplete: !item.isComplete,
        };

        return updatedItem;
      }

      return item;
    });

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

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

  return (
    <List
      list={listData.list}
      onToggleComplete={handleToggleComplete}
    />
  );
};

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

const listReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_ITEM': {
      const newList = state.list.map((item) => {
        if (item.id === action.id) {
          const updatedItem = {
            ...item,
            isComplete: !item.isComplete,
          };

          return updatedItem;
        }

        return item;
      });

      return { ...state, list: newList };
    }
    default:
      throw new Error();
  }
};

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

  function handleToggleComplete(id) {
    dispatchListData({ type: 'UPDATE_ITEM', id });
  }

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

  return (
    <List
      list={listData.list}
      onToggleComplete={handleToggleComplete}
    />
  );
};

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


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