7.hooks
-hooks?
함수 컴포넌트에서 클래스 컴포넌트의 생명주기나 state 정의, setState 등의 작업을 할 수 있게 나온 것.
hooks의 이름 앞에는 use를 붙인다.
-대표적 hooks
*useState = state를 사용하기 위한 hooks.
const [변수명, set함수명] = useState(초기값);
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>
);
}
사용예제
이때 변수 각각에 대해 set함수가 따로 존재한다.
변수가 count 라면 함수는 setCount, 변수가 max라면 함수는 setMax 와 같이 사용된다.
*useEffect() = 사이드 이펙트(효과, 영향)를 실행하기 위한 hook. 다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문에 이펙트(효과, 영향)이라 부른다. 클래스 컴포넌트의 생명주기 함수 기을 수행한다.
useEffect(이펙트 함수, 의존성 배열);
useEffect(() => {
//컴포넌트가 마운트 된 이후,
//의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을때 실행
//의존성 배열에 빈배열[]을 넣으면 마운트와 언마운트시에 한번만 실행
//의존성 배열 생략시 업데이트마다 실행
return () => {
//컴포넌트 마운트가 해제되기 전에 실행
}
}, [의존성 변수1, 변수2...])
effect 함수가 마운트, 언마운트시에만 한번씩 실행하게 하고 싶다면 의존성 배열란 공란[]으로 넣는다.
의존성 배열은 생략할 수 있으며 이럴경우 업데이트 될 때마다 호출된다.
import React, {useState, useEffect} from "react";
//클릭할때마다 증가
function Counter(props) {
const [count, setCount] = useState(0);
//componentDidMount, DidUpdate와 비슷하게 작동된다.
useEffect(() => {
//브라우저 API를 사용해서 document의 제목을 업데이트
document.title = `You Clicked ${count} times`
});
return(
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={() => setCount(count+1)}>
클릭
</button>
</div>
);
}
사용예제
의존성 배열을 안 넣고 사용한다면 DOM이 변경된 후에 실행하라는 것으로 인식한다. 그래서 렌더링 될 때마다 실행된다.
import React, {useState, useEffect} from "react";
function UserStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
//구독을 해지 > 컴포넌트가 unmount될 때 호출
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
if(IsOnline === null) return '대기중...';
return isOnline ? '온라인' : '오프라인';
}
will unmount 기능 구현 사용 예제
useEffect에서 unmount를 쓸려면 return을 사용하면 된다.
useEffect는 하나의 컴포넌트 내에 여러개를 사용할 수 있다.
*useMemo() = Memoized value를 리턴하는 hooks.
Memoized value? 최적화를 위해서 사용하는 개념 Memoization을 이용하여 연산량이 많이 드는 함수의 호출 결과를 저장해 둔 값을 말한다. 이 값을 추후 같은 입력값을 불렀을 때 등과 같이 사용하면 자원이 적게 사용되는 장점이 있다.
const memoizedValue = useMemo(() => {
//연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수2, 변수2...);
}, [의존성 변수1, 변수,2...]
이때, 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 작업 등은 렌더링이 일어나는 동안 실행되면 안되기 때문에 여기에 사용하면 안된다.
의존성 배열을 넣지 않으면 렌더링이 일어날 때 마다 매번 생성되서 아무런 의미가 없으며 빈 배열[]을 넣게 되면 컴포넌트가 마운트(생성)될 때만 사용된다.
*useCallback() = useMemo와 비슷한 기능이지만 값이 아닌 함수를 반환한다.
const memoizedCallback = useCallback(() => {
doSomething(의존성 변수1, 변수2...)
},[의존성 변수1, 변수2...]
);
여기서 파라미터로 받는 함수를 callback이라고 부른다. 작동 방식은 useMemo와 동일하다.
import {useState} from "react";
function ParentComponent(props){
const [count, setCount] = useState(0);
//재렌더링 될 때마다 매번 함수가 새로 정의됨
const handleClick = (event) => {
//클릭 이벤트 처리
};
return (
<div>
<button onClick={() => {setCount(count + 1);}}>
<count>
</button>
<ChildComponent handleClick={handleClick} />
</div>
);
}
예시처럼 callback을 사용하지 않을 경우 부모 컴포넌트가 다시 렌더링이 될 때마다 매번 자식 컴포넌트도 다시 렌더링 된다.
import {useState} from "react";
function ParentComponent(props){
const [count, setCount] = useState(0);
//재렌더링 될 때마다 매번 함수가 새로 정의됨
const handleClick = useCallback((event) => {
//클릭 이벤트 처리
}, []);
return (
<div>
<button onClick={() => {setCount(count + 1);}}>
<count>
</button>
<ChildComponent handleClick={handleClick} />
</div>
);
}
이처럼 useCallback을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 된다.
*useRef = 레퍼런스(특정 컴포넌트에 접근할 수 있는 객체)를 사용하기 위한 hook.
const refContatiner = useRef(초기값);
초기값을 넣으면 해당 초기값으로 초기화된 레퍼런스 객체를 반환한다. 예를 들어 null이라면 현재 참조 하고 있는 엘리먼트 값current이 null인 레퍼런스 객체가 반환된다. 이 값은 컴포넌트가 언마운트 될 때까지 유지된다.
내부의 데이터가 변경되었을때 별도로 알리지 않기에 current 속성을 변경한다고 재렌더링이 일어나지 않는다.
funtion TextInputWithFocusButton(props){
const inputElem = useRef(null);
const onButtonClick = () => {
inputElem.current.focus();
};
return (
<div>
<input ref={inputElem} type="text"/>
<button onClick={onButtonClick}>
Focus the input
</button>
</div>
);
}
예시 코드
*callback ref = DOM노드의 변화를 알기 위한 가장 기초적인 방법.
-hook의 규칙
1.무조건 최상위 레벨 (리액트 함수 컴포넌트의 최상위 레벨) 에서만 호출해야 한다.
반복문이나 조건문, 중첩된 함수들 안에서는 hook을 호출하면 안되며 hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 한다.
function Mycomponent(props){
const [name, setName] = useState('Inje');
if(name != ''){
useEffect(() => {
...
});
}
...
}
잘못된 사용법
위와 같은 경우 name값이 빈 문자열이 되면 조건문의 값이 false이므로 호출이 되지 않음.
2. 리액트 함수 컴포넌트에서만 hook을 호출해야 한다.
-Custom Hook 만들기
여러 컴포넌트에서 반복적으로 사용되는 로직을 hook으로 만들어 재사용하기 위해 사용자가 직접 만들어서 쓴다.
import React, {useState, useEffect} from "react";
function UserListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
return (
<li style={{color: isOnline ? 'green' : 'black'}}>
{props.user.name}
</li>
);
}
다음과 같은 코드일때 앞에 동일한 코드가 있다고 한다면 중복되는 부분이 있기 때문에 이런 부분은 custom hook으로 빼올 수 있다.
import React, {useState, useEffect} from "react";
function useUserStatus(userId) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ServerAPI.subscribeUserStatus(userId, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(userId, handleStatusChange);
};
});
return isOnline;
}
이걸 사용해 본다면 다음과 같다.
import React, {useState, useEffect} from "react";
//custom hook
function useUserStatus(userId) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ServerAPI.subscribeUserStatus(userId, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(userId, handleStatusChange);
};
});
return isOnline;
}
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>
);
}
여러개의 컴포넌트에서 하나의 custom hook을 사용할 때 컴포넌트 내부에 있는 모든 state와 effects는 분리되어 있고 각 호출 또한 독립적이다.
-hook 사이에서 데이터를 공유하는 방법
const userList = [
{id : 1, name: 'first'},
{id : 2, name: 'second'},
{id : 3, name: 'three'},
];
function ChatUserSelector(props) {
const [userId, serUserId] = setState(1);
const isUserOnline = useUserStatus(userId);
return (
<>
<Circle color = {isUserOnline ? 'green' : 'red'} />
<select value={userId}
onChange={event => setUserId(Number(event.target.calue))}>
{userList.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
))}
</select>
</>
);
}
const [userId, serUserId] = setState(1); // 먼저 state를 통해 값을 가져와서 저장
const isUserOnline = useUserStatus(userId); //위에서 저장된 값을 함수 값에 넣어서 호출
-실습
import React, { useState } from "react";
//custom Hook
//초기 카운트 값을 파라미터로 받아서 카운트라는 이름의
//state를 생성하여 값을 제공하고 증감을 편리하게 할 수 있도록 하는 함수
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;
useCounter.jsx
import React, {useState, useEffect} from "react";
import useCounter from "./useCounter";
const MAX_CAPACITY = 10; //최대 카운트
function Accommodate(props){
const [isFull, setIsFull] = useState(false);
const [count, increaseCount, decreaseCount] = useCounter(0);
useEffect(() => {
console.log("===========");
console.log("useEffect is Called");
console.log(`isFull : ${isFull}`);
}); //의존성 배열 없음
useEffect(() => {
setIsFull(count >= MAX_CAPACITY);
console.log(`Current count value : ${count}`);
}, {count}); //의존성 배열 있음
return (
<div style={{padding : 16}}>
<p>{`총 ${count}명 수용했습니다`}</p>
<button onClick={increaseCount} disabled={isFull}>입장</button>
<button onClick={decreaseCount}>퇴장</button>
{isFull && <p style={{color:"red"}}>정원이 가득찼습니다.</p>}
</div>
);
}
export default Accommodate;
Accommodate.jsx
(index.js 코드는 생략)
이상태로 실행 할 경우 개발자 모드 콘솔창에
useEffect received a final argument that is not an array (instead, received `object`). When specified, the final argument must be an array.
라는 오류코드가 뜬다. 찾아보니 useEffect가 배열[]로 들어가야하는데 string 즉 문자열로 들어가서 나오는 문제이다
Accommodate.jsx로 넘어가 다음과 같이 바꾼다.
...
useEffect(() => {
setIsFull(count >= MAX_CAPACITY);
console.log(`Current count value : ${count}`);
}, [{count}]); //의존성 배열 있음
...
그럼 위와 같은 모습으로 페이지가 뜬다.
구분을 위해 console.log를 조금 수정했다. 위와 같이 첫 페이지를 떴을 때에는 두개의 useEffect가 호출 된 걸 확인할 수 있다.
입장 버튼을 눌러보면 업데이트가 되면서 의존성 배열이 없는 useEffect가 호출 되었고 카운트 값이 변경되었기 때문에 의존성 배열이 있는 useEffect도 호출되는 걸 확인할 수 있다.
가득 채우면 다음과 같다.
원래는 마지막 줄이 나오면 안되서 질문을 해봤더니 의존성 배열에서 []를 쓸 경우 {}를 빼야 했다. 즉
useEffect(() => {
setIsFull(count >= MAX_CAPACITY);
console.log("===========배열있음");
console.log(`Current count value : ${count}`);
}, [count]); //의존성 배열 있음
로 변경을 해야 정상적으로 작동이 된다.
카운트가 변경되어서 의존성 배열이 있는 useEffect가 호출된 뒤에 isFull이 false에서 true로 바뀌었기 때문에 의존성 배열이 없는 useEffect가 한번 더 호출된다.