학습내용
- 라이프사이클
useEffect
에서의 실행 순서useQuery
에서의 실행 순서
체크리스트
특정 컴포넌트의 Mounting, Updating, Unmounting 이 일어나는 시점을 설명할 수 있다.
useEffect / useQuery가 있는 상태에서 리렌더링이 일어날 때의 코드 실행 순서를 설명할 수 있다.
setState 가 비동기적이며 batching 처리되는 개념에 대해 설명할 수 있다.
컴포넌트가 Mount ➡️ Update ➡️ Unmount 되는 과정
- | Mounting | Updating | Unmounting |
---|---|---|---|
시기 | 컴포넌트가 브라우저상에 처음 생성 됐을 때 | New Props, setState(), 강제 업데이트 등의 이유로 컴포넌트가 업데이트 됐을 때 | 컴포넌트가 부라우저상에서 사라졌을 때 |
render | 첫 렌더링 | 리렌더링 | X |
side effect | componentDidMount | componentDidUpdate | componentWillUnmount |
side effect 시기 | mount 직후 | 렌더링 이후 | unmount 직전 |
side effect는 useEffect
의 영향을 받는다.
export default function Home() {
console.log("최상단 - 렌더링 시작");
const [string, setString] = useState("초기값");
const [number, setNumber] = useState(0);
console.log("string:", string);
console.log("number:", number);
return (
<div>
{console.log("return문 실행")}
</div>
);
}
useEffect
가 없는 경우엔 위와 같이 최상단부터 순서대로 코드가 실행된다. 이 과정이 Mounting 내에서 render까지 해당되는 과정이다.
useEffect(()=>{
// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
}, [])
예제 코드에 useEffect
를 적용하면 아래와 같은 순서로 실행된다.
export default function Home() {
console.log("최상단 - 렌더링 시작");
const [string, setString] = useState("초기값");
const [number, setNumber] = useState(0);
console.log("string:", string);
console.log("number:", number);
useEffect(() => {
console.log("componentDidMount");
setString("변경값");
setNumber(100);
console.log("비동기 테스트");
}, []);
return (
<div>
{console.log("return문 실행")}
</div>
);
}
4번까지는 기본 예제와 같다. 5번부터는 최초 렌더링이 직후에 useEffect
가 실행된 부분인데, setState
에서 상태가 변화하였으므로 리렌더링이 일어났다.(=Updating)
batching 처리
setState
함수들이console.log("비동기 테스트")
보다 앞에 있음에도 늦게 실행되는 이유는 비동기 함수이기 때문이다. 비동기 함수는 같은 스코프 내에서 동기적인 코드가 먼저 실행된 후 마지막에 실행된다. 또한 동일한 스코프 내에서 2개 이상의setState
가 존재한다면setState
를 모아서 한 번에 모든 state를 변경한 후 리렌더링을 실행한다. 이것을 batching 처리라고 한다. batching 처리가 일어나지 않는다면setState
가 실행될 때마다 리렌더링이 일어나야 하므로 유저 경험적인 측면에서 바람직 하지 못하다.
useEffect(()=>{
// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
// componentDidUpdate (변경된 state로 컴포넌트 렌더링 된 이후 실행)
}, [state])
componentDidMount의 경우엔 의존성 배열이 비어있기 때문에 한 번만 일어나지만, componentDidUpdate는 의존성 배열의 state
가 변경될 때마다 일어난다. 그러나 state
가 변경되지 않더라도 마운트 직후에는 useEffect
가 무조건 실행되기 때문에 componentDidUpdate에서 componentDidMount와 componentDidUpdate 두 가지 기능을 모두 수행한다.
export default function Home() {
console.log("최상단 - 렌더링 시작");
const [string, setString] = useState("초기값");
const [number, setNumber] = useState(0);
console.log("string:", string);
console.log("number:", number);
useEffect(() => {
console.log("componentDidMount");
setString("변경값");
setNumber(100);
console.log("비동기 테스트");
}, []);
useEffect(() => {
console.log("componentDidMount or componentDidUpdate");
}, [string]);
return (
<div>
{console.log("return문 실행")}
</div>
);
}
7번은 마운트 직후에 실행된 componentDidMount이고 11번은 string
값이 변경되면서 일어난 componentDidUpdate에 해당된다.
useEffect(()=>{
// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
return () => {
// componentWillUnmount (컴포넌트가 사라지기 직전에 실행)
}
}, [])
페이지 이동시 (= 컴포넌트가 사라지기 직전에) 한 번만 실행된다. 즉, 아래 코드에서 페이지 이동시 콘솔창에 componentWillUnmount
만 찍히게 된다.
export default function Home() {
console.log("최상단 - 렌더링 시작");
const [string, setString] = useState("초기값");
const [number, setNumber] = useState(0);
console.log("string:", string);
console.log("number:", number);
useEffect(() => {
console.log("componentDidMount");
setString("변경값");
setNumber(100);
console.log("비동기 테스트");
return () => {
console.log("componentWillUnmount");
};
}, []);
return (
<div>
{console.log("return문 실행")}
</div>
);
}
페이지를 이동한 상태에서 다시 Home 컴포넌트로 뒤로가기 실행시 mount
가 일어난다.
useEffect
는 렌더링 과정에서는 실행되지 않는다. 즉,return
문이 끝난 직후(=렌더링 이후)에 실행되기 때문에return
문 바로 위에 위치하는 것이 좋다.
export default function Home() {
console.log("최상단 - 렌더링 시작");
const { data, isLoading } = useQuery("QueryKey", QueryFn);
console.log("data:", data);
console.log("isLoading:", isLoading);
return (
<div>
{console.log("return문 실행")}
</div>
);
}
QueryFn
함수 실행useQuery
함수 역시 useEffect
와 마찬가지로 렌더링 이후에 실행되기 때문에 2,3번과 같은 결과가 나왔다.
최초의 마운트가 끝나면 useQuery
는 상태를 변경한다. useQuery
안에 setState
가 내장되어 있기 때문이다. 그래서 두 번째 렌더링시에는 7,8번과 같은 결과가 도출된다.
이러한 이유로 return문에서 data.map
을 실행하면 오류가 발생하므로 반드시 옵셔널 체이닝과 함께 사용해야 한다.