Chciałbym, aby użytkownik mógł posortować listę rzeczy do zrobienia. Gdy użytkownicy wybiorą element z listy rozwijanej, ustawi, sortKey
która utworzy nową wersję setSortedTodos
, a następnie uruchomi useEffect
i zadzwonisetSortedTodos
.
Poniższy przykład działa dokładnie tak, jak chcę, jednak eslint zachęca mnie do dodania todos
do useEffect
tablicy zależności, a jeśli to zrobię, spowoduje nieskończoną pętlę (jak można się spodziewać).
const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
Przykład na żywo:
const {useState, useCallback, useEffect} = React;
const exampleToDos = [
{title: "This", priority: "1 - high", text: "Do this"},
{title: "That", priority: "1 - high", text: "Do that"},
{title: "The Other", priority: "2 - medium", text: "Do the other"},
];
function Example() {
const [todos, setTodos] = useState(exampleToDos);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
const sortByChange = useCallback(e => {
setSortKey(e.target.value);
});
return (
<div>
Sort by:
<select onChange={sortByChange}>
<option selected={sortKey === "title"} value="title">Title</option>
<option selected={sortKey === "priority"} value="priority">Priority</option>
</select>
{todos.map(({text, title, priority}) => (
<div className="todo">
<h4>{title} <span className="priority">{priority}</span></h4>
<div>{text}</div>
</div>
))}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
body {
font-family: sans-serif;
}
.todo {
border: 1px solid #eee;
padding: 2px;
margin: 4px;
}
.todo h4 {
margin: 2px;
}
.priority {
float: right;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Myślę, że musi istnieć lepszy sposób na zrobienie tego, co zapewni zadowolenie eslintowi.
sort
wywołanie zwrotne może być po prostu:return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
co ma tę zaletę, że porównuje ustawienia narodowe, jeśli środowisko ma rozsądne informacje o ustawieniach regionalnych. Jeśli chcesz, możesz też rzucić w to destrukcją: pastebin.com/7X4M1XTHeslint
generowany?[<>]
przycisk paska narzędzi)? Fragmenty stosu obsługują React, w tym JSX; oto jak to zrobić . W ten sposób ludzie mogą sprawdzić, czy ich proponowane rozwiązania nie mają problemu z nieskończoną pętlą ...todos
do tablicy zależnościuseEffect
, i możesz zobaczyć, dlaczego nie powinieneś. :-)Odpowiedzi:
Twierdzę, że oznacza to, że takie podejście nie jest idealne. Funkcja jest rzeczywiście zależna od
todos
. GdybysetTodos
zostanie wywołany w innym miejscu, funkcja wywołania zwrotnego musi zostać ponownie obliczona, w przeciwnym razie działa na nieaktualnych danych.Dlaczego mimo wszystko przechowujesz posortowaną tablicę w stanie? Możesz użyć
useMemo
do sortowania wartości, gdy zmienia się klucz lub tablica:Następnie odniesienie
sortedTodos
wszędzie.Przykład na żywo:
Pokaż fragment kodu
Nie ma potrzeby przechowywania posortowanych wartości w stanie, ponieważ zawsze można wyprowadzić / obliczyć posortowaną tablicę z tablicy „podstawowej” i klucza sortowania. Twierdzę, że ułatwia to również zrozumienie kodu, ponieważ jest mniej skomplikowany.
źródło
useMemo
. Tylko pytanie poboczne, dlaczego nie użyć.localCompare
tego rodzaju?Powodem nieskończonej pętli jest to, że todos nie pasują do poprzedniego odwołania i efekt zostanie uruchomiony ponownie.
Po co w ogóle korzystać z efektu kliknięcia? Możesz uruchomić go w takiej funkcji:
a po rozwijaniu wykonaj
onChange
.Przy okazji, zwróć uwagę na zależność, ESLint ma rację! Twoje zadania do wykonania, w przypadku opisanym powyżej, są zależne i powinny znajdować się na liście. Podejście do wyboru przedmiotu jest niewłaściwe, a zatem twój problem.
źródło
data.slice(0)
tworzy kopię.setState
ponieważ nie będzie edytować istniejącego obiektu, a zatem sklonuje go wewnętrznie. Nieprawidłowe sformułowanie w mojej odpowiedzi, prawda. Zmienię to.setState
nie klonuje danych. Dlaczego myślisz, że?setState
niczego nie klonuje . Felix i OP mają rację, musisz skopiować tablicę przed posortowaniem.state
.Musisz tutaj użyć funkcjonalnej formy
setState
:Działające kody i skrzynka
Nawet jeśli kopiujesz stan, aby nie mutować oryginalnego, nadal nie ma gwarancji, że uzyskasz jego najnowszą wartość, ponieważ ustawienie stanu jest asynchroniczne. Ponadto większość metod zwróci płytką kopię, więc i tak może dojść do mutacji stanu oryginalnego.
Korzystanie z funkcji
setState
zapewnia, że otrzymujesz najnowszą wartość stanu i nie mutujesz oryginalnej wartości stanu.źródło
.sort
zmienia tablicę w miejscu, więc nadal musisz ją skopiować samodzielnie.