вызов setState внутри пользовательского хука не обновляет состояние - PullRequest
2 голосов
/ 23 сентября 2019

Я пытаюсь настроить основной пользовательский хук, чтобы вернуть ориентацию устройства в React Native.У меня есть пример функционального кода рядом с этим, и я не могу понять, почему это не сработает.Я думаю, что это какая-то проблема с закрытием / определением области видимости, но ясно, что я не знаю.

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

Текущее поведение: вызов setOrientation не меняет значение orientation.Ожидаемое поведение: вызов setOrientation обновляет значение orientation.

Как только это произойдет, я думаю, что ловушка будет работать правильно, поскольку она возвращает null каждый раз, когда Dimensions обновляет.

[ОБНОВЛЕНИЕ: отредактировано для ясности]


import { Dimensions, } from 'react-native';

function useDeviceDimensions() {

  const [orientation, setOrientation] = useState(null);

  useEffect(() => {
    const setDimensions = (e) => {
      console.log('running', orientation); // null
      const {width, height} = e.window;
      setOrientation(width > height ? 'landscape' : 'portrait');
    };

    Dimensions.addEventListener('change', setDimensions);

    return () => Dimensions.removeEventListener('change', setDimensions);
  }, [])

  return orientation;
}

export default useDeviceDimensions;```

1 Ответ

2 голосов
/ 24 сентября 2019

Ваш пример работает ...

setOrientation фактически изменит значение состояния orientation, а useDeviceDimensions вернет измененное orientation.Проблема скорее в том, где вы поместили оператор console.log - и вы правы, это связано с замыканием (и его областью действия), переданным в useEffect.

useEffect вызывается только один раз с вашим заданным обратным вызовом.Этот обратный вызов является закрытием и может получить доступ к orientation из useState.Во время создания замыкания переменная orientation будет иметь значение null.Поскольку никогда не создается новое замыкание ([] зависимости в useEffect), оно всегда будет печатать значение null, когда включенный прослушиватель изменений запускается.

Однако setOrientation внутри setDimensions отправит и сообщит об изменении в React.Процесс выглядит следующим образом:

  1. Закрытие useEffect вызывается один раз при первом рендеринге.
  2. Через некоторое время запускается событие change.
  3. setDimensions внутри вашего замыкания всегда печатает одно и то же значение из внешней области видимости функции orientation (которая во время построения имеет значение null).
  4. setOrientation вызывается, React внутренне обновляет состояние и запускаетновый цикл рендеринга в родительском компоненте.
  5. useDeviceDimensions снова вызывается из родительского компонента, теперь он имеет и возвращает новое состояние orientation.
  6. Новое изменение инициируется,перейдите к 3 снова.

Точка 4 работает в замыкании, потому что React имеет внутренний список «ячеек памяти» для каждого компонента, где он может читать и обновлять недавнее состояние, поэтому обновления работаютчерез область закрытия (см. здесь для получения дополнительной информации).Сторона чтения orientation - это устаревшая переменная, если хотите, которая имеет ссылку на первое значение состояния, которое React хранит изначально.

... но лучше объявить useEffect безопасным способом

Ваш хук useEffect использует состояние, которое вы не упоминаете в его массиве зависимостей.Что React docs говорит по этому поводу:

Если вы используете эту оптимизацию, убедитесь, что массив включает в себя все значения из области действия компонента (например, реквизиты и состояние), которые изменяются со временем и используютсяэффект.В противном случае ваш код будет ссылаться на устаревшие значения из предыдущих рендеров. Ссылка

Так что, если вы также хотите использовать orientation в useEffect, вы можете объявить его как зависимость:

 useEffect(() => {
  ...
  }, [orientation]);

Посмотритев этом Codesandbox для примера, надеюсь, это немного прояснит ситуацию!

...