React Hook이란?
- React Hook이란, React 버전 16.8부터 React 요소로 새로 추가되었음.
- Hook이 나오기 전까지는 State 값 접근, lifecycle 구현을 사용하기 위해 Class 컴포넌트 선언을 해줘야했음.
- 또한 Function 컴포넌트는 한번 호출되고 메모리상에서 사라져 State 값 접근과 lifecycle 구현이 불가능.
기존의 class component를 사용하는 React 방식
import "./style";
import { Component, render } from "preact";
export default class App extends Component {
state = {
count: 0
};
modify = (n) => {
this.setState({
count: n
});
};
render() {
const { count } = this.state;
return (
<div>
<div>{count}</div>
<button onClick={() => this.modify(count + 1)}> Increment</button>
</div>
);
}
}
if (typeof window !== "undefined") {
render(<App />, document.getElementById("root"));
}
Hook
- Hook을 사용하면 기존 class 바탕의 코드를 작성할 필요 없이 함수 컴포넌트에서도 state 값에 접근이 가능하고 함수형 프로그래밍으로 다음과 같이 작성 가능해짐.
import React, { Component, useState } from "react";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
return (
<>
{count}
<p></p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
export default App;
Hook의 종류
1. useState(): 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해줌. 해당 함수의 인자에는 상태의 기본값이 들어감.
2. useEffect(): 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있음.
3. useReducer()
- useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용함.
- 현재 state와 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수. 반드시 불변성을 지켜줘야 함.
- 첫번째 인자로 reducer 함수를 받고, 두번째 인자로 해당 리듀서의 기본값을 넣어줌.
- 이 Hook은 배열을 리턴하고, 첫번째 인자가 상태고, 두번째 인자가 dispatch 함수인 길이가 2인 배열을 리턴함.
useReducer 예시
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
function Counter() {
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
dispatch({ type: 'INCREMENT' });
};
const onDecrease = () => {
dispatch({ type: 'DECREMENT' });
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
4. useMemo()
- 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있음.
- 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식.
- 예를 들어 아래 예제에서 users에 변화가 있을 때만 countActiveUsers() 함수가 실행되야하는데, input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여 자원이 낭비됨.
- 이때 useMemo() Hook 함수를 통해 성능을 최적화할 수 있음.
useMemo 코드 예시
import React, { useRef, useState, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = e => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const [users, setUsers] = useState([
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: false
}
]);
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;
5. useCallback()
- useMemo()와 비슷하며, useMemo()를 기반으로 만들어짐. useMemo() 는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용.
- 주로 렌더링 성능을 최적화해야 하는 상황에서 사용. 이벤트 핸들러 함수를 필요할 때만 생성할 수 있음.
- 첫번째 인자에는 생성하고 싶은 함수를 넣고, 두번째 인자에는 배열을 넣는다. 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야함.
- 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 주어야 함.
useCallback 사용 예시
const onChange = useCallback(e => {
const { name, value } = e.target;
setInputs(inputs => ({
...inputs,
[name]: value
}));
}, []);
const onRemove = useCallback(id => {
setUsers(users => users.filter(user => user.id !== id));
}, []);
6. useRef()
- 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 한다.
- useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.
useRef 사용 예시
import React, { useState, useRef } from 'react';
function InputTest() {
const [text, setText] = useState('');
const nameInput = useRef();
const onChange = e => {
setText(e.target.value)
};
const onReset = () => {
setText('');
nameInput.current.focus();
};
return (
<div>
<input
name="name"
onChange={onChange}
value={text}
ref={nameInput}
/>
<button onClick={onReset}>초기화</button>
<div>
<b>내용: </b>
{text}
</div>
</div>
);
}
export default InputTest;
Hook 규칙
1. Top Level에서만 Hook을 호출해야 한다. Hook을 호출하는 순서는 항상 같아야 한다.
- 반복문, 조건문, 중첩된 함수 내에서 Hook을 사용하면 안된다.
- 왜 훅의 호출 순서가 같아야 하는 걸까?
- 리액트가 상태값을 구분할 수 있는 유일한 정보는 Hook이 사용된 순서이기 때문이다. 리액트가 Hook이 호출된 순서에 의존한다는 것이다.
- 예시로 반복문 안에서 Hook을 호출했을 때 반복문이 true라면 괜찮겠지만 값이 false라면 건너뛰게 된다. 이렇게 하면 실행순서가 바뀔 수 있어 오류를 일으킨다.
- 조건문 혹은 반복문을 사용하고 싶을때는 useEffect안에 넣어 사용하면 된다.
- useEffect 내 조건문에서 사용하는 예시
export function RequireLogin(){
const navigate = useNavigate();
let auth = GetStore("user", "user");
useEffect(() => {
console.log("auth::", auth);
if(auth==="123123"){
navigate('/login');
}
}, []);
}
2. React 함수형 컴포넌트 또는 Custom Hook 내부에서만 Hook을 호출해야 한다.
- 일반적인 Javascript 함수에서는 호출하면 안된다.
- 직접 작성한 custom hook에서는 사용이 가능하다.
다음과 같이 useEffect에서 호출하는 비동기 함수 내에서 Hook을 호출하는 것도 가능.
export function RequireLogin(){
const navigate = useNavigate(); // HOOK
let auth = GetStore("user", "user");
const checkAuth = async () => {
let url = 'http://localhost:8000/api_v1/public/login';
await Axios.post(url, {id:"", passwd:""}, {withCredentials: true})
.then((res) => {
if(res){
console.log(res);
navigate('/login'); // HOOK 호출
}
}).catch((e) => {
navigate('/login');
console.log(e);
})
}
useEffect(() => {
console.log("auth::", auth);
checkAuth();
}, []);
}
블록 스코프 안에서는 Hook 사용 불가능!
export default function App(){
return {
<div>
// 불가능
<div>{const [value, setvalue] = useState()}</div>
</div>
}
}
비동기 함수(async 키워드가 붙은 함수)는 콜백함수로 사용할 수 없다.
export default function App(){
// useEffect Hook 내부에, 비동기 함수가 들어가므로 에러 발생
useEffect(async () => {
await Promise.resolve(1)
}, [])
return {
<div>
<div>Test</div>
</div>
}
}
→ 위와 같은 규칙으로 useNavigate와 같은 Hook을 직접적으로 호출하는 것이 아닌 아래와 같이 변후로 선언해서 함수를 호출
import { useNavigate } from "react-router-dom";
const Login = () => {
const navigate = useNavigate();
return(
<div className="login">
<input placeholder="전화번호, 사용자 이름 또는 이메일"/>
<input placeholder="비밀번호/>
<button onClick={() => {navigate("/main");}}>로그인</button>
</div>
)
}
Custom Hook 만들기
- input을 관리하는 코드와 같이 반복되는 로직을 처리해야할 때 Custom Hook을 사용
- 반복되는 로직을 쉽게 재사용이 가능함.
- Custom Hook을 만들기 위해선 use라는 키워드로 시작하는 함수를 만들어야함.
- 해당 함수 내에서 useState, useEffect, useReducer, useCallback 등 Hooks를 사용하여 원하는 기능을 구현하여 함수형 컴포넌트에서 사용할 수 있음.
- 함수형 컴포넌트의 함수 이름은 대문자로 시작해야함.
export const apiGet = async () => {
const navigate = useNavigate(); // 함수형 컴포넌트가 아니므로 호출 불가능
}
export const ApiGet = async () => {
const navigate = useNavigate(); // 함수형 컴포넌트이므로 호출 가능
}
'Web > React' 카테고리의 다른 글
[React] redux-persist를 통한 Store 유지하기 (0) | 2023.01.24 |
---|---|
Redux-toolkit으로 상태관리하기 (0) | 2023.01.24 |