React kancalarında `setState` geri araması nasıl kullanılır


119

React hooks useStatebileşen durumunu ayarlamak için tanıttı . Ancak geri aramayı değiştirmek için aşağıdaki kod gibi kancaları nasıl kullanabilirim:

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

Durum güncellendikten sonra bir şeyler yapmak istiyorum.

useEffectFazladan şeyler yapmak için kullanabileceğimi biliyorum , ancak bir bit kodu gerektiren önceki durum değerini kontrol etmem gerekiyor. useStateKanca ile kullanılabilecek basit bir çözüm arıyorum .


1
sınıf bileşeninde, eşzamansız kullandım ve setState'te bir geri arama eklemek için yaptığınızla aynı sonucu elde etmeyi bekliyorum. Maalesef, kancadan çalışmıyor. Eşzamansız ekleyip beklesem bile, tepki, durumun güncellenmesini beklemeyecek. Belki bunu yapmanın tek yolu efekti kullanmaktır.
MING WU

2
@Zhao, henüz doğru cevabı işaretlemedin. Lütfen birkaç saniye
ayırabilir

Yanıtlar:


128

Bunu useEffectbaşarmak için kanca kullanmanız gerekir .

const [counter, setCounter] = useState(0);

const doSomething = () => {
  setCounter(123);
}

useEffect(() => {
   console.log('Do something after counter has changed', counter);
}, [counter]);

48
Bu console.log, ilk işlemede ve zaman counterdeğişikliklerinde tetiklenecektir. Ya yalnızca durum güncellendikten sonra bir şeyler yapmak istiyorsanız, ancak başlangıç ​​değeri ayarlandığında ilk işlemede istemiyorsanız? Sanırım değeri kontrol edebilir useEffectve bir şey yapmak isteyip istemediğinize karar verebilirsiniz. Bu, en iyi uygulama olarak kabul edilir mi?
Darryl Young

2
useEffectİlk işlemede çalışmayı önlemek için , ilk işlemede çalışmayan özel bir useEffectkanca oluşturabilirsiniz. Böyle bir kanca oluşturmak için şu soruyu kontrol edebilirsiniz: stackoverflow.com/questions/53253940/…

11
Ve aynı durum değerini değiştirecek olan farklı setState çağrılarında farklı geri aramaları çağırmak istediğimde durum ne olacak? Cevabınız yanlış ve yeni başlayanların kafasını karıştırmamak için doğru olarak işaretlenmemelidir. Gerçek şu ki, setState, net bir çözme yöntemi olmayan sınıflardan kancalara geçiş yaparken en zor sorunlardan birini geri çağırıyor. Bazen gerçekten değere bağlı bir etkiye sahip olacaksınız ve bazen bu, Referanslar'da bir tür bayrak kaydetmek gibi bazı hilekâr yöntemler gerektirir.
Dmitry Lobov

Görünüşe göre, bugün setCounter'ı (değer, geri arama) çağırabilirim, böylece durum güncellendikten sonra bazı görevler bir useEffect'ten kaçınarak yürütülür. Bunun bir tercih mi yoksa belirli bir fayda mı olduğundan tam olarak emin değilim.
Ken Ingram

1
@KenIngram Bunu denedim ve çok özel bir hatayı aldım:Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Greg Sadetsky

22

Önceki durumu güncellemek istiyorsanız, bunu kancalarda yapabilirsiniz:

const [count, setCount] = useState(0);


setCount(previousCount => previousCount + 1);

2
Demek setCounteristediğini düşünüyorumsetCount
Mehdi Dehghani

15

useEffect, yalnızca durum güncellemelerinde tetiklenir :

const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)

useEffect(() => {
  if (!isFirstRender.current) {
    console.log(state) // do something after state has updated
  }
}, [state])

useEffect(() => { 
  isFirstRender.current = false // toggle flag after first render/mounting
}, [])

Üstü taklit edecek setStategeri arama en iyi ve değil ilk durumlarına ilişkin ateşi. Ondan özel bir Kanca çıkarabilirsin:

function useEffectUpdate(effect, deps) {
  const isFirstRender = useRef(true) 

  useEffect(() => {
    if (!isFirstRender.current) {
      effect()
    }  
  }, deps) // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    isFirstRender.current = false;
  }, []);
}

// ... Usage inside component
useEffectUpdate(() => { console.log(state) }, [state])

Alternatif: sınıflarda olduğu gibi bir geri arama almak useState için uygulanabilirsetState .
ford04

15

Sanırım kullanmak useEffectsezgisel bir yol değil.

Bunun için bir sarmalayıcı oluşturdum. Bu özel kancada, geri aramanızı setStateparametre yerine parametreye iletebilirsiniz useState.

Typescript sürümünü yeni oluşturdum. Bu yüzden, bunu Javascript'te kullanmanız gerekiyorsa, koddan bir tür notasyonu kaldırmanız yeterlidir.

Kullanım

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.log(n) // 2
});

Beyanname

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';

type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;

function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
  const [state, _setState] = useState(initialState);

  const callbackRef = useRef<Callback<T>>();
  const isFirstCallbackCall = useRef<boolean>(true);

  const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
    callbackRef.current = callback;
    _setState(setStateAction);
  }, []);

  useEffect(() => {
    if (isFirstCallbackCall.current) {
      isFirstCallbackCall.current = false;
      return;
    }
    callbackRef.current?.(state);
  }, [state]);

  return [state, setState];
}

export default useStateCallback;

Dezavantaj

Geçilen ok işlevi bir değişken dış işleve başvurursa, durum güncellendikten sonra bir değer değil, geçerli değeri yakalar. Yukarıdaki kullanım örneğinde, console.log (state) 2 değil 1 yazdıracaktır.


1
Teşekkürler! havalı yaklaşım. Bir örnek kod kutusu
ValYouW

@ValYouW Örnek oluşturduğunuz için teşekkür ederiz. Tek sorun, ok işlevi, değişken dış işlevi referans alırsa, durum güncellendikten sonra değerleri değil, geçerli değerleri yakalayacağıdır.
MJ Studio

Evet, işlevsel bileşenlerle akılda kalan kısım bu ...
ValYouW

Önceki durum değerini buradan nasıl elde edebilirim?
Arıkan-Zerob

@ Ankan-Zerob Sanırım, Kullanım bölümünde stategeri arama çağrıldığında bir öncekine atıfta bulunuyor. Değil mi?
MJ Studio

0

Sorunuz çok geçerli. UseEffect'in varsayılan olarak bir kez ve bağımlılık dizisi her değiştiğinde çalıştırıldığını söyleyeyim.

aşağıdaki örneğe bakın:

import React,{ useEffect, useState } from "react";

const App = () => {
  const [age, setAge] = useState(0);
  const [ageFlag, setAgeFlag] = useState(false);

  const updateAge = ()=>{
    setAgeFlag(false);
    setAge(age+1);
    setAgeFlag(true);
  };

  useEffect(() => {
    if(!ageFlag){
      console.log('effect called without change - by default');
    }
    else{
      console.log('effect called with change ');
    }
  }, [ageFlag,age]);

  return (
    <form>
      <h2>hooks demo effect.....</h2>
      {age}
      <button onClick={updateAge}>Text</button>
    </form>
  );
}

export default App;

SetState geri çağrısının kancalarla yürütülmesini istiyorsanız, bayrak değişkenini kullanın ve useEffect içinde IF ELSE OR IF bloğunu verin, böylece bu koşullar sağlandığında yalnızca o kod bloğu yürütülür. Her ne kadar zaman etkisi bağımlılık dizisi değiştikçe çalışır, ancak efekt içindeki IF kodu yalnızca belirli koşullarda çalıştırılır.


3
Bu işe yaramayacak. UpdateAge'daki üç ifadenin gerçekte hangi sırayla çalışacağını bilmiyorsunuz. Üçü de asenkron. Garanti edilen tek şey, ilk satırın 3. satırdan önce çalışmasıdır (çünkü aynı durumda çalışırlar). 2. satır hakkında hiçbir şey bilmiyorsun. Bu örnek, bunu görmek için çok basit.
Mohit Singh

Arkadaşım mohit. React sınıflarından hook'lara geçerken bu tekniği büyük bir karmaşık react projesinde uyguladım ve mükemmel çalışıyor. SetState geri aramasını değiştirmek için kancaların herhangi bir yerinde aynı mantığı deneyin ve bileceksiniz.
arjun sah

3
"projemdeki işler bir açıklama değildir", dokümanları okuyun. Hiç senkronize değiller. UpdateAge'daki üç satırın bu sırada çalışacağından emin olamazsınız. Eğer senkronize olmuşsa, o zaman neye ihtiyaç var, setAge'dan sonra doğrudan console.log () satırını çağırın.
Mohit Singh

useRef, "ageFlag" için çok daha iyi bir çözümdür.
Dr.Flink

0

setState() Bileşen durumundaki değişiklikleri sıralar ve React'e bu bileşenin ve alt bileşenlerinin güncellenmiş durumla yeniden oluşturulması gerektiğini söyler.

setState yöntemi eşzamansızdır ve aslına bakılırsa, bir söz vermez. Dolayısıyla, bir işlevi güncellemek veya çağırmak istediğimiz durumlarda, ikinci argüman olarak setState işlevinde işlev geri çağrı olarak adlandırılabilir. Örneğin, yukarıdaki durumunuzda, bir işlevi setState geri araması olarak çağırdınız.

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

Yukarıdaki kod, sınıf bileşeni için iyi çalışır, ancak işlevsel bileşen durumunda, setState yöntemini kullanamayız ve bu, aynı sonucu elde etmek için kullanım efekti kancasını kullanabiliriz.

Akla gelen bariz yöntem, ypu'nun useEffect ile kullanabileceği şu şekildedir:

const [state, setState] = useState({ name: "Michael" })

useEffect(() => {
  console.log(state) // do something after state has updated
}, [state])

Ancak bu, ilk oluşturmada da tetiklenir, bu nedenle kodu aşağıdaki gibi değiştirebiliriz; burada ilk oluşturma olayını kontrol edebilir ve durum oluşturmadan kaçınabiliriz. Bu nedenle uygulama şu şekilde yapılabilir:

İlk oluşturmayı tanımlamak için buradaki kullanıcı kancasını kullanabiliriz.

UseRef Hook, fonksiyonel bileşenlerde değiştirilebilir değişkenler oluşturmamıza izin verir. DOM düğümlerine / React öğelerine erişmek ve yeniden oluşturmayı tetiklemeden değişken değişkenleri depolamak için kullanışlıdır.

const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);

useEffect(() => {
 if (!firstTimeRender.current) {
    console.log(state);
  }
}, [state])

useEffect(() => { 
  firstTimeRender.current = false 
}, [])

0

Durum ayarlandıktan sonra bazı parametrelerle API araması yapmak istediğim bir kullanım durumum vardı . Bu parametreleri durumum olarak ayarlamak istemedim, bu yüzden özel bir kanca yaptım ve işte çözümüm

import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';

export const useStateWithCallback = initialState => {
  const [state, setState] = useState(initialState);
  const callbackRef = useRef(_noop);

  const handleStateChange = useCallback((updatedState, callback) => {
    setState(updatedState);
    if (_isFunction(callback)) callbackRef.current = callback;
  }, []);

  useEffect(() => {
    callbackRef.current();
    callbackRef.current = _noop; // to clear the callback after it is executed
  }, [state]);

  return [state, handleStateChange];
};

-3

UseEffect birincil çözümdür. Ancak Darryl'in bahsettiği gibi, useEffect'i kullanmak ve ikinci parametrenin bir kusuru olduğu için state'e geçmek, bileşen başlatma sürecinde çalışacaktır. Sadece geri arama işlevinin güncellenmiş durum değerini kullanarak çalışmasını istiyorsanız, bir yerel sabit ayarlayabilir ve bunu hem setState hem de geri aramada kullanabilirsiniz.

const [counter, setCounter] = useState(0);

const doSomething = () => {
  const updatedNumber = 123;
  setCounter(updatedNumber);

  // now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
  console.log(updatedNumber);
}

4
setCounter zaman uyumsuzdur, console.log'un setCounter'dan sonra mı yoksa önce mi çağrılacağını bilmiyorsunuz.
Mohit Singh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.