React Hooks useEffect에서 oldValues와 newValues를 비교하는 방법은 무엇입니까?


150

rate, sendAmount 및 receiveAmount의 세 가지 입력이 있다고 가정합니다. 3 개의 입력을 useEffect diffing params에 넣었습니다. 규칙은 다음과 같습니다.

  • sendAmount가 변경되면 계산 receiveAmount = sendAmount * rate
  • receiveAmount가 변경되면 계산 sendAmount = receiveAmount / rate
  • 속도를 변경 한 경우, 내가 계산할 receiveAmount = sendAmount * rate때 sendAmount > 0또는 내가 계산할 sendAmount = receiveAmount / rate때receiveAmount > 0

다음은 문제를 설명하기 위한 코드 샌드 박스 https://codesandbox.io/s/pkl6vn7x6j 입니다.

를 비교하는 방법이 oldValues과 newValues에 좋아하는 componentDidUpdate대신이 경우에 3 처리기를 만드는가?

감사


https://codesandbox.io/s/30n01w2r06의 최종 솔루션은 다음과 같습니다.usePrevious

이 경우 useEffect각 변경으로 인해 동일한 네트워크 호출이 발생하므로 여러 개를 사용할 수 없습니다 . 그렇기 때문에 changeCount변경 사항도 추적하는 데 사용 합니다. changeCount또한 로컬의 변경 사항 만 추적하는 데 도움 이 되므로 서버의 변경으로 인한 불필요한 네트워크 호출을 방지 할 수 있습니다.


componentDidUpdate가 정확히 어떻게 도움이됩니까? 여전히이 세 가지 조건을 작성해야합니다.
— Estus Flask

2 가지 옵션 솔루션으로 답변을 추가했습니다. 그들 중 하나가 당신을 위해 일합니까?
— 벤 잉어

답변:


209

useRef를 사용하여 이전 소품 을 제공하기 위해 사용자 정의 후크를 작성할 수 있습니다.

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

그런 다음에 사용하십시오. 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])
}

그러나 useEffect각 변경 ID에 대해 두 개를 별도로 사용 하여 개별적으로 처리하려는 경우 읽고 이해하기가 더 명확하고 아마 더 좋고 명확합니다.


8
두 useEffect통화를 별도로 사용하는 것에 대한 참고 사항에 감사드립니다 . 여러 번 사용할 수 있다는 것을 몰랐습니다!
— JasonH

3
나는 위의 코드를 시도했지만 eslint은 저를 경고하고 useEffect없는 의존성prevAmount
— Littlee

2
prevAmount는 state / props의 이전 값에 대한 값을 보유하므로 이펙터를 사용하기 위해 의존성으로 전달할 필요가 없으며 특정 경우에 대해이 경고를 사용하지 않을 수 있습니다. 자세한 내용은이 게시물을 읽을 수 있습니까?
— Shubham Khatri

이것을 좋아하십시오 – useRef는 항상 구조에 온다.
— Fernando Rojo

@ShubhamKhatri : ref.current = value를 사용하는 이유는 무엇입니까?
— curly_brackets

40

누구든지 TypeScript 버전의 사용을 찾고있는 경우

import { useEffect, useRef } from "react";

const usePrevious = <T extends {}>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

2
이 할 TSX 파일 변화 작업이 얻을하는 TSX 파일에서 작동하지 않습니다 const usePrevious = <T extends any>(...인터프리터가 <T> 생각 볼 수 있도록이 JSX하지 않고 일반적인 제한 사항입니다
— apokryfos

1
왜 효과 <T extends {}>가 없는지 설명 할 수 있습니까? 그것은 나를 위해 일하는 것 같지만 그것을 사용하는 것의 복잡성을 이해하려고 노력하고 있습니다.
— Karthikeyan_kk

.tsx파일은 html과 동일한 jsx를 포함합니다. 제네릭 <T>이 닫히지 않은 구성 요소 태그 일 수 있습니다.
— SeedyROM

반환 유형은 어떻습니까? 나는 얻는다Missing return type on function.eslint(@typescript-eslint/explicit-function-return-type)
— Saba Ahang

@SabaAhang 죄송합니다! TS는 유형 유추를 사용하여 대부분 반환 유형을 처리하지만 보푸라기를 활성화 한 경우 경고를 제거하기 위해 반환 유형을 추가했습니다.
— SeedyROM

25

옵션 1-후크 비교

현재 값을 이전 값과 비교하는 것은 일반적인 패턴이며 구현 세부 정보를 숨기는 고유 한 사용자 지정 후크를 정당화합니다.

const useCompare = (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}

const Component = (props) => {
  ...
  const hasVal1Changed = useCompare(val1)
  const hasVal2Changed = useCompare(val2);
  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
    if (hasVal2Changed ) {
      console.log("val2 has changed");
    }
  });

  return <div>...</div>;
};

데모

옵션 2-값 변경시 useEffect 실행

const Component = (props) => {
  ...
  useEffect(() => {
    console.log("val1 has changed");
  }, [val1]);
  useEffect(() => {
    console.log("val2 has changed");
  }, [val2]);

  return <div>...</div>;
};

데모


두 번째 옵션은 나를 위해 일했습니다. 왜 useEffect Twice를 작성해야하는지 안내해 주시겠습니까?
— Tarun Nagpal

@TarunNagpal, 반드시 useEffect를 두 번 사용할 필요는 없습니다. 사용 사례에 따라 다릅니다. 변경된 val을 기록하고 싶다고 상상해보십시오. 의존성 배열에 val1과 val2가 모두 있으면 값이 변경 될 때마다 실행됩니다. 그런 다음 전달하는 함수 내에서 올바른 메시지를 기록하기 위해 어떤 val이 변경되었는지 알아 내야합니다.
— 벤 잉어

답장을 보내 주셔서 감사합니다. "함수 안에서 전달할 때 어떤 val이 변경되었는지 알아 내야합니다"라고 말할 때. 우리가 그것을 달성 할 수있는 방법. 안내하십시오
— Tarun Nagpal

6

방금 이러한 종류의 시나리오를 해결하는 react-delta 를 게시했습니다 . 제 생각 useEffect에는 너무 많은 책임이 있습니다.

책임

  1. 의존성 배열의 모든 값을 사용하여 비교합니다. Object.is
  2. # 1의 결과에 따라 효과 / 정리 콜백을 실행합니다.

책임 해체

react-deltauseEffect의 책임을 여러 개의 작은 고리로 나눕니다 .

책임 # 1

책임 # 2

내 경험에 따르면이 방법은 useEffect/ useRef솔루션 보다 유연하고 깨끗하며 간결 합니다.


내가 찾고있는 것. 시도해 볼게요!
— hackerl33t

정말 좋아 보이지만 약간 과잉 아닌가요?
— 히사토

@ 히사토 그것은 과잉 일 수도 있습니다. 실험적인 API입니다. 솔직히 나는 널리 알려 지거나 채택되지 않았기 때문에 팀에서 많이 사용하지 않았습니다. 이론적으로는 멋지게 들리지만 실제로는 가치가 없을 수도 있습니다.
— Austin Malerba

3

상태는 기능적 구성 요소의 구성 요소 인스턴스와 밀접하게 연결되어 있지 않으므로 예를 useEffect들어을 사용하여 먼저 저장 하지 않으면 이전 상태에 도달 할 수 없습니다 useRef. 이는 또한 이전 상태가 setState업데이터 기능 내에서 사용 가능하기 때문에 상태 업데이트가 잘못된 위치에서 잘못 구현되었을 수 있음을 의미합니다 .

이는 useReducerRedux와 유사한 저장소를 제공하고 각 패턴을 구현할 수 있는 좋은 사용 사례입니다 . 상태 업데이트는 명시 적으로 수행되므로 어떤 상태 속성이 업데이트되는지 파악할 필요가 없습니다. 이것은 이미 파견 된 행동에서 분명하다.

여기의 예 는 것처럼 보일 수 있습니다 무엇 :

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

@estus 님 안녕하세요,이 아이디어에 감사드립니다. 그것은 또 다른 사고 방식을 제공합니다. 그러나 각 경우에 대해 API를 호출해야한다는 것을 언급하지 않았습니다. 그에 대한 해결책이 있습니까?
— rwinzhang

무슨 소리 야? 내부 손잡이 'API'는 원격 API 엔드 포인트를 의미합니까? 답변에서 codesandbox를 세부 정보로 업데이트 할 수 있습니까?
— Estus Flask

업데이트 sendAmount에서 계산하지 않고 비동기식 으로 페치 등 을 가져오고 싶다고 가정 할 수 있습니다 . 이것은 많이 바뀝니다. 이 작업을 수행하는 것이 가능 useReduce하지만 까다로울 수 있습니다. Redux를 다루기 전에 비동기 작업이 간단하지 않다는 것을 알 수 있습니다. github.com/reduxjs/redux-thunk 는 비동기 작업을 허용하는 Redux의 대중적인 확장입니다. 다음은 동일한 패턴 (useThunkReducer) 인 codesandbox.io/s/6z4r79ymwr로 useReducer를 보강하는 데모입니다 . 디스패치 된 함수는입니다 async(요청 됨).
— Estus Flask

1
질문의 문제는 실제 사례가 아닌 다른 것을 묻고 변경했기 때문에 기존 답변이 질문을 무시한 것처럼 나타나는 동안 매우 다른 질문입니다. 이 방법은 원하는 답변을 얻는 데 도움이되지 않으므로 권장되지 않습니다. 답변에서 이미 언급 한 질문에서 중요한 부분을 제거하지 마십시오 (계산 수식). 그래도 여전히 문제가있는 경우 (이전의 의견 이후 (아마도 가능)) 귀하의 사례를 반영하는 새로운 질문을하고이를 이전의 시도로 여기에 연결하십시오
— Estus Flask

안녕하세요,이 코드 샌드 박스 codesandbox.io/s/6z4r79ymwr 이 아직 업데이트되지 않았다고 생각합니다 . 질문을 다시 바꾸겠습니다. 죄송합니다.
— rwinzhang

3

Ref를 사용하면 앱에 새로운 종류의 버그가 생깁니다.

usePrevious누군가가 전에 주석을 달아서이 사례를 보도록하겠습니다 .

  1. prop.minTime : 5 ==> 참조 전류 = 5 | 기준 전류 설정
  2. prop.minTime : 5 ==> 참조 전류 = 5 | 새로운 값은 참조 전류와 같습니다.
  3. prop.minTime : 8 ==> 참조 전류 = 5 | 새로운 값은 참조 전류와 같지 않습니다
  4. prop.minTime : 5 ==> 참조 전류 = 5 | 새로운 값은 참조 전류와 같습니다.

우리가 볼 수 있듯이, ref우리는 사용하기 때문에 내부를 업데이트하지 않습니다useEffect


1
React에는이 예제가 있지만 이전 소품에 관심이있을 때 ...이 state아닌 ...을 사용하고 props있습니다. 오류가 발생합니다.
— santomegonzalo

3

정말 간단한 소품 비교를 위해 useEffect를 사용하여 소품이 업데이트되었는지 쉽게 확인할 수 있습니다.

const myComponent = ({ prop }) => {
  useEffect(() => {
     ---Do stuffhere----
    }
  }, [prop])

}

그러면 소품이 바뀌면 useEffect가 코드를 실행합니다.


1
연속 prop변경 후에는 한 번만 작동 합니다.~ do stuff here ~
— Karolis Šarapnickis

2
당신의 상태를 "재설정"하기 위해 setHasPropChanged(false)마지막에 전화해야 할 것 같습니다 ~ do stuff here ~. (하지만이 추가로 다시 쓰게 리셋 것)
— 케빈 왕에게

의견을 보내 주셔서 감사합니다. 업데이트 된 솔루션입니다
— Charlie Tupman

AntonioPavicevac 오티즈 @ 난 그냥 useEffect 밖으로 버리고 그냥 소품 확인 후 더 나은 솔루션이 될 수 있습니다, 렌더링 한 번에 호출 것이다 사실로 propHasChanged 지금에 렌더링 대답을 업데이 트했습니다
— 찰리 Tupman

나는 이것의 원래 사용이 없어 졌다고 생각한다. 코드를 되돌아 보면 useEffect를 사용할 수 있습니다
— Charlie Tupman

2

사용자 정의 후크가 필요없는 대체 솔루션 인 수락 된 답변을 시작하십시오.

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

1
좋은 해결책이지만 구문 오류가 있습니다. useRef아닙니다 userRef. 전류 사용을 잊어 버렸습니다prevAmount.current
— Blake Plumb

0

다음은 내가 사용하는 것보다 직관적이라고 생각하는 사용자 정의 후크입니다 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)
}

useTransition다음과 같이 사용하십시오 .

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

희망이 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.