Бесконечный цикл в использованииEffect - PullRequest
0 голосов
/ 30 октября 2018

Я поигрался с новой системой ловушек в React 16.7-alpha и застрял в бесконечном цикле в useEffect, когда обрабатываемое мной состояние - это объект или массив.

Сначала я использую useState и инициирую его с пустым объектом, подобным этому:

const [obj, setObj] = useState({});

Затем в useEffect я использую setObj, чтобы снова установить для него пустой объект. В качестве второго аргумента я передаю [obj], надеясь, что он не обновится, если content объекта не изменилось. Но он продолжает обновляться. Я думаю, потому что независимо от содержания, это всегда разные объекты, заставляющие React думать, что оно постоянно меняется?

useEffect(() => {
  setIngredients({});
}, [ingredients]);

То же самое верно и для массивов, но, как примитив, он не застрянет в цикле, как ожидалось.

Используя эти новые хуки, как мне обращаться с объектами и массивами при проверке того, изменился контент или нет?

Ответы [ 6 ]

0 голосов
/ 29 июня 2019

Ваш бесконечный цикл из-за округлости

useEffect(() => {
  setIngredients({});
}, [ingredients]);

setIngredients({}); изменит значение ingredients (будет возвращать новую ссылку каждый раз), которое будет работать setIngredients({}). Чтобы решить эту проблему, вы можете использовать любой подход:

  1. Передайте другой второй аргумент для использованияEffect
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);

setIngrediants будет работать при изменении timeToChangeIngrediants.

  1. Я не уверен, какой вариант использования оправдывает изменение ингредиентов после его изменения. Но если это так, вы передаете Object.values(ingrediants) в качестве второго аргумента для использования Effect.
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));
0 голосов
/ 27 апреля 2019

Я не уверен, что это сработает для вас, но вы можете попробовать добавить .length следующим образом:

useEffect(() => {
        // fetch from server and set as obj
}, [obj.length]);

В моем случае (я выбирал массив!) Он получал данные при монтировании, затем снова только при изменении и не зацикливался.

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

Как сказано в документации (https://reactjs.org/docs/hooks-effect.html), хук useEffect предназначен для использования , когда вы хотите, чтобы какой-то код выполнялся после каждого рендеринга . Из документов:

Запускается ли useEffect после каждого рендера? Да!

Если вы хотите настроить это, вы можете следовать инструкциям, которые появятся позже на этой же странице (https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects). По сути, метод useEffect принимает второй аргумент , что React будет проверьте, чтобы определить, должен ли эффект быть запущен снова или нет.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

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

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

Я тоже однажды столкнулся с той же проблемой и исправил ее, убедившись, что передал примитивные значения во втором аргументе [].

Если вы передадите объект, React сохранит только ссылку на объект и запустит эффект, когда ссылка изменится, что обычно происходит каждый раз (пока я не знаю, как).

Решение состоит в том, чтобы передать значения в объект. Вы можете попробовать,

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [Object.values(obj)]);

или

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [obj.keyA, obj.keyB]);
0 голосов
/ 31 октября 2018

Была такая же проблема. Я не знаю, почему они не упоминают об этом в документах. Просто хочу немного добавить к ответу Тобиаса Хаугена.

Для запуска каждого компонента / родительского перерисовки вам необходимо использовать:

  useEffect(() => {

    // don't know where it can be used :/
  })

Чтобы выполнить что-либо только один раз после монтирования компонента (будет обработано один раз), вам нужно использовать:

  useEffect(() => {

    // do anything only one time if you pass empty array []
    // keep in mind, that component will be rendered one time (with default values) before we get here
  }, [] )

Для запуска чего-либо один раз при монтировании компонента и в данных / данных2 изменить:

  const [data, setData] = useState(false)
  const [data2, setData2] = useState('default value for first render')
  useEffect(() => {

// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
  }, [data, data2] )

Как я использую его большую часть времени:

export default function Book({id}) { 
  const [book, bookSet] = useState(false) 

  useEffect(() => {
    loadBookFromServer()
  }, [id]) // every time id changed, new book will be loaded

  async function loadBookFromServer() {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }

  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}
0 голосов
/ 31 октября 2018

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

useEffect(() => {
  setIngredients({});
}, []);

Это было разъяснено мне в сообщении в блоге о перехватах React на https://www.robinwieruch.de/react-hooks/

...