기존의 함수 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있도록 지원한다. 여기서 자세한 사항을 확인할 수 있다.
가장 기본적인 훅이며 함수 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해준다.
// Say.js
import React, { useState } from "react";
const Say = () => {
// useState에는 함수의 초기값을 설정한다. 값은 자유롭게 설정할 수 있으며 함수 호출 시 배열이 반환된다. 첫번쩨 원소는 현재 상태이고 두 번째 원소는 상태를 바꿔주는 함수이다.
const [message, setMessage] = useState("");
const onClickEnter = () => setMessage("hello");
const onClickLeave = () => setMessage("bye");
return (
<div>
<h1>{message}</h1>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
</div>
);
};
export default Say;
리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있다. 클래스형 컴포넌트의 componentDidMount
와 componentDidUpdate
를 합친 형태라고 생각해도 좋다.
맨 처음 렌더링 될 때만 실행하고 업데이트될 때는 실행하지 않으려면 함수의 두 번쩨 파라미터에 비어있는 배열을 넣는다.
useEffect(() => {
console.log("마운트될 때만 실행합니다.");
}, []);
두 번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣는다.
useEffect(() => {
console.log(name);
}, [name]);
컴포넌트가 사라질 (언마운트) 때나 업데이트 전에 clean up function인 return()함수를 사용한다.
useEffect(() => {
console.log(name);
return () => {
console.log("clean up");
};
}, [name]);
useState
보다 다양한 컴포넌트 상황에 따라 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 훅으로 리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수이다. 반드시 불변성을 지켜야 한다.
리덕스에서 사용하는 액션 객체에는 type 필드가 반드시 있어야 하지만 useReducer
에서 사용하는 액션 객체는 선택적이다. 객체가 아니라 문자열이나 숫자여도 된다.
function reducer(state, action) {
// action.type 에 따라 다른 작업 수행
switch (action.type) {
case "INCREMENT":
return { value: state.value + 1 };
case "DECREMENT":
return { value: state.value - 1 };
case "ADD": // 이 부분은 {type: 'ADD'} 가 들어오면 실행
return [...state, action.data];
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
}
훅 사용 시 state 값과 dispatch 함수를 받아오며 여기서 state는 현재 가리키고 있는 상태이며 dispatch는 액션을 발생시키는 함수이다.
const [state, dispatch] = useReducer(리듀서 이름, 초기 데이터);
import { userReducer } from "../reducers/userReducer";
function UserList() {
const [state, dispatch] = useReducer(userReducer, userData);
return (
<>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button
onClick={() =>
dispatch({ type: "ADD", data: { id: 11, name: "11", email: "11" } })
}
>
Add userlist
</button>
</>
);
}
함수 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다.
export default React.memo(TodoListItem);
플젝 레포에서 확인할 수 있다.
useMemo
와 비슷한 함수로, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 이 훅을 사용하면 특정 함수를 새로 만들지 않고 재사용할 수 있다.
// 첫 번째로 생성하고 싶은 함수를, 두 번째 파라미터로 함수를 새로 생성하기 위한 조건값인 배열을 넣는다
const onInsert = useCallback(() => {
setNumber("");
}, [number]); // number가 바뀌었을 때만 함수 생성
const onChange = useCallback((e) => {
// const onChange = e => { setNumber(e.target.value); }
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
}, [number, list]);
함수 컴포넌트에서 ref를 쉽게 사용할 수 있게 해준다.
import React, { useRef, useState } from "react";
function Calculator() {
const [resultNum, setResultNum] = useState(0);
const firstNum = useRef(0);
const secondNum = useRef(0);
const sum = () => {
setResultNum(
Number(firstNum.current.value) + Number(secondNum.current.value)
);
};
return (
<div>
<input ref={firstNum} />
<input ref={secondNum} />
<button onClick={sum}>더하기</button>
<p>{resultNum}</p>
</div>
);
}
export default Calculator;
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 currnet 값이 실재 엘리먼트를 가리킨다. 따라서 변수명.current
로 언제든 접근이 가능하다.
컴포넌트 로컬 변수(렌더링과 상관없이 바뀔 수 있는 값)를 사용해야 할 때 useRef를 활용할 수 있다. 다만 ref안의 값이 바뀌어도 컴포넌트가 렌더링 되지는 않는다. 따라서 렌더링과 관련되지 않은 값을 관리할 때 사용한다.
import React, { useRef } from "react";
function Calculator() {
const resultNum = useRef(0);
const firstNum = useRef(0);
const secondNum = useRef(0);
const sum = () => {
resultNum.current =
Number(firstNum.current.value) + Number(secondNum.current.value);
console.log(resultNum.current);
};
return (
<div>
<input ref={firstNum} />
<input ref={secondNum} />
<button onClick={sum}>더하기</button>
<p>{resultNum.current}</p>
</div>
);
}
export default Calculator;
batch 기능이란 State 변경할 함수가 여러개 있을 경우 각각 재렌더링을 시키는 것이 아니라 마지막에 1번 재렌더링을 해주는 기능이다. useTransition
와 useDeferrenedValue
을 통해 느린 컴포넌트의 성능을 향상시킬 수 있다.
import React, { useDeferredValue, useState, useTransition } from "react";
let a = new Array(100).fill(0);
const App = () => {
let [name, setName] = useState("");
// isPending은 startTransition가 처리중일 때 true 값이된다.
let [isPending, startTransition] = useTransition();
// 해당 state 아니면 변수하나를 집어넣을 수 있으며 해당 값을 늦게 처리해준다.
let state = useDeferredValue(state);
return (
<div>
<input
onChange={(e) => {
startTransition(() => {
// 코드의 실행 시점을 늦춘다.
setName(e.target.value);
});
}}
/>
{a.map((arr, i) => {
return <div key={i}>{name}</div>;
})}
</div>
);
};
다른 개발자가 만든 훅을 라이브러리로 설치하여 사용할 수 있다.
공통적으로 사용되는 훅의 경우 커스텀 훅으로 만들어서 사용할 수 있다.
// hooks/useInput.jsx
import { useState } from "react";
function useInput(initialValue) {
const [userInput, setUserInput] = useState(initialValue);
const onChange = (e) => {
const { name, value } = e.target;
setUserInput({ ...userInput, [name]: value });
};
return { userInput, onChange };
}
export default useInput;
// components/Login.jsx
import React from "react";
import useInput from "../hooks/useInput";
function Login() {
const { userInput, onChange } = useInput({ id: "", pw: "" });
const onKeyDown = (e) => {
if (e.key === "Enter") {
login();
}
};
const login = () => {
alert(userInput.id, userInput.pw);
};
return (
<div onChange={onChange}>
<input name="id" placeholder="아이디" />
<input
name="pw"
placeholder="비밀번호"
type="password"
onKeyDown={onKeyDown}
/>
<button onClick={login}>로그인</button>
</div>
);
}
export default Login;
정말 유익한 글이었습니다.