下拉式数据绑定在不同的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在未来对这些元素进行任何修改。
我们可以通过对被映射的项目的label 和value 属性进行解构,然后直接引用它们,从而使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将不高兴,并试图用setItems 和setLoading 函数来设置状态。这是因为这个状态已经不存在了。我们可以通过使用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钩子来控制状态中的选定下拉值。然后我们可以通过设置状态的初始值来设置下拉的初始选择值