https://www.udemy.com/course/react-next-master/
react의 component들도 lifecycle을 갖는데, 다음의 3단계를 가진다.
Mount -> Update -> Unmount
이렇게 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("업데이트")
})
조금 복잡해보일지도 모르는데, 동작 원리를 알면 굉장히 간단하다. useRef
는 current
라는 property에 값을 저장할 수 있는 hook
이다. 이는 리렌더링이 발생해도 current
에 이전에 저장한 값을 저장하고 있는데, 마치 pointer나 reference 객체를 생각하면 된다.
때문에 처음에는 current
로 false
를 저장하고 있지만 처음 useEffect
가 실행되면서 isMountRef
의 current
를 true
로 바꾸게 된다. 이후에는 isMountRef
의 current
값이 변경되는 일이 없으므로 오직 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함수가 실행된다.
그러나, 두 번째 인자가 []
이므로 rerendering될 때는 실행되지 않는다. 따라서, 오직 unmount될 때만 실행되는 것이다.
함수 컴포넌트에서 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하게 만들어주는 것이다.