1. 훅
훅(Hook)
- 원래 존재하는 어떤 기능에 마치 갈고리를 거는 것처럼 끼어 들어가 같이 수행되는 것
- 리액트의 state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만든 것
- 훅의 이름은 모두 use로 시작
2. useState
- state를 위한 훅
- 함수 컴포넌트에서는 기본적으로 state라는 것을 제공하지 않기 때문에 클래스 컴포넌트처럼 state를 사용하고 싶으면 useState() 훅을 사용해야 함
const [변수명, set함수명 ] = useState(초깃값);
- 초깃값을 넣어 useState()를 호출하면 리턴 값으로 배열이 나옴
- 리턴된 배열의 첫 번째 항목은 state로 선언된 변수, 두 번째 항목은 해당 state의 set함수
- 클래스 컴포넌트에서는 setState() 함수 하나를 사용해서 모든 state 값을 업데이트할 수 있었지만 useState()를 사용하는 방법에서는 변수 각각에 대해 set 함수가 따로 존재
ex)
Counter 컴포넌트
- 버튼을 클릭하면 카운트를 하나씩 증가시키고 현재 카운트를 보여줌
- 카운트를 함수의 변수로 선언해서 사용하게 되면 버튼 클릭 시 카운트 값을 증가시킬 수 는 있지만,
재렌더링(Re-rendering)이 일어나지 않아 새로운 카운트 값이 화면에 표시 되지 않음
import React, { useState } from "react";
function Counter(props) {
var count = 0;
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={() => count++}>
클릭
</button>
</div>
);
}
useState()를 사용한 counter 컴포넌트
- useStae()를 사용하여 카운트 값을 state로 관리하도록 만든 것
- 버튼이 눌렸을 때 setCount() 함수를 호출해서 카운트를 1 증가시킴
- count의 값이 변경되면 컴포넌트가 재렌더링되면서 화면에 새로운 카운트 값이 표시됨
import React, { useState } from "react";
function Counter(props) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={()=> setCount(count+1)}>
클릭
</button>
</div>
);
}
export default Counter;
3. useEffect
- 리액트의 함수 컴포넌트에서 사이드 이펙트를 실행할 수 있도록 해주는 훅
- 리액트에서의 사이드 이펙트는 효과, 영향을 뜻하는 이펙트(effect)의미에 가까움
-> 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 등의 작업 등
- 클래스 컴포넌트에서 제공하는 생명주기 함수인 componentDidMount(), componentDidUpdate(), componentWillUnmount()와 동일한 기능을 하나로 통합해서 제공
useEffect(이펙트 함수, 의존성 배열);
- 의존성 배열은 이펙트가 의존하고 있는 배열인데, 배열 안에 있는 변수 중에 하나라도 값이 변경되었을 때 사용
- 이펙트 함수는 처음 컴포넌트가 렌더링된 이후와 업데이트로 인핸 재렌더링 이후에 실행
- 이펙트 함수가 마운트와 언마운트시에 단 한번씩만 실행되게 하고 싶으면, 의존성 배열에 빈 배열([ ])을 넣음
-> 해당 이펙트가 props나 state에 있는 어떤 값에도 의존하지 않는 것이 되므로 여러번 실행되지 않음
- 의존성 배열을 생략하게 되면 컴포넌트가 업데이트될 때 마다 호출됨
- useEffect() 혹은 하나의 컴포넌트에 여러 개를 사용할 수 있음
useEffect(()=> {
//컴포넌트가 마운트된 이후,
//의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행
//의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한번씩만 실행됨
//의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
return () => {
//컴포넌트가 마운트 해제되기 전에 실행됨
..
}
}, [의존성 변수1, 의존성 변수2,...]);
ex)
- 의존성 배열 없이 useEffect()를 사용하면 리액트는 DOM이 변경된 이후에 해당 이펙트 함수를 실행하라는 의미로 받아들임
- 기본적으로 컴포넌트가 처음 렌더링될 때를 포함해서 매번 렌더링될 떄마다 이펙트가 실행됨
- 이펙트는 함수 컴포넌트 안에서 선언되기 때문에 해당 컴포넌트의 props와 state에 접근할 수도 있음
import React, { useState, useEffect } from "react";
function Counter(props) {
const [count, setCount ] = useState(0);
//componentDidMount, componentDidUpdate와 비슷하게 작동
useEffect(()=> {
//브라우저 API를 사용해서 doucment의 title을 업데이트
document.title = `총 ${count}번 클릭했습니다.`;
});
return (
<div>
<p>총 { count }번 클릭했습니다.</p>
<button onClick={()=> setCount(count+1)}>
클릭
</button>
</div>
);
}
export default Counter
ex)
- componentWillUnmount() 기능 구현
- useEffect()에서 ServerAPI를 사용하여 사용자의 상태를 구독하고 있음
이후 함수 하나를 리턴하는데 해당 함수 안에는 구독을 해지하는 API를 호출하도록 되어 있음
- useEffect()에서 리턴하는 함수는 컴포넌트가 마운트 해제될 때 호출됨
import React, { useState, useEffect } from "react";
function UserStatus(props) {
const [isOnline, setIsOnline ] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.inOnline);
}
useEffect(()=>{
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
if(isOnline === null) {
return '대기중...';
}
return isOnline ? '온라인' : '오프라인';
}
export default UserStatus
4. useMemo 와 useCallback
메모이제이션
- 최적화를 위해 사용하는 개념
- 비용이 높은 함수의 호출 결과를 저장해 두었다가, 같은 입력값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해놨던 호출 결과를 바로 반환
- 메모이제이션이 된 결괏값을 Memoized value라고 함
useMemo
- Memoized value를 리턴하는 훅
- 파라미터로 Memoized value를 생성하는 create 함수와 의존성 배열을 받음
- 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 create 함수를 호출하여 결괏값을 반환
그렇지 않은 경우에는, 기존 함수의 결괏값을 그대로 반환
const memoizedValue = useMemo (
() => {
//연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
- useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행됨
-> 렌더링이 일어나는 동안 실행돼서는 안될작업을 useMemo() 함수에 넣으면 안됨
(ex. 서버에서 데이터를 받아옴)
- 의존성 배열을 넣지 않을 경우 렌더링이 일어날 때마다 매번 함수가 실행됨
const memoizedValue = useMemo(
() => computeExpensiveValue(a,b)
);
- 의존성 배열에 빈 배열을 넣게 되면 컴포넌트 마운트 시에만 함수가 실행
useCallback
- useMemo()와 유사한 역할을 하나 값이 아닌 함수를 반환
- 함수와 의존성 배열을 파라미터로 받음
- useCallback() 훅에서는 파라미터로 받는 함수를 콜백(callback)이라고 부름
- useCallback() 훅을 사용하지 않고 컴포넌트 내에 함수를 정의한다면 매번 렌더링이 일어날 떄 마다 함수가 새로 정의됨
- useCallback() 훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하도록 해서 불필요한 반복 작업을 없애줌
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
5. useRef
- 변경 가능한 .current라는 속성을 가진 하나의 상자
- 레퍼런스(Reference)를 사용하기 위한 훅
- 레퍼런스 = 특정 컴포넌트에 접근할 수 있는 객체
- useRef 훅은 레퍼런스 객체(ref object)를 반환
- 레퍼런스 객체의 .current 속성
-> 현재 레퍼런스하고 있는 엘리먼트
- 파라미터로 들어온 초깃값으로 초기화된 레퍼런스 객체를 반환
- 반환된 레퍼런스 객체는 컴포넌트의 라이프타임 전체에 걸쳐서 유지됨
const refContainer = useRef(초깃값);
ex)
- useRef() 훅을 사용하여 버튼 클릭 시 <input>에 포커스를 하도록 함
- 초깃값으로 null을 넣었고 결과로 반환된 inputElem이라는 레퍼런스 객체를 <input>태그에 넣어줌
- 버튼 클릭 시 호출되는 함수에서 inputElem.current를 통해 실제 엘리먼트에 접근하여 focus() 함수를 호출
import { useRef } from "react";
function TextInputWithFocusButton(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() 훅은 매번 렌더링될 때마다 항상 같은 ref 객체를 반환
- useRef() 훅은 내부의 데이터가 변경되었을 때 별도로 알리지 않음
-> .current 속성을 변경하는 것은 재렌더링을 일으키지 않음
-> ref에 DOM node가 연결되거나 분리되었을 경우 어떤 코드를 실행하고 싶다면 callback ref를 사용해야 함
6. 훅의 규칙
1) 훅은 무조건 최상위 레벨(Top Level)에서만 호출해야 함
- 반복문이나 조건문 또는 중첩된 함수들 안에서 훅을 호출하면 안됨
- 훅은 컴포넌트가 렌더링 될 떄마다 매번 같은 순서로 호출되야 함
2) 리액트 함수 컴포넌에서만 훅을 호출해야 함
- 일반적인 자바스크립트 함수에서 훅을 호출하면 안됨
- 훅은 리액트 함수 컴포넌트에서 호출하거나 직접 만든 커스텀 훅(Custom Hook)에서만 호출 가능
7. 커스텀 훅
- 커스텀 훅을 만드는 이유는 여러 컴포넌트에서 반복적으로 사용되는로직을 훅으로 만들어 재사용하기 위함
1) 커스텀 훅을 만들어야 하는 상황
UserStatus 컴포넌트
- isOnline State에 따라 사용자의 상태가 온라인인지 아닌지를 텍스트로 보여주는 컴포넌트
import React, { useState, useEffect } from "react";
function UserStatus(props) {
const [isOnline, setIsOnline ] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.inOnline);
}
useEffect(()=>{
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
if(isOnline === null) {
return '대기중...';
}
return isOnline ? '온라인' : '오프라인';
}
export default UserStatus
UserListItem 컴포넌트
- 연락처 목록을 제공
- 이때 온라인인 사용자의 이름은 초록색으로 표시
import React, { useState, useEffect } from "react";
function UserListItem(props) {
const [isOnline, setIsOnline ] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.inOnline);
}
useEffect(()=>{
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
return (
<li style={{color:isOnline ? 'green' : 'balck'}}>
{props.user.name}
</li>
);
}
export default UserListItem
- UserStatus와 useState(), useEffect() 훅을 사용하는 부분이 동일, 중복된 코드
2) 커스텀 훅 추출하기
- 중복되는 로직을 useUserStatus() 커스텀 훅으로 추출
useUserStatus 훅
- 사용자의 온라인 / 오프라인 상태를 구독
- 해당 사용자가 온라인인지 오프라인인지의 상태를 리턴
import React, { useState, useEffect } from "react";
function useUserStatus (props) {
const [isOnline, setIsOnline ] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.inOnline);
}
useEffect(()=>{
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
return isOnline;
}
export default useUserStatus
3) 커스텀 훅 사용하기
- 커스텀 훅을 사용하여 코드 변경
function UserStatus(props) {
const isOnline = useUserStatus(props.user.id);
if(isOnline === null) {
return '대기중...';
}
return isOnline ? '온라인' : '오프라인';
}
function UserListItem(props) {
const isOnline = useUserStatus(props.user.id);
return (
<li style={{color:isOnline ? 'green' : 'black'}}>
{props.user.name}
</li>
);
}
- 커스텀 훅은 단순히 state와 연관된 로직을 재사용이 가능하게 만든 것
-> 커스텀 훅을 사용하는 컴포넌트들은 state 를 공유하지 않음
-> 여러 개의 컴포넌트에서 하나의 커스텀 훅을 사용할 떄에 컴포넌트 내부에 있는 모든 state와 effects는 전부 분리되어 있음
8. 실습 - 훅을 사용한 컴포넌트 개발
useCounter 커스텀 훅 | - 카운트 기능 훅 |
Accommodate 컴포넌트 | - useCounter() 훅을 사용하는 사람 수용 관리 컴포넌트 - 최대 수용 인원을 초과하면 경고 문구 표시 |
useCounter.jsx
- 초기 카운트 값을 파라미터로 받아서 count라는 이름의 state를 생성하여 값을 제공하고 카운트 증가 및 감소를 편리하게 할 수 있도록 함수를 제공하는 훅
import React from "react";
import { useState } from "react";
function useCounter(initialValue) {
const [count, setCount] = useState(initialValue);
const increaseCount = () => setCount((count) => count+1);
const decreaseCount = () => setCount((count) => Math.max(count-1),0);
return [count,increaseCount, decreaseCount];
}
export default useCounter;
Accomodate.jsx
- useCounter() 훅을 사용하여 카운트를 관리
- 의존성 배열이 없는 useEffect() 훅
-> 컴포넌트가 마운트된 직후에 호출되며 이후 컴포넌트가 업데이트 될때마다 호출
- 의존성 배열이 있는 useEffect() 훅
-> 컴포넌트가 마운트된 직후에 호출되며, 이후 count 값이 바뀔때 마다 호출됨
-> 용량이 가득 찼는지 아닌지의 상태를 isFull이라는 state에 저장
import React, { useState, useEffect } from "react";
import useCounter from "./useCounter";
const MAX_CAPCITY = 10;
function Accommodate(props) {
const [isFull, setIsFull] = useState(false);
const [count, increseCount, decreaseCount] = useCounter(0);
useEffect(() => {
console.log("====================");
console.log("useEffect() is called");
console.log(`isFull: ${isFull}`);
});
useEffect(() => {
setIsFull(count >= MAX_CAPCITY);
console.log(`Current count value: ${count}`);
},[count]);
return (
<div style={{padding:100}}>
<p>{`총 ${count} 명 수용했습니다.`}</p>
<button onClick={increseCount} disabled={isFull}>
입장
</button>
<button onClick = {decreaseCount}>퇴장</button>
{isFull && <p style={{color:"red"}}>정원이 가득찼습니다.</p>}
</div>
);
}
- 두 개의 useEffect() 훅이 호출되고 카운트 값 증가
- 의존성 배열이 없는 useEffect() 훅은 컴포넌트가 업데이트됐기 때문에 호출됨
- 의존성 배열이 있는 useEffect() 훅은 count 값이 변경되었기 떄문에 호출됨
'react' 카테고리의 다른 글
[소플] ch 9. 조건부 렌더링 (1) | 2023.10.28 |
---|---|
[소플] ch 8. 이벤트 핸들링 (0) | 2023.10.28 |
[소플] ch 6. state와 생명 주기 (0) | 2023.10.27 |
[소플] ch 5. 컴포넌트와 Props (0) | 2023.10.27 |
[소플] ch4. 엘리먼트 렌더링 (1) | 2023.10.27 |