Что должно вызвать срабатывание в реакции?Действия пользователя, изменение состояния или отсутствие достоверных данных? - PullRequest
0 голосов
/ 18 февраля 2019

В этой теме я хотел бы использовать ловушки реакции для демонстрации каждой мысли.

Проблема заключается в том, что при использовании реагирования для создания приложения, если требуется асинхронная выборка, каким должен быть триггервызов извлечения?

Например, у нас есть компонент 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
);

Я не думаю, что это шаблон, который поощряет реагирование, useEffectHook строго говорит нам не делать различий между 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>
    );
}

Это смертельно просто:

  1. Когда пользователь нажимает кнопку, текущее значение ввода keyword предоставляется как filterKeyword state.
  2. Поскольку аргумент listMember изменяется, его следует вызывать с новым значением.
  3. Таким образом, выборка запускается как эффект.

Я нахожу это решение концептуально ясным, однако некоторые проблемы все еще возникают:

  1. Это приводит к действию обновления состояния синхронизации, которое мы стараемся предотвратить в более ранних версиях реакции ( no-did-update-set-state )
  2. Если аргумент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 решения не просто отличаются друг от друга своей реализацией, это больше концептуальные модели , мне интересно, какой из них выбрать в качестве лучшей практики.

1 Ответ

0 голосов
/ 18 февраля 2019

После вашего комментария я понял кое-что еще.В любом случае, я не согласен с тем, что вы не можете повторить поведение componentDidMount, потому что даже документы говорят, что

Если вы хотите запустить эффект и очистить его толькоодин раз (при монтировании и демонтировании) вы можете передать пустой массив ([]) в качестве второго аргумента


МОЙ ОРИГИНАЛЬНЫЙ ОТВЕТ НИЖЕ

Прежде всего, начиная сВаш первый фрагмент Hooks (следующий)

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

Это не на 100% правильно ... это правильно, если вы используете useCallback, но если вы используете useEffect, вы получаете то же поведение componentDidMount.

Взятьпосмотрите на мои коды и поле , код следующий:

import React, { useState, useCallback, useEffect } from "react";

const MemberList = () => {
  const [members, setMembers] = useState([]);
  const [keyword, setKeyword] = useState("");
  const [isLoading, setLoading] = useState(true);
  const handleFilter = useEffect(
    async () => {
      console.log("useCallback");
    },
    [/*listMember, */ keyword]
  );

  return <div>...</div>;
};

export default MemberList;

Я немного его изменил, потому что:

  • У меня нет listMember функция, поэтому я заменил ее на console.log
  • useCallback не является полезным для цели, которую вы хотите достичь, выНе функция listMember, которая вызывается при изменении фильтра, но useCallback просто возвращает запомненную функцию ... она не вызывает ее!(см. документы )
  • useEffect, вместо этого, достигает вашей цели, он вызывает переданную функцию каждый раз, когда меняется фильтр keyword.И он всегда вызывается после первого монтирования (если вы посмотрите на консоль, вы найдете мой журнал)
  • Я прокомментировал параметр listMember, который вы передали, потому что, когда вы пытаетесь избежать этого побочного эффектасрабатывание идентификатора ... вы должны использовать значения, а не ссылки!Даже если функция всегда делает то же самое, она может меняться (особенно в React) при каждом рендеринге ... и поэтому побочный эффект повторно запускается, потому что строгое равенство === говорит о том, что оно изменилось (в случае функций даже== возвращает это, но я хотел объяснить вам разницу) ... так, например, не используйте массивы, а сопоставьте их значения, не используйте объекты, но используйте их идентификаторы ... вы можете использовать их.очевидно, но вам нужно знать, что вы делаете.Достаточно пропустить keyword.

Для остальной части вашего вопроса мне не очень понятно, о чем вы спрашиваете, потому что вы сообщили несколько разных примеров ... с различным поведением ... иты спрашиваешь, что лучше ...???

...