Я бы хотел, чтобы пользователь мог сортировать список элементов задач. Когда пользователь выбирает элемент из выпадающего списка, он устанавливает, sortKey
который создаст новую версию setSortedTodos
, и, в свою очередь, вызывает useEffect
вызов и setSortedTodos
.
Приведенный ниже пример работает именно так, как я хочу, однако eslint побуждает меня добавить todos
к useEffect
массиву зависимостей, и если я это сделаю, это вызовет бесконечный цикл (как и следовало ожидать).
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]);
Живой пример:
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>
Я думаю, что должен быть лучший способ сделать это, чтобы Эслинт был счастлив.
sort
обратный вызов может быть простым:return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
что также имеет преимущество в сравнении локали, если среда имеет разумную информацию о локали. Если хотите, вы также можете применить к нему деструктуризацию: pastebin.com/7X4M1XTHeslint
выкидывает?[<>]
кнопка панели инструментов)? Фрагменты стека поддерживают React, включая JSX; вот как это сделать . Таким образом, люди могут проверить, что в их предложенных решениях нет проблемы бесконечного цикла ...todos
массив зависимостейuseEffect
, и вы можете понять, почему этого не следует делать . :-)Ответы:
Я бы сказал, что это означает, что идти по этому пути не идеально. Функция действительно зависит от
todos
. ЕслиsetTodos
вызывается где-то еще, функция обратного вызова должна быть пересчитана, иначе она работает с устаревшими данными.Почему вы храните отсортированный массив в любом случае? Вы можете использовать
useMemo
для сортировки значений при изменении ключа или массива:Тогда ссылка
sortedTodos
везде.Живой пример:
Показать фрагмент кода
Нет необходимости хранить отсортированные значения в состоянии, так как вы всегда можете получить / вычислить отсортированный массив из «базового» массива и ключа сортировки. Я бы сказал, что это также облегчает понимание вашего кода, поскольку он менее сложен.
источник
useMemo
. Просто побочный вопрос, почему бы не использовать.localCompare
в сортировке?Причина бесконечного цикла в том, что задачи не соответствуют предыдущей ссылке, и эффект будет запущен повторно.
Зачем использовать эффект для клик-действия в любом случае? Вы можете запустить его в такой функции:
и в выпадающем списке сделайте
onChange
.Обратите внимание на зависимость, кстати, ESLint прав! Ваши Todos, в случае, описанном выше, являются зависимостью и должны быть в списке. Подход к выбору предмета неправильный, а значит и ваша проблема.
источник
data.slice(0)
создает копию.setState
поскольку он не будет редактировать существующий объект и, следовательно, будет внутренне его клонировать. Неправильная формулировка в моем ответе, правда. Я отредактирую это.setState
не клонирует данные. Почему вы так думаете?setState
ничего не клонирует. Феликс и ОП верны, вам нужно скопировать массив перед сортировкой.state
.Что вам нужно сделать здесь, это использовать функциональную форму
setState
:Рабочие коды и коробка
Даже если вы копируете состояние, чтобы не изменять исходное, все равно не гарантируется, что вы получите его последнее значение из-за асинхронного состояния установки. Кроме того, большинство методов вернут мелкую копию, так что вы можете в любом случае поменять исходное состояние.
Использование функционала
setState
гарантирует, что вы получите последнее значение состояния и не измените первоначальное значение состояния.источник
.sort
изменяет массив на месте, так что вам все равно придется копировать его самостоятельно.