Katakanlah saya memiliki 3 input: rate, sendAmount, dan acceptAmount. Saya menempatkan 3 masukan itu pada parameter diffing useEffect. Aturannya adalah:
- Jika sendAmount berubah, saya menghitung
receiveAmount = sendAmount * rate
- Jika acceptAmount berubah, saya menghitung
sendAmount = receiveAmount / rate
- Jika tarif berubah, saya menghitung
receiveAmount = sendAmount * rate
kapansendAmount > 0
atau saya menghitungsendAmount = receiveAmount / rate
kapanreceiveAmount > 0
Berikut adalah codesandbox https://codesandbox.io/s/pkl6vn7x6j untuk mendemonstrasikan masalahnya.
Adakah cara untuk membandingkan oldValues
dan newValues
suka componentDidUpdate
daripada membuat 3 penangan untuk kasus ini?
Terima kasih
Inilah solusi terakhir saya dengan usePrevious
https://codesandbox.io/s/30n01w2r06
Dalam kasus ini, saya tidak dapat menggunakan beberapa useEffect
karena setiap perubahan mengarah ke panggilan jaringan yang sama. Itu sebabnya saya juga biasa changeCount
melacak perubahannya. Ini changeCount
juga membantu untuk melacak perubahan dari hanya lokal, jadi saya dapat mencegah panggilan jaringan yang tidak perlu karena perubahan dari server.
sumber
Jawaban:
Anda dapat menulis hook kustom untuk memberi Anda props sebelumnya menggunakan useRef
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
lalu gunakan di
useEffect
const Component = (props) => { const {receiveAmount, sendAmount } = props const prevAmount = usePrevious({receiveAmount, sendAmount}); useEffect(() => { if(prevAmount.receiveAmount !== receiveAmount) { // process here } if(prevAmount.sendAmount !== sendAmount) { // process here } }, [receiveAmount, sendAmount]) }
Namun lebih jelas dan mungkin lebih baik dan lebih jelas untuk membaca dan memahami jika Anda menggunakan dua
useEffect
secara terpisah untuk setiap id perubahan, Anda ingin memprosesnya secara terpisahsumber
useEffect
panggilan secara terpisah. Tidak sadar Anda bisa menggunakannya berkali-kali!useEffect
dependensi hilangprevAmount
Jika ada yang mencari versi TypeScript dari usePrevious:
Dalam
.tsx
modul:import { useEffect, useRef } from "react"; const usePrevious = <T extends unknown>(value: T): T | undefined => { const ref = useRef<T>(); useEffect(() => { ref.current = value; }); return ref.current; };
Atau dalam
.ts
modul:import { useEffect, useRef } from "react"; const usePrevious = <T>(value: T): T | undefined => { const ref = useRef<T>(); useEffect(() => { ref.current = value; }); return ref.current; };
sumber
const usePrevious = <T extends any>(...
membuatnya berfungsi dalam file TSX, ubah untuk membuat penerjemah melihat bahwa <T> bukan JSX dan merupakan batasan umum<T extends {}>
tidak akan berhasil. Tampaknya berfungsi untuk saya tetapi saya mencoba memahami kompleksitas menggunakannya seperti itu..ts
file. Jika Anda memperpanjang{}
Anda akan mendapatkan kesalahan jika Anda melewatkan menentukan T inusePrevious<T>
.Opsi 1 - jalankan useEffect saat nilai berubah
const Component = (props) => { useEffect(() => { console.log("val1 has changed"); }, [val1]); return <div>...</div>; };
Demo
Opsi 2 - hook useHasChanged
Membandingkan nilai saat ini dengan nilai sebelumnya adalah pola umum, dan membenarkan kaitan khusus miliknya yang menyembunyikan detail implementasi.
const Component = (props) => { const hasVal1Changed = useHasChanged(val1) useEffect(() => { if (hasVal1Changed ) { console.log("val1 has changed"); } }); return <div>...</div>; }; const useHasChanged= (val: any) => { const prevVal = usePrevious(val) return prevVal !== val } const usePrevious = (value) => { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
Demo
sumber
Saya baru saja menerbitkan react-delta yang memecahkan skenario semacam ini. Menurut saya,
useEffect
tanggung jawabnya terlalu banyak.Tanggung jawab
Object.is
Memutus Tanggung Jawab
react-delta
memecahuseEffect
tanggung jawab menjadi beberapa kait yang lebih kecil.Tanggung jawab # 1
usePrevious(value)
useLatest(value)
useDelta(value, options)
useDeltaArray(valueArray, options)
useDeltaObject(valueObject, options)
some(deltaArray)
every(deltaArray)
Tanggung jawab # 2
useConditionalEffect(callback, boolean)
Menurut pengalaman saya, pendekatan ini lebih fleksibel, bersih, dan ringkas daripada
useEffect
/useRef
solutions.sumber
Keluar dari jawaban yang diterima, solusi alternatif yang tidak memerlukan pengait khusus:
const Component = (props) => { const { receiveAmount, sendAmount } = props; const prevAmount = useRef({ receiveAmount, sendAmount }).current; useEffect(() => { if (prevAmount.receiveAmount !== receiveAmount) { // process here } if (prevAmount.sendAmount !== sendAmount) { // process here } return () => { prevAmount.receiveAmount = receiveAmount; prevAmount.sendAmount = sendAmount; }; }, [receiveAmount, sendAmount]); };
sumber
useRef
tidakuserRef
. Lupa menggunakan arusprevAmount.current
Karena status tidak digabungkan erat dengan instance komponen dalam komponen fungsional, status sebelumnya tidak dapat dicapai
useEffect
tanpa menyimpannya terlebih dahulu, misalnya denganuseRef
. Ini juga berarti bahwa pembaruan status mungkin salah diimplementasikan di tempat yang salah karena status sebelumnya tersedia di dalamsetState
fungsi updater.Ini adalah kasus penggunaan yang baik
useReducer
yang menyediakan penyimpanan mirip Redux dan memungkinkan untuk mengimplementasikan pola masing-masing. Pembaruan status dilakukan secara eksplisit, jadi tidak perlu mencari tahu properti negara mana yang diperbarui; ini sudah jelas dari tindakan yang dikirim.Berikut adalah contoh tampilannya:
function reducer({ sendAmount, receiveAmount, rate }, action) { switch (action.type) { case "sendAmount": sendAmount = action.payload; return { sendAmount, receiveAmount: sendAmount * rate, rate }; case "receiveAmount": receiveAmount = action.payload; return { sendAmount: receiveAmount / rate, receiveAmount, rate }; case "rate": rate = action.payload; return { sendAmount: receiveAmount ? receiveAmount / rate : sendAmount, receiveAmount: sendAmount ? sendAmount * rate : receiveAmount, rate }; default: throw new Error(); } } function handleChange(e) { const { name, value } = e.target; dispatch({ type: name, payload: value }); } ... const [state, dispatch] = useReducer(reducer, { rate: 2, sendAmount: 0, receiveAmount: 0 }); ...
sumber
sendAmount
, dll secara asinkron alih-alih menghitungnya, bukan? Ini banyak berubah. Ini mungkin untuk dilakukan denganuseReduce
tetapi mungkin rumit. Jika Anda pernah berurusan dengan Redux sebelumnya, Anda mungkin tahu bahwa operasi asinkron tidak langsung ke sana. github.com/reduxjs/redux-thunk adalah ekstensi populer untuk Redux yang memungkinkan tindakan asinkron. Berikut adalah demo yang menambahkan useReducer dengan pola yang sama (useThunkReducer), codesandbox.io/s/6z4r79ymwr . Perhatikan bahwa fungsi dispatched adalahasync
, Anda dapat melakukan permintaan di sana (berkomentar).Menggunakan Ref akan memperkenalkan jenis bug baru ke dalam aplikasi.
Mari kita lihat kasus ini menggunakan
usePrevious
komentar seseorang sebelumnya:Seperti yang bisa kita lihat di sini, kita tidak memperbarui internal
ref
karena kita sedang menggunakanuseEffect
sumber
state
... dan bukanprops
... ketika Anda peduli dengan props lama maka kesalahan akan terjadi.Untuk perbandingan prop yang sangat sederhana, Anda dapat menggunakan
useEffect
untuk memeriksa dengan mudah apakah sebuah prop telah diperbarui.const myComponent = ({ prop }) => { useEffect(() => { ---Do stuffhere---- }, [prop]) }
useEffect
hanya akan menjalankan kode Anda jika prop berubah.sumber
prop
perubahan berturut-turut Anda tidak akan dapat~ do stuff here ~
setHasPropChanged(false)
di akhir~ do stuff here ~
untuk "mengatur ulang" negara Anda. (Tapi ini akan mengatur ulang di rerender ekstra)Berikut hook khusus yang saya gunakan yang menurut saya lebih intuitif daripada menggunakan
usePrevious
.import { useRef, useEffect } from 'react' // useTransition :: Array a => (a -> Void, a) -> Void // |_______| | // | | // callback deps // // The useTransition hook is similar to the useEffect hook. It requires // a callback function and an array of dependencies. Unlike the useEffect // hook, the callback function is only called when the dependencies change. // Hence, it's not called when the component mounts because there is no change // in the dependencies. The callback function is supplied the previous array of // dependencies which it can use to perform transition-based effects. const useTransition = (callback, deps) => { const func = useRef(null) useEffect(() => { func.current = callback }, [callback]) const args = useRef(null) useEffect(() => { if (args.current !== null) func.current(...args.current) args.current = deps }, deps) }
Anda akan menggunakan
useTransition
sebagai berikut.useTransition((prevRate, prevSendAmount, prevReceiveAmount) => { if (sendAmount !== prevSendAmount || rate !== prevRate && sendAmount > 0) { const newReceiveAmount = sendAmount * rate // do something } else { const newSendAmount = receiveAmount / rate // do something } }, [rate, sendAmount, receiveAmount])
Semoga membantu.
sumber
Jika Anda lebih suka
useEffect
pendekatan pengganti:const usePreviousEffect = (fn, inputs = []) => { const previousInputsRef = useRef([...inputs]) useEffect(() => { fn(previousInputsRef.current) previousInputsRef.current = [...inputs] }, inputs) }
Dan gunakan seperti ini:
usePreviousEffect( ([prevReceiveAmount, prevSendAmount]) => { if (prevReceiveAmount !== receiveAmount) // side effect here if (prevSendAmount !== sendAmount) // side effect here }, [receiveAmount, sendAmount] )
Perhatikan bahwa pertama kali efek dijalankan, nilai sebelumnya yang diteruskan ke Anda
fn
akan sama dengan nilai masukan awal Anda. Ini hanya penting bagi Anda jika Anda ingin melakukan sesuatu ketika nilainya tidak berubah.sumber
Anda dapat menggunakan useImmer sebagai lawan useState dan mengakses status. Contoh: https://css-tricks.com/build-a-chat-app-using-react-hooks-in-100-lines-of-code/
sumber