Значение перехватчиков React недоступно в функции прослушивателя событий

Я решил использовать перехватчики React для своего компонента, используя прослушиватель событий windom width и resize. Проблема в том, что я не могу получить доступ к текущему значению, которое мне нужно. Я получаю вложенное значение, которое было установлено при добавлении прослушивателя событий.

Передача функции в функцию установки значения не является для меня решением, потому что она заставляет отрисовывать и нарушает другие мои функции.

Я прилагаю минималистичный пример, чтобы показать основную проблему:

import React, { Component, useState, useEffect } from 'react';
import { render } from 'react-dom';

const App = () => {
  const [width, setWidth] = useState(0);

  const resize = () => {
    console.log("width:", width); // it's always 0 even after many updates
    setWidth(window.innerWidth);
  };

  useEffect(() => {
    resize();
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, []);

  return <div>{width}</div>;
}

render(<App />, document.getElementById('root'));

ЖИВУЮ ДЕМО ЗДЕСЬ

Пожалуйста, о помощи.


person Artimal    schedule 24.03.2019    source источник


Ответы (2)


Каждый рендер вы получаете новую копию resize функции. Каждая копия фиксирует текущее значение width. У вашего слушателя есть копия, которая была создана при первом рендеринге с помощью width = 0.

Чтобы решить эту проблему, у вас есть несколько вариантов:

  1. Обновлять слушатели при width изменениях

    useEffect(() => {
      resize();
      window.addEventListener("resize", resize);
      return () => window.removeEventListener("resize", resize);
    }, [width]);
    
  2. Используйте функциональные обновления, чтобы получить текущую ширину внутри слушателя.

    const resize = () => {
      setWidth(oldWidth => {
        console.log("width:", oldWidth);
        return window.innerWidth
      });
    };
    
  3. Сохраните ширину в изменяемой ссылке.

    const widthRef = useRef(width);
    
    const resize = () => {
      console.log("width:", widthRef.current);
      widthRef.current = window.innerWidth;
      setWidth(window.innerWidth);
    };
    
person UjinT34    schedule 24.03.2019
comment
Да, было бы. Второй useEffect не поможет. Вам нужно либо удалить-добавить слушателей, когда width изменяется, либо получить текущее значение width другим способом. Отсюда 3 возможных решения. Первый - самый простой. - person UjinT34; 24.03.2019
comment
Ваш код плохо оптимизирован и не помогает решить исходную проблему. Это просто скрывает проблему. Сначала useEffect устанавливает прослушиватель событий, который сбрасывает width на начальное значение. Это вызывает повторную визуализацию и второй useEffect. Второй useEffect устанавливает для width правильное значение. Таким образом, каждое событие изменения размера запускает два повторного рендеринга вместо одного, и один из вызовов resize использует неправильное значение. - person UjinT34; 24.03.2019
comment
Люблю 3-й способ. Спасибо. - person Artimal; 24.03.2019

Другой способ получить данные - установить переменную вне компонента

ДЕМО

import React, { Component, useState, useEffect } from 'react';
import { render } from 'react-dom';

//******* 
const appData = {
      width:0
}
//*******

const App = () => {
  const [width, setWidth] = useState(0);

  const resize = () => {
    //******* get width
    console.log('===>',appData.width);
    //*******

    console.log("width:", width); // it's always 0 even after many updates
    setWidth(window.innerWidth);
    //******* update width
    appData.width = window.innerWidth;
    //*******
  };

  useEffect(() => {
    resize();
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, []);

  return <div>{width}</div>;
}

render(<App />, document.getElementById('root'));
person Anton Dmitrievich    schedule 02.07.2019