728x90
Function Component |
Class Component |
state 사용 불가 | 생성자에서 state를 정의 |
Lifecycle에 따른 기능 구현 불가 | setState() 함수를 통해 state 업데이트 |
Hooks을 통해 클래스 Component의 기능을 모두 구현 | Lifecycle methods 제공 |
Hook
- 리액트 클래스 Component에서만 가능했던 기능을 따로 작성할 필요 없이 함수 Component에서 사용할 수 있게 도와주는 라이브러리
Hook의 규칙
- Hook은 무조건 리액트 함수 컴포넌트의 최상위 레벨에서만 호출해야 한다.
- 따라서 반복문이나 조건문, 중첩된 함수 안에서 Hook을 호출해서는 안 된다.
- Hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 한다.
- 이렇게 해야 리액트가 다수의 useState()와 useEffect() Hook을 호출해서 컴포넌트의 state를 올바르게 관리할 수 있다.
- 리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.
- 그렇기 때문에 일반적인 자바스크립트 함수에서 Hook을 호출해서는 안 된다.
- eslint-plugin-react-hooks라는 Hook의 규칙을 따르도록 강제해 주는 패키지가 있다.
useState() Hook
- 함수 Component에서는 기본적으로 state를 제공하지 않는데 useState() Hook을 이용하면 state를 사용할 수 있다.
import React, { useState } from "react";
function Counter(props) {
var count = 0;
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button on Click={() => count++}>
클릭
</button>
</div>
);
}
- 위의 코드는 Counter()라는 함수 Component이다.
- Counter() 함수는 버튼을 클릭하면 count 값이 1씩 오르고 현재 count 값을 화면에 출력한다.
- 그런데 위의 코드처럼 count를 함수의 변수로 선언하게 되면 버튼 클릭 시 count 값을 증가시킬 수는 있지만 리렌더링이 일어나지 않아 새로운 count 값이 화면에 출력되지 않는다.
- 따라서 state를 사용해 count 값이 바뀔 때마다 리렌더링이 일어나도록 해야 한다.
- 함수 Component에는 state 기능이 없기 때문에 useState()를 사용하여 state를 선언하고 업데이트해야 한다.
useState() 사용법
const [변수명, set함수명] = useState(초기값);
- 초기값을 넣어 useState를 호출하면 리턴값으로 배열이 나온다.
- 배열 안에는 두 가지 항목이 들어가 있다.
- 첫 번째 항목은 state로 선언된 변수이고, 두 번째 항목은 해당 state의 set 함수입니다.
예시
import React, { useState } from "react";
function Counter(props) {
const [count, setCount] = useState(0); // 변수 각각에 대해 set 함수가 따로 존재한다.
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button on Click={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
- 위의 코드는 useState를 사용하여 count 값을 state로 관리하도록 만든 것이다.
- 버튼이 눌리면 setCount 함수를 호출하여 count에 1을 더한다.
- count 값이 변경되면 Component가 리렌더링 되면서 화면에 새로운 count 값이 표시된다.
- 클래스 Component에서는 setState함수 하나를 이용해 모든 state 값을 업데이트할 수 있지만 함수 Component의 경우에는 변수 각각에 대해 set 함수가 따로 존재한다.
useEffect() Hook
- 리액트의 함수 컴포넌트에서 Side effect를 실행할 수 있게 해주는 Hook이다.
- 리액트에서 Side effect는 효과, 영향이라는 뜻에 가깝다.
- 예를 들면 서버에서 데이터를 받아오거나, 수동으로 DOM을 변경하는 경우를 effect라고 말한다.
- effect라고 부르는 이유는 다른 컴포넌트에 영향을 끼칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문이다. 렌더링이 끝난 이후에 수행되는 작업이다.
- 이러한 작업들은 Main이 Side에서 실행된다는 의미에서 Side effect라고 한다.
- 클래스 Component에서 제공하는 생명주기 함수인 componentDidMount(), componentDidUpdate(), componentWillUnmount()의 기능을 하나로 통합하여 제공한다.
useEffect() 사용법
// 컴포넌트가 마운트 된 이후
useEffect(이펙트 함수, 의존성 배열);
// 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
useEffect(이펙트 함수, []);
// 의존성 베열에 빈 배열을 넣으면 mount와 unmount 시에 단 한 번씩만 실행됨
useEffect(이펙트 함수);
// 의존성 배열 생략 시 컴포넌트 업데이트 마다 실행됨
...
return () => {
// 컴포넌트가 마운트 해제되기 전에 실행됨
...
}
- 의존성 배열이란 useEffect 함수가 의존하는 배열로, 배열 안에 있는 변수 중에 하나라도 값이 변경이 되면 이펙트 함수가 실행된다.
- 기본적으로 이펙트 함수는 Component가 처음 렌더링 된 이후와 업데이트로 인한 리렌더링 이후에 실행된다.
- 만약 이펙트 함수가 mount와 unmount 시에 단 한 번씩만 실행되게 하고 싶다면 useEffect(이펙트 함수, []) 이런 식으로 빈 배열을 넣으면 된다.
- 이렇게 하면 해당 이펙트가 props나 state에 있는 어떤 값에도 의존하지 않는 것이 되므로 여러 번 실행되지 않는다.
- 의존성 배열을 생략하면 Component가 업데이트될 때마다 호출된다.
예시
import React, { useState, useEffect } from "react";
function Counter(props) {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷하게 작동한다.
useEffect(() => {
// 브라우저 API를 사용해서 document의 title을 업데이트한다.
document.title = 'you clicked ${count} times'; // count state에 접근하여 해당 값을 사용
});
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button on Click={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
- 위 코드처럼 의존성 배열 없이 useEffect를 사용하면 리액트는 DOM이 변경된 이후에 해당 effect 함수를 실행하라는 의미로 받아들인다.
- 그래서 기본적으로 Component가 처음 렌더링될 때를 포함해서 매 렌더링 시에 이펙트 함수가 호출된다.
- 위 코드의 경우 처음 mount 되었을 때 실행되고, 그다음부터는 컴포넌트가 업데이트될 때마다 실행된다.
- 또한 effect는 함수 Component 안에서 선언되기 때문에 해당 Component의 props와 state에 접근할 수 있다.
- 위 코드에서는 count라는 state에 접근하여 해당 값이 포함된 문자열을 생성해서 사용한다.
함수 Component에서의 componentWillUnmount() 구현
import React, { useState, useEffect } from "react";
function UseStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
if (isOnline === null) {
return '대기중...';
}
return isOnlinde ? '온라인' : '오프라인';
}
- 위 코드는 서버 API를 사용하여 사용자의 상태를 구독하고 있다.
- useEffect 함수는 Component가 unmount 되면 return 함수를 호출한다.
- Component가 unmount 되면 구독을 해제하는 코드가 리턴에 들어가 있다.
- 결과적으로 useEffect() 함수의 리턴문이 하는 역할은 클래스 Component에서의 componentWillUnmount() 함수가 하는 역할과 동일하다.
function UserStatusWithCounter(props) {
const [sount, setCount] = useState(0);
useEffect(() => {
document.title = '총 ${count}번 클릭했습니다.';
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
}
- 또한 useState(), useEffect() Hook은 하나의 Component 안에 여러 개를 사용할 수 있다.
- 위 코드는 하나의 Component 안에 두 개의 useState()와 useEffect() Hook이 들어가 있다.
useMemo() Hook
- Memoized value를 리턴하는 Hook이다.
Memoization
- 컴퓨터 분야에서 최적화를 위해 사용하는 개념이다.
- 연산량이 많은(비용이 높은) 함수의 호출 결과를 저장해 두었다가 같은 입력값으로 함수를 호출하면 다시 연산하지 않고 이전에 저장한 호출 결과를 반환하는 것을 말한다.
- Memoized value란 이렇게 저장된 호출 결괏값을 의미한다.
const memoizedValue = useMemo(
() => {
// 연산량이 높은 작업을 수행하여 결과를 반환한다.
return computeExpensiceValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
- useMemo() Hook은 파라미터로 memoized value를 생성하는 create 함수와 의존성 배열을 받는다.
- Memoization 개념처럼 의존성 배열에 들어있는 변수가 변했을 경우에만 새로운 create 함수를 호출하여 결괏값을 반환하며, 그렇지 않으면 기존 함수의 결괏값을 그대로 반환한다.
- useMemo() Hook을 사용하면 컴포넌트가 리렌더링 될 때마다 연산량이 많은 작업을 반복하는 것을 피할 수 있다.
주의할 점
- useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행된다. 그렇기 때문에 렌더링이 일어나는 동안 실행되서는 안 될 작업을 useMemo() 함수에 넣으면 안 된다.
- useEffect() Hook에서 실행되어야 할 사이드 이펙트를 예로 들 수 있다.
- 서버에서 데이터를 받아오거나, 수동으로 DOM을 변경하는 작업은 렌더링이 일어나는 동안 실행되어서는 안 되기 때문에 useMemo()가 아닌 useEffect() Hook을 사용해야 한다.
const memoizedValue = useMemo(
() => computeExpensiceValue(의존성 변수1, 의존성 변수2);
);
- 의존성 배열을 넣지 않을 경우 매 렌더링마다 함수가 실행된다. 따라서 useMemoHook()에 의존성 배열을 넣지 않고 실행하는 것은 아무런 의미가 없다.
const memoizedValue = useMemo(
() => {
return computeExpensiceValue(a, b);
},
[]
);
- 의존성 배열이 빈 배열일 경우에는 컴포넌트 마운트시에만 creat 함수가 호출된다. 마운트 이후에는 값이 변경되지 않기 때문에 마운트 시에만 값을 계산할 필요가 있을 경우에 사용하면 된다.
- 사용하는 경우는 드물다.
useCallback() Hook
- useMemo() Hook과 유사하지만 값이 아닌 함수를 반환한다는 차이점이 있다.
- 컴포넌트가 렌더링될 때마다 매번 함수를 정의하는 것이 아니라 의존성 배열이 바뀐 경우에만 함수를 재정의하고 리턴한다.
const memoizedcallBack = usecallBack(
() => {
doSmoething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);
- 두 코드는 동일한 역할을 한다.
- 의존성 배열이 바뀐 경우에만 함수를 재정의한다.
useRef()
- Reference를 사용하기 위한 Hook이다.
- Reference 객체를 반환한다.
Reference
- 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.
refObject.current
- reference에는 current라는 속성이 있는데 현재 참조하고 있는 Element를 의미한다.
const refContainer = useRef(초기값);
- 초기값을 넣으면 해당 초기값으로 초기화된 reference를 반환한다.
- 만약 초기값이 null이라면 current의 값이 null인 reference 객체가 반환된다.
- 이렇게 반환된 객체는 컴포넌트의 라이프 사이클 전체에 걸쳐 유지된다.
- useRef() Hook은 변경 가능한 current라는 속성을 가진 하나의 상자라고 생각하면 된다.
function TextIputWithFocusButton(props) {
const inputElem = useRef(null);
const onButtonClick = () => {
// 'current'는 마운트된 input element를 카리킨다.
inputElem.current.focus();
};
return (
<>
<input ref={inputElem} type="text" />
<button onClick={onButtonClick}>
Focus the input
</button>
</>
);
}
- 이 코드는 UseRef() Hook을 사용하여 버튼 클릭 시 input에 focus를 하도록 하는 코드이다.
- 초기값은 null을 넣었고, 결과로 반환된 inputElem이라는 reference 객체를 input 태그 안에 넣는다.
- 버튼 클릭 시 호출되는 함수에서 inputElem.current를 통해 실제 element에 접근하여 focus 함수를 호출한다.
<div ref={myRef}>
- 리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current 속성에 현재에 해당되는 DOM node를 저장한다.
- ref 속성과 기능은 비슷하지만 useRef() Hook은 클래스의 인스턴스 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있다.
- 이것이 가능한 이유는 useRef() Hook이 일반적인 자바스크립트 객체를 리턴하기 때문이다.
- 그렇다면 개발자가 직접 current 속성이 포함된 자바스크립트 객체를 만들어 사용해도 되는 것 아닌가 하는 생각이 들 수 있다.
- useRef() Hook을 사용하는 것과 직접 current 속성이 포함된 모양의 객체를 만들어 사용하는 것의 차이점은 useRef() Hook은 매번 렌더링 될 때마다 항상 같은 reference 객체를 반환한다는 것이다.
Callback ref
- useRef() Hook은 내부의 데이터가 변경되었을 때 따로 알리지 않기 때문에 current 속성을 변경한다고 해서 리렌더링이 일어나지 않는다.
- 따라서 ref에 DOM node가 연결되거나 분리되었을 경우에 어떤 코드를 실행하고 싶다면 callback ref를 사용해야 한다.
- DOM node의 변화를 알기 위한 가장 기초적인 방법은 callback ref를 사용하는 것이다.
- 리액트는 다른 노드에 연결될 때마다 callback을 호출한다.
function MeasureExample(props) {
const [height, setHeight] = useState(0);
const measureRef = useCallback(node => {
if (node != null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref ={measuredRef}>안녕, 리액트</h1>
<h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
</>
);
}
- 이 코드는 reference를 위해서 useRef() Hook을 사용하지 않고 useCallback() Hook을 사용하는 callback ref 방식을 사용했다. useRef() Hook을 사용하게 되면 reference 객체가 current 속성이 변경되었는지를 따로 알리지 않기 때문이다.
- callback ref 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보를 업데이트할 수 있다.
- 이 예제 코드에서는 h1 태그의 높이값을 매번 업데이트하고 있다.
- useCallback() Hook의 의존성 배열로 비어있는 배열을 넣었는데 이렇게 하면 h1 태그가 마운트, 언마운트 될 때에만 callback 함수가 호출되며 리렌더링이 일어날 때에는 호출되지 않는다.
참고
인프런 - 처음 만난 리액트
728x90
'[FrontEnd] > React' 카테고리의 다른 글
React - Handling Events (0) | 2023.06.17 |
---|---|
React - Custom Hook 만들기 (0) | 2023.06.06 |
React - State & Lifecycle (0) | 2023.06.02 |
React - Components & Props (0) | 2023.06.02 |
React - Rendering Elements (0) | 2023.06.01 |