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


150

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

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

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

를 비교하는 방법이 oldValuesnewValues에 좋아하는 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.