对获取的数据进行排序
Sort fetched data
我需要对获取的数据添加排序 (ascending/descending)。
我从 API 端点获取所有数据。我映射该数组中的每个对象以显示在单独的组件卡中。但是一旦我选择从降序名称对数据进行排序,我就会快速更改组件,如果它们是从 Z 到 A 排序的,但它会立即转换回初始获取状态(从 A 到 Z)。
你能告诉我问题出在哪里吗?我不知道为什么,但感觉排序数组没有保存在我用来映射所有卡片的状态“数据”中。
import { useState } from 'react';
import { useEffect } from 'react';
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState('default');
useEffect(() => {
fetchData();
sortData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
setData(data);
};
function sortData() {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
return (
<div className='content'>
<header className='content__header'>
<h1>Header placeholder</h1>
</header>
<div className='wrapper'>
<div className='wrapper__sort-buttons'>
<select
defaultValue='default'
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value='default'>
Sort by
</option>
<option value='ascending'>Ascending</option>
<option value='descending'>Descending</option>
</select>
</div>
<ul className='wrapper__list'>
{data.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
这是我很快得到的:
然后它回到初始状态:
您使用 useEffect
的方式似乎导致您的组件在您每次更改排序类型时重新获取数据。由于多个地方在不同时间更新您的数据状态,这可能会导致竞争条件。
我会将排序逻辑移动到 useMemo
中,并且仅在初始加载时获取 useEffect
中的数据:
import { useEffect, useMemo, useState } from "react";
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from "uuid";
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState("default");
// Move sort logic here...
const sortedData = useMemo(() => {
let result = data;
if (sortType === "descending") {
result = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === "ascending") {
result = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
return result;
}, [data, sortType]);
// Only fetch data once on component mount...
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch(
"https://restcountries.com/v2/all?fields=name,region,area"
);
const data = await response.json();
setData(data);
};
return (
<div className="content">
<header className="content__header">
<h1>Header placeholder</h1>
</header>
<div className="wrapper">
<div className="wrapper__sort-buttons">
<select
defaultValue="default"
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value="default">
Sort by
</option>
<option value="ascending">Ascending</option>
<option value="descending">Descending</option>
</select>
</div>
<ul className="wrapper__list">
{/* Use sortedData here instead of data... */}
{sortedData.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
这是 Codesandbox 中的一个基本示例(我注释掉了您的 styles/card 组件):https://codesandbox.io/s/goofy-tdd-8lio9?file=/src/App.js
发生这种情况的原因可能是设置状态函数本质上是异步的,并且调用 setData 的顺序与您预期的不同。
因此,对于使用 sortType 'default' 的初始调用,您没有注意到任何更改,因为您按原样返回数据。但是一旦您将其更改为 'descending',sortData()
中的 setData()
比 fetchData()
中的 setData()
更早被调用,因此您的状态中已经有数据,您会看到数据发生变化在 UI 中停留片刻,然后调用函数 fetchData
中的 setData()
并将您的数据替换为您从 API 调用中获得的未排序或升序的数据订单。
可能的解决方案
不要在 fetchData
方法中设置状态,而只需在 sortData
方法中设置一次,因为无论如何你都需要它。
所以你的代码看起来像这样:
// we will call sortData inside fetchData so remove it from here
useEffect(() => {
fetchData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
// using API response data as an input to sortData function
sortData(data)
};
// using data from parameter instead of state
function sortData(data) {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
改进
您的 API 调用不依赖于 SORTING ORDER,因此您不需要一次又一次地调用 API,只需调用一次,然后对数据进行排序值从下拉菜单更改。
// call the API on initial load only
useEffect(() => {
fetchData();
}, []);
// and on sortType change you can handle it like this:
useEffect(() => {
sortData(data);
}, [sortType]);
// and using this approach you can use the exact same code for both functions implementation that you posted in your question above.
我需要对获取的数据添加排序 (ascending/descending)。
我从 API 端点获取所有数据。我映射该数组中的每个对象以显示在单独的组件卡中。但是一旦我选择从降序名称对数据进行排序,我就会快速更改组件,如果它们是从 Z 到 A 排序的,但它会立即转换回初始获取状态(从 A 到 Z)。
你能告诉我问题出在哪里吗?我不知道为什么,但感觉排序数组没有保存在我用来映射所有卡片的状态“数据”中。
import { useState } from 'react';
import { useEffect } from 'react';
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState('default');
useEffect(() => {
fetchData();
sortData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
setData(data);
};
function sortData() {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
return (
<div className='content'>
<header className='content__header'>
<h1>Header placeholder</h1>
</header>
<div className='wrapper'>
<div className='wrapper__sort-buttons'>
<select
defaultValue='default'
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value='default'>
Sort by
</option>
<option value='ascending'>Ascending</option>
<option value='descending'>Descending</option>
</select>
</div>
<ul className='wrapper__list'>
{data.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
这是我很快得到的:
然后它回到初始状态:
您使用 useEffect
的方式似乎导致您的组件在您每次更改排序类型时重新获取数据。由于多个地方在不同时间更新您的数据状态,这可能会导致竞争条件。
我会将排序逻辑移动到 useMemo
中,并且仅在初始加载时获取 useEffect
中的数据:
import { useEffect, useMemo, useState } from "react";
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from "uuid";
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState("default");
// Move sort logic here...
const sortedData = useMemo(() => {
let result = data;
if (sortType === "descending") {
result = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === "ascending") {
result = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
return result;
}, [data, sortType]);
// Only fetch data once on component mount...
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch(
"https://restcountries.com/v2/all?fields=name,region,area"
);
const data = await response.json();
setData(data);
};
return (
<div className="content">
<header className="content__header">
<h1>Header placeholder</h1>
</header>
<div className="wrapper">
<div className="wrapper__sort-buttons">
<select
defaultValue="default"
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value="default">
Sort by
</option>
<option value="ascending">Ascending</option>
<option value="descending">Descending</option>
</select>
</div>
<ul className="wrapper__list">
{/* Use sortedData here instead of data... */}
{sortedData.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
这是 Codesandbox 中的一个基本示例(我注释掉了您的 styles/card 组件):https://codesandbox.io/s/goofy-tdd-8lio9?file=/src/App.js
发生这种情况的原因可能是设置状态函数本质上是异步的,并且调用 setData 的顺序与您预期的不同。
因此,对于使用 sortType 'default' 的初始调用,您没有注意到任何更改,因为您按原样返回数据。但是一旦您将其更改为 'descending',sortData()
中的 setData()
比 fetchData()
中的 setData()
更早被调用,因此您的状态中已经有数据,您会看到数据发生变化在 UI 中停留片刻,然后调用函数 fetchData
中的 setData()
并将您的数据替换为您从 API 调用中获得的未排序或升序的数据订单。
可能的解决方案
不要在 fetchData
方法中设置状态,而只需在 sortData
方法中设置一次,因为无论如何你都需要它。
所以你的代码看起来像这样:
// we will call sortData inside fetchData so remove it from here
useEffect(() => {
fetchData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
// using API response data as an input to sortData function
sortData(data)
};
// using data from parameter instead of state
function sortData(data) {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
改进
您的 API 调用不依赖于 SORTING ORDER,因此您不需要一次又一次地调用 API,只需调用一次,然后对数据进行排序值从下拉菜单更改。
// call the API on initial load only
useEffect(() => {
fetchData();
}, []);
// and on sortType change you can handle it like this:
useEffect(() => {
sortData(data);
}, [sortType]);
// and using this approach you can use the exact same code for both functions implementation that you posted in your question above.