Saya telah bermain-main dengan sistem hook baru di React 16.7-alpha dan terjebak dalam loop tak terbatas dalam useEffect ketika keadaan yang saya tangani adalah objek atau array.
Pertama, saya menggunakan useState dan memulainya dengan objek kosong seperti ini:
const [obj, setObj] = useState({});
Kemudian, dalam useEffect, saya menggunakan setObj untuk menyetelnya ke objek kosong lagi. Sebagai argumen kedua saya meneruskan [obj], berharap itu tidak akan diperbarui jika konten objek tidak berubah. Tapi itu terus diperbarui. Saya rasa karena apa pun isinya, objek ini selalu berbeda sehingga membuat React berpikir itu terus berubah?
useEffect(() => {
setIngredients({});
}, [ingredients]);
Hal yang sama berlaku dengan array, tetapi sebagai primitif, ia tidak akan terjebak dalam loop, seperti yang diharapkan.
Dengan menggunakan kait baru ini, bagaimana saya harus menangani objek dan larik saat memeriksa cuaca, konten telah berubah atau tidak?
sumber
Jawaban:
Meneruskan array kosong sebagai argumen kedua ke useEffect membuatnya hanya berjalan pada mount dan unmount, sehingga menghentikan loop tak terbatas.
useEffect(() => { setIngredients({}); }, []);
Ini diklarifikasi kepada saya dalam posting blog di React hooks di https://www.robinwieruch.de/react-hooks/
sumber
Punya masalah yang sama. Saya tidak tahu mengapa mereka tidak menyebutkan ini di dokumen. Hanya ingin menambahkan sedikit pada jawaban Tobias Haugen.
Untuk berjalan di setiap komponen / perenderan induk, Anda perlu menggunakan:
useEffect(() => { // don't know where it can be used :/ })
Untuk menjalankan apa pun hanya satu kali setelah pemasangan komponen (akan dirender sekali), Anda perlu menggunakan:
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 }, [] )
Untuk menjalankan apa pun satu kali pada pemasangan komponen dan pada perubahan data / data2 :
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] )
Bagaimana saya menggunakannya di sebagian besar waktu:
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> }
sumber
Saya mengalami masalah yang sama juga sekali dan saya memperbaikinya dengan memastikan saya meneruskan nilai primitif di argumen kedua
[]
.Jika Anda melewatkan suatu objek, React hanya akan menyimpan referensi ke objek tersebut dan menjalankan efeknya ketika referensi berubah, yang biasanya terjadi setiap saat (saya tidak tahu bagaimana caranya).
Solusinya adalah dengan meneruskan nilai pada objek. Anda dapat mencoba,
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [Object.values(obj)]);
atau
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [obj.keyA, obj.keyB]);
sumber
0
ke array ketergantungan juga?Seperti yang dikatakan dalam dokumentasi ( https://reactjs.org/docs/hooks-effect.html ),
useEffect
hook dimaksudkan untuk digunakan ketika Anda ingin beberapa kode dieksekusi setelah setiap render . Dari dokumen:Jika Anda ingin menyesuaikan ini, Anda dapat mengikuti petunjuk yang muncul nanti di halaman yang sama ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ). Pada dasarnya,
useEffect
metode menerima argumen kedua , yang akan diperiksa oleh React untuk menentukan apakah efeknya harus dipicu lagi atau tidak.useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if count changes
Anda dapat mengirimkan objek apa pun sebagai argumen kedua. Jika objek ini tetap tidak berubah, efek Anda hanya akan dipicu setelah pemasangan pertama . Jika objek berubah, efeknya akan dipicu lagi.
sumber
Jika Anda membuat pengait khusus , terkadang Anda dapat menyebabkan pengulangan tak terbatas dengan default sebagai berikut
function useMyBadHook(values = {}) { useEffect(()=> { /* This runs every render, if values is undefined */ }, [values] ) }
Cara mengatasinya adalah dengan menggunakan objek yang sama daripada membuat yang baru pada setiap pemanggilan fungsi:
const defaultValues = {}; function useMyBadHook(values = defaultValues) { useEffect(()=> { /* This runs on first call and when values change */ }, [values] ) }
Jika Anda mengalami ini di kode komponen Anda , loop mungkin diperbaiki jika Anda menggunakan defaultProps daripada nilai default ES6
function MyComponent({values}) { useEffect(()=> { /* do stuff*/ },[values] ) return null; /* stuff */ } MyComponent.defaultProps = { values = {} }
sumber
Saya tidak yakin apakah ini akan berhasil untuk Anda, tetapi Anda dapat mencoba menambahkan .length seperti ini:
useEffect(() => { // fetch from server and set as obj }, [obj.length]);
Dalam kasus saya (saya sedang mengambil sebuah array!) Itu mengambil data di mount, kemudian hanya pada perubahan dan tidak masuk ke dalam loop.
sumber
Jika Anda menyertakan array kosong di akhir useEffect :
useEffect(()=>{ setText(text); },[])
Ini akan berjalan sekali.
Jika Anda menyertakan juga parameter pada array:
useEffect(()=>{ setText(text); },[text])
Ini akan berjalan setiap kali parameter teks berubah.
sumber
Putaran tak terbatas Anda disebabkan oleh sirkularitas
useEffect(() => { setIngredients({}); }, [ingredients]);
setIngredients({});
akan mengubah nilaiingredients
(akan mengembalikan referensi baru setiap kali), yang akan dijalankansetIngredients({})
. Untuk mengatasi ini, Anda dapat menggunakan salah satu pendekatan:const timeToChangeIngrediants = ..... useEffect(() => { setIngredients({}); }, [timeToChangeIngrediants ]);
setIngrediants
akan berjalan jikatimeToChangeIngrediants
telah berubah.Object.values(ingrediants)
sebagai argumen kedua ke useEffect.useEffect(() => { setIngredients({}); }, Object.values(ingrediants));
sumber
Saya yakin mereka mencoba untuk mengungkapkan kemungkinan bahwa seseorang dapat menggunakan data lama, dan untuk mewaspadai hal ini. Tidak masalah jenis nilai yang kita kirimkan
array
untuk argumen kedua selama kita tahu bahwa jika salah satu dari nilai-nilai itu berubah, itu akan mengeksekusi efeknya. Jika kita menggunakaningredients
sebagai bagian dari penghitungan dalam efek, kita harus memasukkannya ke dalamarray
.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]);
sumber
Cara terbaik adalah membandingkan nilai sebelumnya dengan nilai saat ini dengan menggunakan usePrevious () dan _.isEqual () dari Lodash . Impor isEqual dan useRef . Bandingkan nilai Anda sebelumnya dengan nilai saat ini di dalam useEffect () . Jika mereka sama, jangan perbarui lagi. usePrevious (value) adalah hook kustom yang membuat ref dengan useRef () .
Di bawah ini adalah potongan kode saya. Saya menghadapi masalah loop tak terbatas dengan memperbarui data menggunakan firebase hook
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() } })
sumber
Jika Anda DO perlu membandingkan objek dan ketika diperbarui di sini adalah
deepCompare
kait untuk perbandingan. Jawaban yang diterima tentunya tidak membahas hal itu. Memiliki[]
array cocok jika Anda membutuhkan efek untuk berjalan hanya sekali saat dipasang.Selain itu, jawaban lain yang dipilih hanya menangani pemeriksaan untuk tipe primitif dengan melakukan
obj.value
atau sesuatu yang serupa dengan pertama kali sampai ke tingkat di mana ia tidak bersarang. Ini mungkin bukan kasus terbaik untuk objek bersarang dalam.Jadi inilah salah satu yang akan bekerja dalam semua kasus.
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; };
Anda bisa menggunakan yang sama di
useEffect
hookReact.useEffect(() => { setState(state); }, useDeepCompare([state]));
sumber
Anda juga dapat merusak objek dalam larik ketergantungan, yang berarti status hanya akan diperbarui ketika bagian tertentu dari objek diperbarui.
Demi contoh ini, katakanlah bahan-bahannya mengandung wortel, kita dapat meneruskannya ke dependensi, dan hanya jika wortel diubah, statusnya akan diperbarui.
Anda kemudian dapat mengambil ini lebih jauh dan hanya memperbarui jumlah wortel pada titik-titik tertentu, sehingga mengontrol kapan negara akan memperbarui dan menghindari putaran tak terbatas.
useEffect(() => { setIngredients({}); }, [ingredients.carrots]);
Contoh ketika sesuatu seperti ini dapat digunakan adalah ketika pengguna masuk ke situs web. Saat mereka masuk, kami dapat merusak objek pengguna untuk mengekstrak cookie dan peran izin mereka, dan memperbarui status aplikasi yang sesuai.
sumber
Kasus saya istimewa saat menghadapi loop tanpa batas, senario seperti ini:
Saya memiliki Object, katakanlah objX yang berasal dari props dan saya merusaknya dengan props seperti:
const { something: { somePropery } } = ObjX
dan saya menggunakan
somePropery
sebagai ketergantungan padauseEffect
suka saya :useEffect(() => { // ... }, [somePropery])
dan itu menyebabkan saya loop tak terbatas, saya mencoba menangani ini dengan melewatkan keseluruhan
something
sebagai ketergantungan dan itu bekerja dengan baik.sumber