React 재활훈련- 5일차, Lifecycle, hooks

0

react

목록 보기
5/11

https://www.udemy.com/course/react-next-master/

React lifecycle

react의 component들도 lifecycle을 갖는데, 다음의 3단계를 가진다.

Mount -> Update -> Unmount
  1. Mount: component가 화면에 렌더링된 순간이다. eX) 주로 서버에서 데이터를 불러올 때, 사용한다.
  2. Update: component가 초기 렌더링된 이후 state, props가 변화된 이후 리렌더링될 때이다. ex) 어떤 값이 변경되었는지 콘솔에 출력
  3. Unmount: component가 화면에 제거된 순간이다. ex) 컴포넌트가 사용하던 메모리 정리 및 해제

이렇게 lifecycle에 맞게 code를 실행하는 것을 lifecycle control(라이프사이클 제어)라고 한다. 이를 가능하게해주는 것이 바로 useEffect이다.

useEffect를 사용하는 방법은 매우 간단하다.

useEffect(() => {
    console.log(count)
}, [count])

첫번째는 callback함수를 넣어주면 되고, 두번째는 배열안에 변수를 입력해주면 되는데, 이 변수에 update가 발생하면 첫번째에 넣은 callback함수를 실행시켜준다. 따라서, 배열이기 따라서 여러 개의 값을 넣어줘도 된다.

console.log를 잘보면 맨 처음에 한 번 실행되고, update가 발생함에 따라서 계속해서 실행되는 것을 알 수 있다.

useEffect는 하나의 react component에 여러 개를 사용할 수 있다.

useEffect(() => {
    console.log(`카운트: ${count}`)
}, [count])

useEffect(() => {
    console.log(`텍스트: ${text}`)
}, [text])

count가 변동되면 첫번째 useEffect가 실행되고, text가 변동되면 두번째 useEffect가 실행된다.

여기서 조심해야할 것이 있는데, 이렇게 state가 변동됨에 따라 로직을 진행하고 싶다면 onClick과 같은 event handler에서 처리하면 되지 않을까? 싶을 것이다.

const onClickButton = (value) => {
    setCount(count + value)
    console.log(`카운트: ${count}`)
}

const onChangeText = (e) => {
    setText(e.target.value);
    console.log(`텍스트: ${text}`)
}

하지만 안타깝게도 event handler에서 찍은 count, text state값은 정상적인 값으로 나오지 않을 것이다. 이는 setCount, setText와 같은 state의 setter가 비동기적으로 동작하기 때문이다. 따라서, 출력되는 값이 변동된 값이 아니라, 그 이전 값이 나올텐데 이는 변동이 늦게 반영되어서 그런 것이다.

반면 useEffect의 경우는 update가 발생한 이후, 즉 update가 반영된 이후에 실행되므로 정상적인 값이 출력되는 것이다.

useEffect를 사용하여 react component의 생명주기를 구현할 수 있다. 먼저 update시점에 로직을 추가하고 싶다면 useEffect의 두번째 인자를 안넣어주면 된다.

useEffect(() => {
    console.log("업데이트")
})

다음과 같이 두번째 인자를 비워두면 mount시점과 update시점에 모두 호출이 된다. 즉, 처음에 한 번 실행되고, 리렌더링이 발생할 때마다 실행이 되는 것이다.

그런데, 만약 처음에 mount될 때 실행시키지 않고 이후 update시점에만 실행시키고 싶다면, useRef를 이용한 trick을 이용하면 된다.

const isMountRef = useRef(false)

useEffect(() => {
    if (!isMountRef.current) {
        isMountRef.current = true;
        return;
    }
    console.log("업데이트")
})

조금 복잡해보일지도 모르는데, 동작 원리를 알면 굉장히 간단하다. useRefcurrent라는 property에 값을 저장할 수 있는 hook이다. 이는 리렌더링이 발생해도 current에 이전에 저장한 값을 저장하고 있는데, 마치 pointer나 reference 객체를 생각하면 된다.

때문에 처음에는 currentfalse를 저장하고 있지만 처음 useEffect가 실행되면서 isMountRefcurrenttrue로 바꾸게 된다. 이후에는 isMountRefcurrent값이 변경되는 일이 없으므로 오직 update가 되어 리렌더링 될 때에만 useEffect의 callback 함수가 실행된다.

다음으로 맨 처음 mount 단계일 때만 실행되고, 이후에는 실행되지 않도록 하고 싶다면 useEffect의 두 번째 인자로 []를 넣어주면 된다.

useEffect(() => {
    console.log("mount")
}, [])

mount될 때 딱 한번만 호출되고, 이후에는 update되어도 실행되지 않는다.

마지막으로 unmount 단계는 어떻게 할 수 있을까?

useEffect(() => {
    return () => {
        console.log("언마운트")
    }
}, [])

좀 기가막히고 코가막히는 문법인데, 예전에 react를 배웠던 문법 그대로, 왜 아직도 이딴 식으로 쓰는 지는 모르겠다.

useEffect 첫번째 인자인 callback함수에 return으로 callback함수를 써주고, 두번째 인자를 빈 []배열로 만들어주면 된다.

이렇게 첫번째 인자인 callback함수에 return으로 callback함수를 만들어주면, 딱 두 가지 인 경우에 return으로 준 callback함수가 실행된다.

  1. unmount할 때 (component가 삭제될 때)
  2. rerendering될 때

그러나, 두 번째 인자가 []이므로 rerendering될 때는 실행되지 않는다. 따라서, 오직 unmount될 때만 실행되는 것이다.

React Hook

함수 컴포넌트에서 react가 제공하는 다양한 기능들을 사용할 수 있는 method들이다. 대표적으로 useState, useEffect, useRef 등이 있다. 앞에서 알 수 있듯이 react hook은 접두사로 use~를 사용하는 관행이 있다.

이외에도 아주 다양하게 hook들이 있는데, useReducer, useMemo, useContext등이 있다.

사실 예전에 react는 class형 component을 통해서 이러한 모든 기능들을 사용할 수 있었다. 그러나 class형식의 component는 너무 복잡하고, 무거웠기 때문에 관리가 어렵다는 단점이 있었다.

이에 따라 아무런 기능도 없는 함수형 component를 만들게되고 여기에 기능을 하나씩 추가하기 시작했다. 그 기능을 추가하는 방식이 바로 hook이었다. 이 덕분에 class형 component처럼 모든 기능들을 가진 class를 만들 필요가 없어졌고, 필요한 기능만 hook을 통해 추가하면 되므로 훨씬 더 code가 간결해지고 사용하기 쉬워졌다.

react hook은 react에서 기본적으로 제공하는 hook말고도 개발자가 직접 만들어 사용하는 custom hook도 있다.

custom hook을 사용하는 가장 큰 이유는 반복되는 logic을 단순히 만들기 위한 것이다. 그럼 굳이 custom hook을 만들 필요가 있는가? 그냥 javascript 함수로 만들어 공통으로 사용하면 되지 않을까?? 여기에는 큰 한계가 있는데, 사실 react에서는 react component이외의 일반함수에서 react hooks들의 사용을 금지하고 있다. 가령 다음과 같은 code의 형식이 금지된다고 생각하면 된다.

function update() {
  const isMountRef = useRef(false)

  useEffect(() => {
    if (!isMountRef.current) {
      isMountRef.current = true;
      return;
    }
    console.log("업데이트")
  })
}

update함수를 react component에서 사용하는 것 자체는 문제가 없을 것이다. 그러나, 이는 엄연히 추천되지 않은 문법이며 어떠한 결과를 낳을 지 모르기 때문에 사용이 금기시 되어있다.

따라서 react hooks는 다음과 같은 곳에서만 호출이 가능하다고 생각하면 된다.
1. component 내부
2. 또 다른 react hook(custom hook) 포함

따라서 반복되는 hook logic들이 있다면 custom hook을 만들어 로직을 분리하면 된다.

custom hook을 만드는 방법은 매우 간단한데, 똑같이 javascript 함수를 만들되 앞에 접두사로 use~를 써주면 된다.

function useUpdate(cb) {
  const isMountRef = useRef(false)

  useEffect(() => {
    if (!isMountRef.current) {
      isMountRef.current = true;
      return;
    }
    cb()
  })
}

아주 간단하다. 사용하는 곳에서도, 쉽게 호출하면 된다.

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

  const mount = () => {
    console.log("mount")
  }

  useUpdate(mount)
  ...
}

이렇게 custom hook을 통해서 react component의 logic을 단순화하고 code를 clean하게 만들어주는 것이다.

0개의 댓글