React 16.8에 새로 추가된 기능, react 모듈에 선언되어있는 함수
class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있도록 해줌
useState,useEffect를 포함한 hooks는
실행 될 때 마다 dispatcher를 선언하고 useState 메소드 실행해서 그 값을 반환
할당부를 거슬러 올라가니 dispatcher는 전역 변수 ReactCurrentDispatcher로부터 가져온다
함수가 선언부보다 상위에 있는 값에 접근 -> Closure
(객체가 생성된 이후 그 상태를 변경할 수 없음)
state를 갱신하는 것은 병합하는 것이 아니라 대체하는 것
변경하려는 상태가 여러개일때 : 배열이나 객체는 참조자료형이므로 객체를 복사하여 새로운 객체를 생성한 후 변경
Object.assign()/ 스프레드 연산자(...)
두 방법 모두 완전한 복사(Deep copy)를 지원하지 않아서 객체 내부의 객체는 얕은 복사(Shallow copy)
1: import React, { useState } from 'react';
2:
3: function Example() {
4: const [count, setCount] = useState(0);
5:
6: return (
7: <div>
8: <p>You clicked {count} times</p>
9: <button onClick={() => setCount(count + 1)}>
10: Click me
11: </button>
12: </div>
13: );
14: }
//첫 번째 줄: useState Hook을 React에서 가져옴
//네 번째 줄: useState Hook을 이용하면 state 변수와 해당 state를 갱신할 수 있는 함수가 만들어짐
//또한, useState의 인자의 값으로 0을 넘겨주면 count 값을 0으로 초기화할 수 있다.
//아홉 번째 줄: 사용자가 버튼 클릭을 하면 setConut 함수를 호출하여
//state 변수를 갱신.
//React는 새로운 count 변수를 Example 컴포넌트에 넘기며 해당 컴포넌트를 리렌더링
웹이 로딩되고 최초로 App함수가 호출
App은 인수로 0을 전달하며 useState를 호출합니다.
useState는 실행될 때 마다 초기값을 전달받지만, 내부적으로 _value값이 undefined인지 확인해서, 최초의 호출에만 초기값을 _value에 할당하고, 이후 초기값은 사용되지 않습니다.
이후 _value와 그 값을 재할당하는 setState 함수를 배열에 담아 반환합니다.
setState 호출
전달 받은 값 1을 react 모듈 상단의 _value에 할당합니다.
컴포넌트 리렌더링을 trigger합니다.
setState가 실행되어 리렌더링이 발생
위에서 리렌더링 과정에서 해당 컴포넌트 함수가 실행되고, 새로운 jsx를 반환한다고 정리했습니다.
setState가 리렌더링을 트리거하며 App함수가 두 번째로 실행되었을 때
다시 인수 0을 useState에 전달하며 호출
useState는 내부적으로 _value값을 확인하고, undefined가 아닌 값이 할당되어 있기 때문에 초기값 할당문을 실행하지 않습니다.
이후 useState가 현재 시점의 _value와 setState를 반환합니다. (이 시점에서 _value는 1입니다.)
두 번째 실행된 App 함수 내부에서 useState가 반환한 값을 비구조화 할당으로 추출해 변수에 할당.
-> setState 함수는 자신과 함께 반환된 변수를 변경시키는게 아니라(const), 다음 useState가 반환할 react 모듈의 _value를 변경시키고, 컴포넌트를 리렌더링 시키는 역할.
변경된 값은 useState가 가져옴
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업 등
Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해줌
컴포넌트 내부에 둠으로써 effect를 통해 state 변수(또는 그 어떤 prop에도)에 접근가능(closure)
컴포넌트 생성 후 처음 화면에 렌더링(표시)
컴포넌트에 새로운 props가 전달되며 렌더링
컴포넌트에 상태(state)가 바뀌며 렌더링
Hook을 쓸 때 주의할 점
최상위에서만 Hook을 호출
React 함수 내에서 Hook을 호출
(종속성 배열 내의)어떠한 값의 변경이 일어날 때만 effect를 발생시킬 수 있다.(함수실행)
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.
위의 예시에서 우리는 [count]를 두 번째 인수로 넘깁니다. 이것이 의미하는 바는 다음과 같습니다. 만약 count가 5이고 컴포넌트가 리렌더링된 이후에도 여전히 count는 변함없이 5라면 리액트는 이전 렌더링 시의 값 [5]를 그다음 렌더링 때의 [5]와 비교합니다. 배열 내의 모든 값이 같기 때문에(5 === 5) 리액트는 effect를 건너뛰게 됩니다. 이런 식으로 최적화가 가능합니다.
count가 6으로 업데이트된 뒤에 렌더링하면 리액트는 이전에 렌더링된 값 [5]를 그다음 렌더링 시의 [6]와 비교합니다. 이때 5 !== 6 이기 때문에 리액트는 effect를 재실행합니다. 배열 내에 여러 개의 값이 있다면 그중의 단 하나만 다를지라도 리액트는 effect를 재실행합니다.
빈 배열을 useEffect의 두번째 인자로 사용하면, 컴포넌트가 처음 생성될때만 effect 함수가 딱 한번 실행 (effect를 실행하고 이를 정리(clean-up)하는 과정(마운트와 마운트 해제))
-> 처음 단 한번, 외부 API를 통해 리소스를 받아오고 더이상 API 호출이 필요하지 않을 때
effect 안의 prop과 state는 초깃값을 유지
mount
unmount
//컴포넌트가 최초 화면에 렌더링 될 때만 실행
useEffect(() => {
console.log("컴포넌트가 화면에 나타남"); // mount
}, []);
//컴포넌트가 사라질 때 실행
useEffect(() => {
console.log("컴포넌트가 화면에 나타남"); // mount
return () => {
console.log("컴포넌트가 화면에서 사라짐"); // unmount
//콜백에 리턴을 해주면 사라질 때 실행됩니다.
};
}, []);
기본 형태의 useEffect, 컴포넌트가 처음 생성되거나, props가 업데이트되거나, 상태(state)가 업데이트될 때 effect 함수가 실행됨
-> 어떤 작업을 하던 컴포넌트의 변화를 감지하고 컴포넌트 전체를 다시 렌더링