Vòng lặp vô hạn được sử dụng


107

Tôi đã chơi với hệ thống hook mới trong React 16.7-alpha và bị mắc kẹt trong một vòng lặp vô hạn trong useEffect khi trạng thái tôi đang xử lý là một đối tượng hoặc mảng.

Đầu tiên, tôi sử dụng useState và khởi tạo nó bằng một đối tượng trống như thế này:

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

Sau đó, trong useEffect, tôi sử dụng setObj để đặt lại nó thành một đối tượng trống. Đối số thứ hai, tôi chuyển [obj], hy vọng rằng nó sẽ không cập nhật nếu nội dung của đối tượng không thay đổi. Nhưng nó vẫn tiếp tục cập nhật. Tôi đoán vì bất kể nội dung, đây luôn là những đối tượng khác nhau khiến React nghĩ rằng nó liên tục thay đổi?

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

Điều này cũng đúng với các mảng, nhưng khi còn là nguyên thủy, nó sẽ không bị mắc kẹt trong một vòng lặp, như mong đợi.

Sử dụng các hook mới này, tôi nên xử lý các đối tượng và mảng như thế nào khi kiểm tra thời tiết nội dung có thay đổi hay không?


Tobias, trường hợp sử dụng nào yêu cầu thay đổi giá trị của thành phần, khi giá trị của nó đã thay đổi?
Ben Carp

@Tobias, bạn nên đọc câu trả lời của tôi. Tôi chắc chắn bạn sẽ chấp nhận nó là câu trả lời chính xác.
HalfWebDev

Câu trả lời:


125

Truyền một mảng trống làm đối số thứ hai để sử dụngEffect làm cho nó chỉ chạy trên mount và ngắt kết nối, do đó dừng bất kỳ vòng lặp vô hạn nào.

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

Điều này đã được tôi làm rõ trong bài đăng trên blog về React hooks tại https://www.robinwieruch.de/react-hooks/


1
Nó thực sự là một mảng trống không phải là một đối tượng trống mà bạn nên chuyển vào.
GifCo 21/02 '19

17
Điều này không giải quyết vấn đề, bạn cần phải vượt qua phụ thuộc được sử dụng bởi các móc
helado

1
Lưu ý rằng khi ngắt kết nối, hiệu ứng sẽ chạy một chức năng dọn dẹp nếu bạn đã chỉ định một chức năng. Hiệu ứng thực tế không chạy khi ngắt kết nối. reactjs.org/docs/hooks-effect.html#example-using-hooks-1
tony

2
Sử dụng mảng trống khi setIngrediets là một phụ thuộc là một phản vật chất như Dan Abramov đã nói. Làm useEffetct không điều trị như một componentDidMount () phương pháp
p7adams

1
vì những người ở trên không cung cấp liên kết hoặc tài nguyên về cách giải quyết vấn đề này một cách chính xác, tôi khuyên những người đi vịt sắp tới nên kiểm tra điều này, vì nó đã khắc phục sự cố vòng lặp vô hạn của tôi: stackoverflow.com/questions/56657907/…
C. Rib

72

Có cùng một vấn đề. Tôi không biết tại sao họ không đề cập đến điều này trong tài liệu. Chỉ muốn thêm một chút vào câu trả lời của Tobias Haugen.

Để chạy trong mọi thành phần / kết xuất gốc, bạn cần sử dụng:

  useEffect(() => {

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

Để chạy bất cứ thứ gì chỉ một lần sau khi component mount (sẽ được hiển thị một lần), bạn cần sử dụng:

  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
  }, [] )

Để chạy bất cứ thứ gì một lần trên component mount và trên data / data2 thay đổi:

  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] )

Cách tôi sử dụng nó hầu hết thời gian:

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

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

  // Remeber that it's not always safe to omit functions from the list of dependencies. Explained in comments.
  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>  
}


@endavid phụ thuộc vào prop bạn đang sử dụng là gì
ZiiMakc

1
tất nhiên, nếu bạn không sử dụng bất kỳ giá trị nào từ phạm vi thành phần, thì bạn có thể yên tâm bỏ qua. Nhưng từ quan điểm thiết kế / kiến ​​trúc thuần túy của vue, đây không phải là một cách thực hành tốt, vì nó yêu cầu bạn di chuyển toàn bộ chức năng của mình vào bên trong hiệu ứng nếu bạn cần sử dụng đạo cụ và bạn có thể kết thúc với một phương thức useEffect sẽ sử dụng một số lượng dòng mã quá lớn.
egdavid

16

Tôi cũng gặp phải vấn đề tương tự một lần và tôi đã sửa nó bằng cách đảm bảo rằng tôi chuyển các giá trị nguyên thủy vào đối số thứ hai [].

Nếu bạn truyền một đối tượng, React sẽ chỉ lưu trữ tham chiếu đến đối tượng và chạy hiệu ứng khi tham chiếu thay đổi, thường là mỗi lần thay đổi (bây giờ tôi không làm thế nào).

Giải pháp là truyền các giá trị trong đối tượng. Bạn co thể thử,

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

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

hoặc là

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

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

3
Một cách khác là tạo ra giá trị như vậy với useMemo, như vậy tài liệu tham khảo được giữ và giá trị phụ thuộc được đánh giá là giống nhau
helado

@helado bạn có thể sử dụng useMemo () cho các giá trị hoặc useCallback () cho các chức năng
Juanma Menendez

11

Như đã nói trong tài liệu ( https://reactjs.org/docs/hooks-effect.html ), useEffecthook được sử dụng khi bạn muốn một số mã được thực thi sau mỗi lần hiển thị . Từ các tài liệu:

UseEffect có chạy sau mỗi lần kết xuất không? Đúng!

Nếu bạn muốn tùy chỉnh điều này, bạn có thể làm theo hướng dẫn xuất hiện sau này trong cùng một trang ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ). Về cơ bản, useEffectphương pháp này chấp nhận một đối số thứ hai , React sẽ kiểm tra để xác định xem hiệu ứng có được kích hoạt lại hay không.

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

Bạn có thể chuyển bất kỳ đối tượng nào làm đối số thứ hai. Nếu đối tượng này không thay đổi, hiệu ứng của bạn sẽ chỉ được kích hoạt sau lần gắn kết đầu tiên . Nếu đối tượng thay đổi, hiệu ứng sẽ được kích hoạt trở lại.


11

Nếu bạn đang xây dựng một hook tùy chỉnh , đôi khi bạn có thể gây ra một vòng lặp vô hạn với mặc định như sau

function useMyBadHook(values = {}) {
    useEffect(()=> { 
           /* This runs every render, if values is undefined */
        },
        [values] 
    )
}

Cách khắc phục là sử dụng cùng một đối tượng thay vì tạo một đối tượng mới trên mỗi lần gọi hàm:

const defaultValues = {};
function useMyBadHook(values = defaultValues) {
    useEffect(()=> { 
           /* This runs on first call and when values change */
        },
        [values] 
    )
}

Nếu bạn gặp phải vấn đề này trong mã thành phần của mình , vòng lặp có thể được sửa nếu bạn sử dụng defaultProps thay vì giá trị mặc định ES6

function MyComponent({values}) {
  useEffect(()=> { 
       /* do stuff*/
    },[values] 
  )
  return null; /* stuff */
}

MyComponent.defaultProps = {
  values = {}
}

Cảm ơn! Điều này không rõ ràng đối với tôi và chính xác là vấn đề tôi đang gặp phải.
mắc kẹt vào

1
Bạn đã cứu ngày của tôi! Cảm ơn nhé người anh em.
Han Van Pham

6

Tôi không chắc liệu điều này có hiệu quả với bạn hay không nhưng bạn có thể thử thêm .length như thế này:

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

Trong trường hợp của tôi (tôi đang tìm nạp một mảng!), Nó đã tìm nạp dữ liệu trên mount, sau đó lại chỉ khi thay đổi và nó không đi vào vòng lặp.


1
Điều gì sẽ xảy ra nếu một mục được thay thế trong mảng? Trong trường hợp đó, độ dài của mảng sẽ giống nhau và hiệu ứng sẽ không chạy!
Aamir Khan

6

Nếu bạn bao gồm mảng trống ở phần cuối của useEffect :

useEffect(()=>{
        setText(text);
},[])

Nó sẽ chạy một lần.

Nếu bạn cũng bao gồm tham số trên mảng:

useEffect(()=>{
            setText(text);
},[text])

Nó sẽ chạy bất cứ khi nào tham số văn bản thay đổi.


tại sao nó sẽ chỉ chạy một lần nếu chúng ta đặt một mảng trống ở cuối hook?
Karen

Một mảng trống ở cuối useEffect là một triển khai có mục đích của các nhà phát triển để dừng các vòng lặp vô hạn trong các tình huống mà bạn có thể cần setState bên trong useEffect chẳng hạn. Nếu không, điều này sẽ dẫn đến useEffect -> cập nhật trạng thái -> useEffect -> vòng lặp vô hạn.
to240

4

Vòng lặp vô hạn của bạn là do tính tuần hoàn

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

setIngredients({});sẽ thay đổi giá trị của ingredients(sẽ trả về một tham chiếu mới mỗi lần), tham chiếu này sẽ chạy setIngredients({}). Để giải quyết vấn đề này, bạn có thể sử dụng một trong hai cách tiếp cận:

  1. Chuyển đối số thứ hai khác để sử dụng
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);

setIngrediantssẽ chạy khi timeToChangeIngrediantsđã thay đổi.

  1. Tôi không chắc trường hợp sử dụng nào biện minh cho các thành phần thay đổi khi nó đã được thay đổi. Nhưng nếu đúng như vậy, bạn chuyển Object.values(ingrediants)làm đối số thứ hai để useEffect.
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));

2

Nếu bạn sử dụng tính năng tối ưu hóa này, hãy đảm bảo rằng mảng bao gồm tất cả các giá trị từ phạm vi thành phần (chẳng hạn như đạo cụ và trạng thái) thay đổi theo thời gian và được sử dụng bởi hiệu ứng.

Tôi tin rằng họ đang cố gắng thể hiện khả năng một người có thể đang sử dụng dữ liệu cũ và nhận thức được điều này. Loại giá trị chúng ta gửi trong arrayđối số thứ hai không quan trọng miễn là chúng ta biết rằng nếu bất kỳ giá trị nào trong số đó thay đổi, nó sẽ thực thi hiệu ứng. Nếu chúng ta đang sử dụng ingredientsnhư một phần của phép tính trong hiệu ứng, chúng ta nên đưa nó vào array.

const [ingredients, setIngredients] = useState({});

// This will be an infinite loop, because by shallow comparison ingredients !== {} 
useEffect(() => {
  setIngredients({});
}, [ingredients]);

// If we need to update ingredients then we need to manually confirm 
// that it is actually different by deep comparison.

useEffect(() => {
  if (is(<similar_object>, ingredients) {
    return;
  }
  setIngredients(<similar_object>);
}, [ingredients]);


1

Cách tốt nhất là so sánh giá trị trước đó với giá trị hiện tại bằng cách sử dụng usePrevious ()_.isEqual () từ Lodash . Nhập isEqualuseRef . So sánh giá trị trước đó của bạn với giá trị hiện tại bên trong useEffect () . Nếu chúng giống nhau, không có gì khác cập nhật. usePrevious (value) là một hook tùy chỉnh tạo ra một ref với useRef () .

Dưới đây là đoạn mã của tôi. Tôi đang gặp vấn đề về vòng lặp vô hạn khi cập nhật dữ liệu bằng cách sử dụng móc firebase

import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
  useUserStatistics
} from '../../hooks/firebase-hooks'

export function TMDPage({ match, history, location }) {
  const usePrevious = value => {
    const ref = useRef()
    useEffect(() => {
      ref.current = value
    })
    return ref.current
  }
  const userId = match.params ? match.params.id : ''
  const teamId = location.state ? location.state.teamId : ''
  const [userStatistics] = useUserStatistics(userId, teamId)
  const previousUserStatistics = usePrevious(userStatistics)

  useEffect(() => {
      if (
        !isEqual(userStatistics, previousUserStatistics)
      ) {
        
        doSomething()
      }
     
  })


1
Tôi đoán mọi người không tán thành điều này vì bạn đã đề xuất sử dụng thư viện của bên thứ ba. Tuy nhiên, ý tưởng cơ bản là tốt.
jperl

1

Trong trường hợp bạn cần so sánh đối tượng và khi nó được cập nhật ở đây là một deepComparemóc để so sánh. Câu trả lời được chấp nhận chắc chắn không giải quyết được điều đó. Có một []mảng là phù hợp nếu bạn cần hiệu ứng chỉ chạy một lần khi được gắn kết.

Ngoài ra, các câu trả lời được bình chọn khác chỉ giải quyết việc kiểm tra các kiểu nguyên thủy bằng cách thực hiện obj.valuehoặc điều gì đó tương tự để lần đầu tiên đến mức mà nó không được lồng vào nhau. Đây có thể không phải là trường hợp tốt nhất cho các đối tượng lồng nhau sâu.

Vì vậy, đây là một trong những sẽ hoạt động trong mọi trường hợp.

import { DependencyList } from "react";

const useDeepCompare = (
    value: DependencyList | undefined
): DependencyList | undefined => {
    const ref = useRef<DependencyList | undefined>();
    if (!isEqual(ref.current, value)) {
        ref.current = value;
    }
    return ref.current;
};

Bạn có thể sử dụng tương tự trong useEffecthook

React.useEffect(() => {
        setState(state);
    }, useDeepCompare([state]));

0

Bạn cũng có thể hủy cấu trúc đối tượng trong mảng phụ thuộc, nghĩa là trạng thái sẽ chỉ cập nhật khi các phần nhất định của đối tượng được cập nhật.

Vì lợi ích của ví dụ này, giả sử các thành phần chứa cà rốt, chúng ta có thể chuyển nó cho phụ thuộc và chỉ khi cà rốt thay đổi, trạng thái sẽ cập nhật.

Sau đó, bạn có thể thực hiện điều này xa hơn và chỉ cập nhật số lượng cà rốt tại một số điểm nhất định, do đó kiểm soát khi nào trạng thái sẽ cập nhật và tránh vòng lặp vô hạn.

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

Một ví dụ về thời điểm một cái gì đó như thế này có thể được sử dụng là khi người dùng đăng nhập vào một trang web. Khi họ đăng nhập, chúng tôi có thể phá hủy đối tượng người dùng để trích xuất cookie và vai trò quyền của họ, đồng thời cập nhật trạng thái của ứng dụng cho phù hợp.


0

Trường hợp của tôi đặc biệt khi gặp phải một vòng lặp vô hạn, senario là như thế này:

Tôi đã có một Object, giả sử như objX đến từ các đạo cụ và tôi đã phá hủy nó trong các đạo cụ như:

const { something: { somePropery } } = ObjX

và tôi đã sử dụng someProperynhư một sự phụ thuộc vào useEffecttương tự của mình :


useEffect(() => {
  // ...
}, [somePropery])

và nó gây ra cho tôi một vòng lặp vô hạn, tôi đã cố gắng xử lý điều này bằng cách chuyển toàn bộ somethingdưới dạng phụ thuộc và nó hoạt động bình thường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.