在React中向列表中添加一个项目

745 阅读7分钟

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

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

import React from 'react';

const list = [
  {
    id: 'a',
    name: 'Robin',
  },
  {
    id: 'b',
    name: 'Dennis',
  },
];

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

export default App;

到目前为止,这个列表只是一个JavaScript变量,还不是有状态的。要修改它,例如添加一个项目,我们需要通过使用React的状态和useState Hook使列表有状态。

const initialList = [
  {
    id: 'a',
    name: 'Robin',
  },
  {
    id: 'b',
    name: 'Dennis',
  },
];

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

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

现在我们有了一个有状态的列表,我们可以修改它了。让我们添加一个输入字段和一个按钮,每个都有一个处理函数,它们都处理更新输入字段的状态并最终向列表中添加一个项目。

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

  function handleChange() {
    // track input field's state
  }

  function handleAdd() {
    // add item
  }

  return (
    <div>
      <div>
        <input type="text" onChange={handleChange} />
        <button type="button" onClick={handleAdd}>
          Add
        </button>
      </div>

      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

在我们添加一个项目之前,我们需要跟踪输入字段的状态,因为如果没有输入字段的值,我们就没有任何文本来给我们想添加到列表中的项目。因此,让我们先给它添加一些状态管理。

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    // add item
  }

  return (
    <div>
      <div>
        <input type="text" value={name} onChange={handleChange} />
        <button type="button" onClick={handleAdd}>
          Add
        </button>
      </div>

      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

我们已经把输入框变成了受控元素,因为它现在从React的状态中接收它的内部值。接下来,只要有人点击按钮,我们就可以把输入字段中的名字作为一个新项目添加到列表中。

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    const newList = list.concat({ name });

    setList(newList);
  }

  return (
    <div>
      <div>
        <input type="text" value={name} onChange={handleChange} />
        <button type="button" onClick={handleAdd}>
          Add
        </button>
      </div>

      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

我们在这里使用对象属性速记初始化,因为变量name 等于对象的属性name 。然后我们使用状态更新器函数来传递新的列表。

添加一个项目是可行的,但有一些缺陷。缺少两件事。首先,我们应该清理一下输入字段。其次,我们也需要为项目定义一个标识符id 属性,否则我们将不再有一个稳定的JSX映射的列表项目的关键属性。我在这里使用uuid节点包,你可以用npm install uuid

import React from 'react';
import { v4 as uuidv4 } from 'uuid';

...

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    const newList = list.concat({ name, id: uuidv4() });

    setList(newList);

    setName('');
  }

  return (
    <div>
      <div>
        <input type="text" value={name} onChange={handleChange} />
        <button type="button" onClick={handleAdd}>
          Add
        </button>
      </div>

      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

就这样了。我们没有改变列表,而是将其作为一个不可改变的数据结构,因此在旧列表和新项目的基础上创建一个新列表。这是因为concat函数并不修改列表,而只是返回一个新的列表。

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

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

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    const newList = list.concat({ name, id: uuidv4() });

    setList(newList);

    setName('');
  }

  return (
    <div>
      <AddItem
        name={name}
        onChange={handleChange}
        onAdd={handleAdd}
      />

      <List list={list} />
    </div>
  );
};

const AddItem = ({ name, onChange, onAdd }) => (
  <div>
    <input type="text" value={name} onChange={onChange} />
    <button type="button" onClick={onAdd}>
      Add
    </button>
  </div>
);

const List = ({ list }) => (
  <ul>
    {list.map((item) => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
);

这就是了。你可以从一个子组件中添加一个项目,而列表则作为状态在父组件的某处进一步管理。现在,我们将继续用React的useReducer Hook替换React的useState。在React中,还原器钩子可以用于复杂的状态转换。目前我们的状态不是这样的,但在你未来的某个项目中可能会有兴趣。让我们从定义一个用于管理有状态列表的reducer函数开始。

const listReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      return state.concat({ name: action.name, id: action.id });
    default:
      throw new Error();
  }
};

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

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

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    dispatchList({ type: 'ADD_ITEM', name, id: uuidv4() });

    setName('');
  }

  return (
    <div>
      <AddItem
        name={name}
        onChange={handleChange}
        onAdd={handleAdd}
      />

      <List list={list} />
    </div>
  );
};

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

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

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    // this doesn't work yet
    const newList = list.concat({
      name,
      id: uuidv4(),
    });

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

    setName('');
  }

  return (
    <div>
      <AddItem
        name={name}
        onChange={handleChange}
        onAdd={handleAdd}
      />

      {listData.isShowList && <List list={listData.list} />}
    </div>
  );
};

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

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    const newList = listData.list.concat({
      name,
      id: uuidv4(),
    });

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

    setName('');
  }

  return (
    <div>
      <AddItem
        name={name}
        onChange={handleChange}
        onAdd={handleAdd}
      />

      {listData.isShowList && <List list={listData.list} />}
    </div>
  );
};

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

const listReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      return {
        ...state,
        list: state.list.concat({ name: action.name, id: action.id }),
      };
    default:
      throw new Error();
  }
};

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

  function handleChange(event) {
    setName(event.target.value);
  }

  function handleAdd() {
    dispatchListData({ type: 'ADD_ITEM', name, id: uuidv4() });

    setName('');
  }

  return (
    <div>
      <AddItem
        name={name}
        onChange={handleChange}
        onAdd={handleAdd}
      />

      <List list={listData.list} />
    </div>
  );
};

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


所有关于在React中向列表添加项目的例子都可以在这个GitHub仓库中看到。如果你对如何在React中向列表中添加项目有任何反馈,请与我联系。