반응 후크에서`setState` 콜백을 사용하는 방법


134

React hooks는 useState구성 요소 상태를 설정하기 위해 도입 되었습니다. 그러나 후크를 사용하여 아래 코드와 같이 콜백을 대체하려면 어떻게해야합니까?

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

상태가 업데이트 된 후에 뭔가를하고 싶습니다.

useEffect추가 작업을 수행하는 데 사용할 수 있지만 비트 코드가 필요한 이전 상태 값을 확인해야합니다. useState후크 와 함께 사용할 수있는 간단한 솔루션을 찾고 있습니다 .


1
클래스 구성 요소에서 async를 사용하고 setState에 콜백을 추가하기 위해 한 것과 같은 결과를 얻기 위해 대기했습니다. 불행히도 후크에서 작동하지 않습니다. async 및 await를 추가하더라도 react는 상태가 업데이트 될 때까지 기다리지 않습니다. useEffect가이를 수행하는 유일한 방법 일 수 있습니다.
MING WU

2
@Zhao, 아직 정답을 표시하지 않았습니다. 친절하게 몇 초만 주
시겠어요

답변:


151

이를 위해서는 useEffect후크를 사용해야합니다.

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

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

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

55
이렇게하면 console.log첫 번째 렌더와 언제든지 counter변경 될 때 . 상태가 업데이트 된 후에 만 ​​수행하고 초기 값이 설정되어 초기 렌더링시에는 수행하지 않으려면 어떻게해야합니까? 나는 당신이 가치를 확인하고 useEffect당신이 무언가를 할 것인지 결정할 수 있다고 생각합니다 . 이것이 모범 사례로 간주됩니까?
Darryl Young 19

2
useEffect초기 렌더링에서 실행되지 않도록하려면 초기 렌더링에서 실행되지 않는 사용자 지정 useEffecthook을 만들 수 있습니다 . 이러한 후크를 만들려면, 당신은이 질문을 확인 할 수 있습니다 stackoverflow.com/questions/53253940/...

14
그리고 다른 setState 호출에서 다른 콜백을 호출하려는 경우 동일한 상태 값이 변경되는 경우는 어떻습니까? 당신의 대답은 틀 렸으며, 초보자를 혼동하지 않도록 올바른 것으로 표시해서는 안됩니다. 사실은 setState가 하나의 명확한 해결 방법이없는 클래스에서 후크로 마이그레이션하는 동안 가장 어려운 문제 중 하나를 콜백한다는 것입니다. 때때로 당신은 가치에 의존하는 어떤 효과를 경험하게 될 것이고, 때로는 Refs에 어떤 종류의 플래그를 저장하는 것과 같은 해키 방법이 필요할 것입니다.
Dmitry Lobov

분명히 오늘은 setCounter (value, callback)를 호출하여 상태가 업데이트 된 후 일부 작업이 실행되도록하여 useEffect를 피할 수 있습니다. 이것이 선호 사항인지 특정 이점인지 완전히 확실하지 않습니다.
Ken Ingram

2
@KenIngram 난 그냥 것을 시도하고이 매우 구체적인 오류가 발생했습니다 :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().
그렉 Sadetsky

26

이전 상태를 업데이트하려면 후크에서 다음과 같이 할 수 있습니다.

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


setCount(previousCount => previousCount + 1);

2
나는에 의해 생각 setCounter의미 당신setCount
메디 Dehghani

@BimalGrg 정말 작동합니까? 나는 이것을 복제 할 수 없다. 다음 오류와 함께 컴파일에 실패합니다.expected as assignment or function call and instead saw an expression
tonitone120

@ tonitone120 setCount 내부에 화살표 함수가 있습니다.
Bimal Grg

@BimalGrg 질문은 상태가 업데이트 된 즉시 코드를 실행할 수있는 기능을 요청하는 것 입니다. 이 코드가 정말로 그 작업에 도움이됩니까?
tonitone120

네, 그것은, 클래스 구성 요소로 this.setState처럼 작동합니다
Aashiq 아메드에게

19

useEffect, 상태 업데이트 에서만 발생 합니다 .

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

위는 setState콜백을 가장 잘 모방하고 초기 상태 에서는 실행되지 않습니다 . 여기에서 사용자 지정 후크를 추출 할 수 있습니다.

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

대안 : 클래스에서 와 같이 콜백을 수신 useState 하도록 구현할 수 있습니다setState .
ford04

18

사용 useEffect은 직관적 인 방법이 아니라고 생각 합니다.

나는 이것을 위해 래퍼를 만들었습니다. 이 커스텀 후크에서는 setState매개 변수 대신 매개 변수로 콜백을 전송할 수 있습니다 useState.

방금 Typescript 버전을 만들었습니다. 따라서 Javascript에서 이것을 사용해야하는 경우 코드에서 유형 표기법을 제거하십시오.

용법

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

선언

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;

약점

전달 된 화살표 함수가 변수 외부 함수를 참조하는 경우 상태가 업데이트 된 후 값이 아닌 현재 값을 캡처합니다. 위의 사용 예에서 console.log (state) 는 2가 아닌 1을 인쇄합니다.


1
감사! 멋진 접근 방식. 샘플 코드 샌드 박스
ValYouW

@ValYouW 샘플을 만들어 주셔서 감사합니다. 유일한 문제는 전달 된 화살표 함수가 변수 외부 함수를 참조하는 경우 상태가 업데이트 된 후 값이 아닌 현재 값을 캡처한다는 것입니다.
MJ Studio

예, 그게 기능적 구성 요소로 눈길을 끄는 부분입니다 ...
ValYouW

여기서 이전 상태 값을 얻는 방법은 무엇입니까?
Ankan-Zerob

@ Ankan-Zerob 나는 Usage 부분에서 state콜백이 호출 될 때 이전 것을 참조하고 있다고 생각 합니다. 그렇지 않나요?
MJ Studio

2

setState() 컴포넌트 상태의 변경 사항을 대기열에 추가하고이 컴포넌트와 그 자식을 업데이트 된 상태로 다시 렌더링해야한다고 React에 알립니다.

setState 메서드는 비동기 적이며 사실 약속을 반환하지 않습니다. 따라서 함수를 업데이트하거나 호출하려는 경우 setState 함수에서 두 번째 인수로 해당 함수를 콜백이라고 부를 수 있습니다. 예를 들어, 위의 경우 함수를 setState 콜백으로 호출했습니다.

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

위의 코드는 클래스 컴포넌트에 대해 잘 작동하지만 함수 컴포넌트의 경우 setState 메소드를 사용할 수 없으며 동일한 결과를 얻기 위해 use effect hook을 사용할 수 있습니다.

분명한 방법은 ypu가 useEffect와 함께 사용할 수 있다는 것입니다.

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

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

그러나 이것은 첫 번째 렌더링에서도 발생하므로 첫 번째 렌더링 이벤트를 확인하고 상태 렌더링을 피할 수있는 코드를 다음과 같이 변경할 수 있습니다. 따라서 구현은 다음과 같은 방법으로 수행 할 수 있습니다.

여기에서 사용자 후크를 사용하여 첫 번째 렌더링을 식별 할 수 있습니다.

useRef 후크를 사용하면 기능적 구성 요소에서 가변 변수를 만들 수 있습니다. DOM 노드 / React 요소에 액세스하고 다시 렌더링을 트리거하지 않고 변경 가능한 변수를 저장하는 데 유용합니다.

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

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

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

1

내 설정에서 useEffect를 사용하여 동일한 문제가 발생했습니다 (배열 여러 하위 구성 요소에서 부모의 상태를 업데이트하고 어떤 구성 요소가 데이터를 업데이트했는지 알아야 함).

promise에 setState를 래핑하면 완료 후 임의의 작업을 트리거 할 수 있습니다.

import React, {useState} from 'react'

function App() {
  const [count, setCount] = useState(0)

  function handleClick(){
    Promise.resolve()
      .then(() => { setCount(count => count+1)})
      .then(() => console.log(count))
  }


  return (
    <button onClick= {handleClick}> Increase counter </button>
  )
}

export default App;

다음 질문은 저를 올바른 방향으로 인도합니다. 후크를 사용할 때 React 배치 상태가 업데이트됩니까?


0

귀하의 질문은 매우 유효합니다. useEffect가 기본적으로 한 번 실행되고 종속성 배열이 변경 될 때마다 실행된다는 점을 말씀 드리겠습니다.

아래 예를 확인하십시오. :

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 콜백을 실행하려면 플래그 변수를 사용하고 useEffect 내부에 IF ELSE OR IF 블록을 제공하여 해당 조건이 충족되면 해당 코드 블록 만 실행되도록합니다. 그러나 시간 효과는 종속성 배열 변경으로 실행되지만 효과 내부의 IF 코드는 해당 특정 조건에서만 실행됩니다.


4
작동하지 않습니다. updateAge 내부의 세 문이 실제로 어떤 순서로 작동하는지 알 수 없습니다. 세 가지 모두 비동기입니다. 보장되는 유일한 것은 첫 번째 줄이 3 일 전에 실행된다는 것입니다 (동일한 상태에서 작동하기 때문에). 당신은 두 번째 줄에 대해 아무것도 모릅니다. 이 예제는 이것을보기에는 너무 단순합니다.
Mohit Singh

내 친구 mohit. 나는 반응 클래스에서 후크로 이동할 때 크고 복잡한 반응 프로젝트 에서이 기술을 구현했으며 완벽하게 작동합니다. setState 콜백을 대체하기 위해 후크의 모든 위치에서 동일한 논리를 시도하면 알 수 있습니다.
arjun sah

4
"내 프로젝트의 작품은 설명이 아닙니다", 문서를 읽으십시오. 전혀 동기식이 아닙니다. updateAge의 세 줄이 그 순서대로 작동 할 것이라고 확신 할 수 없습니다. 그것이 동기화 되었다면 플래그가 필요한 것은 setAge 뒤에 console.log () 줄을 직접 호출하십시오.
Mohit Singh

useRef는 "ageFlag"에 대한 훨씬 더 나은 솔루션입니다.
Dr. Flink

0

상태가 설정된 후 일부 매개 변수 를 사용하여 API를 호출 하려는 사용 사례 가 있습니다. 이 매개 변수를 내 상태로 설정하고 싶지 않았으므로 사용자 지정 후크를 만들었고 여기에 내 솔루션이 있습니다.

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는 기본 솔루션입니다. 그러나 Darryl이 언급했듯이 useEffect를 사용하고 두 번째 매개 변수에 상태를 전달하면 한 가지 결함이 있으므로 구성 요소가 초기화 프로세스에서 실행됩니다. 업데이트 된 상태의 값을 사용하여 콜백 함수를 실행하려면 로컬 상수를 설정하고 setState와 콜백 모두에서 사용할 수 있습니다.

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);
}

5
setCounter는 setCounter 이후 또는 이전에 console.log가 호출되는지 알 수없는 비동기입니다.
Mohit Singh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.