В этой теме я хотел бы использовать ловушки реакции для демонстрации каждой мысли.
Проблема заключается в том, что при использовании реагирования для создания приложения, если требуется асинхронная выборка, каким должен быть триггервызов извлечения?
Например, у нас есть компонент MemberList
, который включает <input>
для ввода ключевого слова фильтра, <button>
для фильтрации результатов списка по ключевому слову и <ul>
для отображения каждогоподробности об элементе:
<div>
<header>
<input value={keyword} onChange={syncKeywordToState} />
<button onClick={handleFilter}>Filter Members</button>
</header>
{
isLoading
? (
<ul>
{data.map(m => <li key={m.id}>{m.name}</li>)}
</ul>
)
: <Loading />
}
</div>
Функция listMember(keyword: string) => Promise<Member[]>
также предназначена для загрузки элементов по ключевому слову, когда выборка ожидает, мы должны отобразить анимацию загрузки вместо списка.
КакВ результате проблема меняется на «когда мы должны вызвать listMember
функцию и запустить анимацию загрузки».
Действие пользователя
Самая простая мысль - вызвать выборку при нажатии кнопки,это может быть:
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [isLoading, setLoading] = useState(true);
const handleFilter = useCallback(
async () => {
const data = await listMember(keyword);
setMembers(data);
setLoading(false);
},
[listMember, keyword]
);
return (
<div>
...
</div>
);
};
Это прекрасно работает, когда пользователь нажимает кнопку, однако при монтировании компонента он не будет извлекать начальный список членов, нам нужен эффект, соответствующий старомуэ-э componentDidMount
жизненный цикл, так как реагирование не предоставляет встроенный хук useDidMount
, мы должны сделать это хитро:
useEffect(
async () => {
setLoading(true);
const data = await listMember(keyword);
setMembers(data);
setLoading(false);
},
[true] // The didMount trick
);
Я не думаю, что это шаблон, который поощряет реагирование, useEffect
Hook строго говорит нам не делать различий между didMount
и didUpdate
, поэтому отдельного эффекта didMount
следует избегать с усилием.
Изменение состояния
Второе возможное решение состоит в том, чтобывызвать выборку, когда изменяется ее зависимое состояние:
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [filterKeyword, setFilterKeyword] = usetState('');
const handleFilter = () => setFilterKeyword(keyword); // Apply keyword to filter
useEffect(
async () => {
setLoading(true);
const data = await listMember(filterKeyword);
setMembers(data);
setLaoding(false);
},
[listMember, filterKeyword]
);
return (
<div>
...
</div>
);
}
Это смертельно просто:
- Когда пользователь нажимает кнопку, текущее значение ввода
keyword
предоставляется как filterKeyword
state. - Поскольку аргумент
listMember
изменяется, его следует вызывать с новым значением. - Таким образом, выборка запускается как эффект.
Я нахожу это решение концептуально ясным, однако некоторые проблемы все еще возникают:
- Это приводит к действию обновления состояния синхронизации, которое мы стараемся предотвратить в более ранних версиях реакции ( no-did-update-set-state )
- Если аргументnt of fetch является более сложным, например, массивы или объекты, равное обращение может потерпеть неудачу,
useMemo
здесь не очень помогает, это может привести к неожиданному дублированию вызовов fetch.
Отсутствие достоверных данных
Последнее понятие - «мы просто извлекаем данные, когда у нас их нет», изменение состояния - это триггер «изменения аргументов выборки», но не «отсутствие результата выборки», вВ кешируемой системе у нас могут быть ситуации, когда даже фильтр меняется, список результатов все еще жив:
const cacheReducer = (caches, {key, value}) => {
return {
...caches,
[key]: {
time: Date.now(),
value,
},
};
};
// Cache valid for 5 seconds
const isDataValid = ({time, value}) => !value || (time - Date.now() > 5000);
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [filterKeyword, setFilterKeyword] = usetState('');
const [caches, addCache] = useReducer({});
const data = caches[filterKeyword];
const handleFilter = () => setFilterKeyword(keyword); // Apply keyword to filter
useEffect(
async () => {
if (!isDataValid(data)) {
setLoading(true);
const data = await listMember(filterKeyword);
addCache({key: filterKeyword, value: data});
setLaoding(false);
}
},
[listMember, filterKeyword, data]
);
return (
<div>
...
</div>
);
}
В этом случае, когда пользователь нажимает кнопку, filterKeyword
- это изменения, но мы также проверяем кэшированныйданные, извлекайте их только тогда, когда они недействительны (либо не найдены, либо просрочены).
Это более точная модель для управления взаимосвязью аргументов, выборки и ее триггера.
Эти 3 решения не просто отличаются друг от друга своей реализацией, это больше концептуальные модели , мне интересно, какой из них выбрать в качестве лучшей практики.