React/React 문법

[React]Hook에서 prevState 구현

DevStory 2021. 10. 5.

이번 포스팅에서는 React Hook에서 prevState를 구현하는 방법을 설명합니다.

 

미리 말씀드리자면, React Hook에서는 클래스 컴포넌트처럼 생명주기 메서드에서 prevState를 사용할 수 없습니다.

 

React Hook에서 사용 가능한 useEffect를 활용한다면, 이전 state 값과 현재 state 값을 비교할 필요가 없기 때문입니다.

 

그럼에도 불구하고 이전 state 값과 현재 state 값을 비교해야 한다면, 커스텀 훅과 useRef를 활용하여 prevState처럼 사용할 수 있습니다.

 


React 클래스 컴포넌트에서 prevState

클래스 컴포넌트의 생명주기 메서드에는 두 번째 인자를 통해 이전 state 값을 구할 수 있습니다.

 

다음은 클래스 컴포넌트 생명주기 메서드 componentDidUpdate에서 prevState를 사용하는 코드입니다.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text1: "",
      text2: ""
    };
  }

  componentDidUpdate(prevProps, prevState) {
    // text1 변경시에만 호출
    if (prevState.text1 !== this.state.text1) {
      console.log("[text1] componentDidUpdate()");
    }
    // text2 변경시에만 호출
    if (prevState.text2 !== this.state.text2) {
      console.log("[text1, text2]componentDidUpdate()");
    }
  }

  render() {
    return (
      <React.Fragment>
        <input type="text" value={this.state.text1}
               onChange={(e) => { this.setState({ text1: e.target.value }); }} />
        <div>Value: {this.state.text1}</div>
        
        <input type="text" value={this.state.text2}
               onChange={(e) => { this.setState({ text2: e.target.value }); }} />
        <div>Value: {this.state.text2}</div>
      </React.Fragment>
    );
  }
}

다음은 코드 실행 과정입니다.

  1. input 텍스트 박스에서 값을 입력합니다.
  2. 값을 입력하면, onChange이벤트가 발생합니다.
  3. onChange이벤트에서 state를 변경합니다.
  4. state가 변경되었으므로 componentDidUpdate 생명주기 메서드가 실행됩니다.

 

componentDidUpdate 메서드에서 두 번째 인자인 prevState로 이전 state와 현재 state를 비교합니다.

 

특정 state의 값이 변경되었을 경우에만 실행해야 하는 로직이 존재할 경우 위 코드처럼 componentDidUpdate 메서드에서 조건문을 통해 로직을 실행시킬 수 있습니다.

 

이러한 방식은 componentDidUpdate 메서드에서 체크해야 하는 state가 많아질수록 조건문이 코드가 난잡해지는 문제가 발생합니다.


React Hook에서 componentDidMount

React Hook에서는 useEffect를 사용하여 특정 state만 변경되었을 경우 로직을 실행시킬 수 있습니다.

const App = () => {
  const [textState1, setTextState1] = useState("");
  const [textState2, setTextState2] = useState("");

  //textState1의 값이 변경되었을 경우에만 실행됩니다.
  useEffect(() => {
    console.log("[textState1] componentDidUpdate()");
  }, [textState1]);

  //textState2의 값이 변경되었을 경우에만 실행됩니다.
  useEffect(() => {
    console.log("[textState2] componentDidUpdate()");
  }, [textState2]);

  return (
    <div>
      <input type="text" value={textState1}
             onChange={(e) => { setTextState1(e.target.value); }} />
      <div>Value: {textState1}</div>
      
      <input type="text" value={textState2}
             onChange={(e) => { setTextState2(e.target.value); }} />
      <div>Value: {textState2}</div>
    </div>
  );
};

React Hook에서는 여러 개의 useEffect를 작성할 수 있습니다.

 

특정 state의 값이 변경되었을 경우 useEffect가 실행되게 하기 위해서 useEffect의 두 번째 매개변수 빈 배열( [] )에 특정 state를 작성합니다.

 

해당 state가 변경되었을 때, useEffect가 실행되므로 클래스 컴포넌트의 componentDidUpdate 메서드처럼 state의 이전 값과 현재 값을 비교할 필요가 없습니다.


커스텀 훅과 useRef를 활용하여 prevState처럼 사용

그럼에도 불구하고 React Hook에서 prevState처럼 사용해야 하는 경우가 있다면, 커스텀 훅과 useRef를 활용합니다.

function usePrevState(state) {
  const ref = useRef();
  console.log(`before : ${state}`);
  //rendering 후 실행됨
  useEffect(() => {
    console.log(`useEffect : ${state}`);
    ref.current = state;
  });
  console.log(`after : ${state}`);
  return ref.current;
}

const App = () => {
  const [textState, setTextState] = useState("init");
  const prevTextState = usePrevState(textState);

  useEffect(() => {
    console.log(`DidMount prevTextState : ${prevTextState}`);
    console.log(`DidMount textState : ${textState}`);
  }, []);

  useEffect(() => {
    console.log(`DidUpdate prevTextState : ${prevTextState}`);
    console.log(`DidUpdate textState : ${textState}`);
  }, [textState]);

  console.log(`render Before prevTextState : ${prevTextState}`);
  console.log(`render Before textState : ${textState}`);
  return (
    <div>
      <input
        type="text"
        value={textState}
        onChange={(e) => {
          setTextState(e.target.value);
        }}
      />
      <div>Value: {textState}</div>
    </div>
  );
};

다음은 App 컴포넌트의 실행 과정입니다.

 

(1) text는 useState()에 호출된 값 "init"으로 초기화됩니다. text의 값은 "init"입니다.

 

(2) 커스텀 훅 usePrevState()가 실행됩니다. text의 값은 현재 "init"이므로 "init"이 전달됩니다.

 

(3) ref 객체를 생성합니다. useRef()에 빈 값을 전달하므로 {current: undefined}를 반환합니다.

 

(4) ref.current가 반환됩니다. 현재 ref.current는 (3) 번 과정에 의해 undefined입니다.

useEffect()는 App 컴포넌트가 렌더링된 후 호출됩니다.

 

아직 App 컴포넌트가 렌더링 되지 않았으므로 useEffect()는 실행되지 않습니다.

 

(5) prevTextState는 undefined입니다.

 

(6) App 컴포넌트가 렌더링 된 직후 usePrevState()의 useEffect()가 비동기적으로 호출됩니다.

 

비동기적으로 호출된 이유는 화면에 DOM을 그리는 것을 우선순위로 하기 때문입니다.

 

화면에 DOM을 그리는 도중 useEffect()가 호출되면 예기치 못한 버그 및 현상이 발생하므로 렌더링 직후 호출됩니다.

usePrevState()의 useEffect()는 ref 객체의 current를 value(현재 value는 "init")로 업데이트합니다.

 

(7) input text박스에 값을 입력해서 text의 값을 변경하면 (1) ~ (6) 과정이 반복됩니다.

 

아래 링크를 통해서 예제 코드를 실행할 수 있으며, 코드의 실행 과정을 콘솔로 확인할 수 있습니다.

☞ CodeSandBox에서 React Hook에서 prevState 사용한 예제

반응형

댓글