用React钩子进行下拉数据绑定的指南

745 阅读6分钟

Dropdown data binding with React hooks 下拉式数据绑定在不同的UI技术中总是很有趣。我们经常想从一个网络API中向下拉菜单提供一个动态数据值的列表。通常情况下,我们希望在下拉项被加载时阻止用户与之互动。我们也可能希望在它们加载后选择一个特定的下拉项目。那么,我们如何用React钩子做这些事情呢?让我们来看看。

创建下拉组件

我们的下拉菜单将由《星球大战》中的角色名称组成。让我们开始创建React组件:

function CharacterDropDown() {
  return (
    <select>
      <option value="Luke Skywalker">
        Luke Skywalker
      </option>
      <option value="C-3PO">C-3PO</option>
      <option value="R2-D2">R2-D2</option>
    </select>
  );
}

这是一个包含3个硬编码字符的功能性React组件。尽管在我们的例子中,项目标签和项目值是一样的,但我们已经明确地指定了它们两个,因为在其他场景中,它们往往是不同的。

一个不错的、简单的开始,但仍有很多工作要做!

使用状态来呈现下拉项目

目前,我们的下拉菜单包含硬编码的项目。如果项目需要是动态的,并从外部来源(如Web API)加载呢?好吧,我们需要做的第一件事就是把项目放在状态中,使其成为动态的。然后我们可以让下拉菜单在渲染其项目时参考这个状态:

function CharacterDropDown() {
  const [items] = React.useState([
    {
      label: "Luke Skywalker",
      value: "Luke Skywalker"
    },
    { label: "C-3PO", value: "C-3PO" },
    { label: "R2-D2", value: "R2-D2" }
  ]);
  return (
    <select>
      {items.map(item => (
        <option
          key={item.value}
          value={item.value}
        >
          {item.label}
        </option>
      ))}
    </select>
  );
}

我们使用useState 钩子来创建一些带有我们的字符的状态。useState 的参数是该状态的初始值。useState 钩子在一个数组的第一个元素中返回状态的当前值--我们已经把它解构为一个items 变量。

所以,我们有一个items 变量,这是一个包含我们的星球大战角色的数组。在return 语句中,我们使用items 数组map 函数来迭代这些角色,并呈现相关的option 元素。注意,我们在option 元素上设置了key 属性,以帮助React在未来对这些元素进行任何修改。

我们可以通过对被映射的项目的labelvalue 属性进行解构,然后直接引用它们,从而使JSX更简洁:

<select>
  {items.map(({ label, value }) => (
    <option key={value} value={value}>
      {label}
    </option>
  ))}
</select>

从网络API中获取数据

我们要用梦幻般的星球大战API中的人物来填充一个下拉菜单。因此,我们需要将来自https://swapi.co/api/people 的数据放入其中,而不是将3个硬编码的字符放入状态中。我们可以用useEffect 钩子来做这件事

function CharacterDropDown() {
  const [items, setItems] = React.useState([]);

  React.useEffect(() => {
    async function getCharacters() {
      const response = await fetch("https://swapi.co/api/people");
      const body = await response.json();
      setItems(body.results.map(({ name }) => ({ label: name, value: name })));
    }
    getCharacters();
  }, []);

  return (
    ...
  );
}

让我们检查一下useEffect 钩子:

  • 它的第一个参数是一个当副作用运行时要执行的函数
  • 第二个参数决定了副作用的运行时间。在我们的例子中,这只是在组件第一次渲染之后,因为我们指定了一个空数组。
  • 我们在useEffect 钩子中的副作用函数需要是异步的,因为网络API调用,但这在useEffect 中是不允许的。这就是为什么我们有一个异步的嵌套getCharacters 函数被调用。
  • getCharacters 函数中,我们使用本地的fetch函数来进行网络API请求。然后我们将响应体映射到我们的items 状态所期望的数据结构中。

让我们再次把注意力转向useState 钩子:

  • 注意我们现在把items 状态默认为一个空数组。
  • 也注意到我们对useState 钩子的第二个参数进行了结构化。这是一个叫做setItems 的函数,我们可以用它来为items 状态设置一个新值。
  • 在我们从Web API中适当地映射数据后,我们使用setItems 函数在getCharacters 函数中设置items 状态。这个对setItems 的调用将导致我们的组件重新渲染并显示下拉项目。

在项目加载时停止用户与下拉菜单的互动

我们可能想在加载数据的时候阻止用户与下拉菜单进行交互。我们可以通过在进行Web API请求时禁用下拉菜单来做到这一点:

function CharacterDropDown() {
  const [loading, setLoading] = React.useState(true);
  const [items, setItems] = React.useState([
    { label: "Loading ...", value: "" }
  ]);
  React.useEffect(() => {
    async function getCharacters() {
      ...
      setItems(body.results.map(({ name }) => ({ label: name, value: name })));
      setLoading(false);
    }
    getCharacters();
  }, []);
  return (
    <select disabled={loading}>
      ...
    </select>
  );
}

我们添加了一个新的状态,叫做loading ,以指示项目是否正在被加载。我们将其初始化为true ,并在项目从Web API获取并设置为items 状态后将其设置为false

然后我们在JSX中的select 元素disabled 属性上引用loading 状态。这将使select 元素在其项目被加载时失效。

请注意,我们已经将items 状态默认为一个数组,其中有一个包含 "加载中... "标签的单项。这是一个很好的提示,让用户清楚地知道正在发生什么。

当组件被卸载时中止加载项目

如果用户浏览到一个不同的页面,并且CharacterDropDown ,而项目仍在被取走,会发生什么?当响应返回时,React将不高兴,并试图用setItemssetLoading 函数来设置状态。这是因为这个状态已经不存在了。我们可以通过使用unmounted 标志来解决这个问题:

React.useEffect(() => {
  let unmounted = false;  async function getCharacters() {
    const response = await fetch(
      "https://swapi.co/api/people"
    );
    const body = await response.json();
    if (!unmounted) {      setItems(
        body.results.map(({ name }) => ({
          label: name,
          value: name
        }))
      );
      setLoading(false);
    }  }
  getCharacters();
  return () => {    unmounted = true;  };}, []);

因此,我们将unmounted 初始化为false ,并在设置状态之前检查它是否仍然为假。

useEffect 钩子中的副作用函数可以返回另一个函数,在组件被卸载时执行。因此,我们返回一个函数,将我们的unmounted 设置为true

我们的下拉菜单现在已经很好很强大了。

用状态控制下拉值

当建立一个表单时,一个常见的模式是用状态来控制字段的值,所以,现在让我们用状态来控制下拉值:

function CharacterDropDown() {
  const [loading, setLoading] = React.useState(true);
  const [items, setItems] = React.useState(...);
  const [value, setValue] = React.useState();  React.useEffect(...);
  return (
    <select
      disabled={loading}
      value={value}      onChange={e => setValue(e.currentTarget.value)}    >
      ...
    </select>
  );
}

我们添加了一个名为value 的新状态,并将其绑定到JSX中select 元素上的value 道具。我们还在change 事件监听器中用onChange 的道具更新这个状态。

设置初始值

我们可能想选择一个下拉菜单的初始值。现在值是由状态控制的,这只是一个简单的设置状态默认值的问题。

const [value, setValue] = React.useState(
  "R2-D2");

🏃播放代码

结束

  • 我们使用useEffect 钩子来加载来自网络API的下拉项目。副作用函数需要包含一个嵌套函数来完成对网络API的调用。
  • 我们使用useState 钩子来加载标志,该标志在下拉项目加载时被设置,可以用来在此过程中禁用下拉功能。
  • 我们使用useState 钩子来保持下拉项目的状态。这是在从网络API获取数据后设置的。
  • 我们还使用useState 钩子来控制状态中的选定下拉值。然后我们可以通过设置状态的初始值来设置下拉的初始选择值