Реагировать на ненужный рендер элемента

Sep 30 2020

Я только начал изучать React и собирал небольшое приложение, которое обращается к API котировок. У API есть конечная точка, которая возвращает случайную цитату. Когда приложение изначально загружается, оно обращается к API и показывает цитату, и есть кнопка, которую можно нажать, чтобы получить новую случайную цитату (новый вызов API).

У меня есть корневой компонент с именем App. Этот компонент имеет дочерний компонент QuoteWrap. Компонент QuoteWrap имеет два дочерних элемента: кнопку, которая используется для получения новой случайной цитаты, и компонент Quote, который показывает автора цитаты и саму цитату. Это код внутри компонента QuoteWrap:

export default function QuoteWrap() {
    const { quoteData, isLoading, fireNewCall } = useQuote();

    const handleClick = () => {
        fireNewCall();
    };

    return(
        <>
            <button onClick={handleClick}>Get random quote</button>
            
            { isLoading ? 
                <h2>Loading...</h2>
            :
                <Quote author={quoteData.author} quote={quoteData.quote} />
            }
        </>
    );
}

useQuote () - это настраиваемая ловушка, которая управляет вызовами API и возвращает 3 значения: 1 - данные, 2 - если вызов находится в процессе и 3 - функция для вызова API.

Очевидно, что каждый раз, когда нажимается кнопка, весь компонент QuoteWrap повторно визуализируется (при изменении quoteData и isLoading). Но на самом деле кнопку не нужно перерисовывать, так как она никогда не меняется.

Итак, я подумал: хорошо, я могу переместить кнопку в компонент приложения. Но тогда у меня нет доступа к функции fireNewCall в хуке useQuote.

Как я могу предотвратить повторную отрисовку кнопки? Это вообще важно в данном случае, или я слишком одержим повторным рендерингом React?

Спасибо!

Ответы

Benjamin Sep 30 2020 at 01:18

Ваш компонент будет повторно отрисовываться каждый раз, когда изменяется функция handleClick, то есть каждый раз при отрисовке QuoteWrap.

Решение - хук useCallback. useCallback будет возвращать ту же функцию для handleClick каждый раз при отрисовке QuoteWrap, если зависимости не изменились.

https://reactjs.org/docs/hooks-reference.html#usecallback

Вы бы использовали это так:

  const handleClick = useCallback(() => {
    fireNewCall();
  },[fireNewCall]);

fireNewCall - это зависимость, поэтому, пока useQuote возвращает стабильную функцию fireNewCall, ваша кнопка не будет повторно отображаться, поскольку свойство handleClick не изменилось.

VietDinh Sep 30 2020 at 01:15

Я думаю, вы можете быть слишком одержимы повторными рендерингами React. Кнопка должна быть отрисована повторно, потому что в некоторых случаях handleClickее следует изменить fireNewCall. Даже если, handleClickникогда не изменится. Не нужно думать о повторном рендеринге элемента.

BernardLeech Sep 30 2020 at 01:24

Примерно то, что сказали Бенджамин и Вьет - в исходном коде для handleClick при каждом рендеринге назначается новая функция. Вы можете использовать React.useCallbackисходную ссылку на функцию и обновлять ее только при изменении чего-либо в массиве зависимостей - в этом случае просто fireNewCallнужно перейти в массив зависимостей.

Но, как говорит Вьет, не слишком зацикливайтесь на этом. Использование React.useCallbackможет даже замедлить ваш код. Ознакомьтесь с сообщениями Kent C. Dodds When to useMemo и useCallback для получения дополнительной информации.