1. Hook ?
hook은 함수 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수이다.
hook은 class안에서는 동작하지 않는다.
대신 class 없이 React를 사용할 수 있게 해주는 것이다.
React는 useState 같은 내장 hook을 몇가지 제공해준다.
컴포넌트 간에 상태 관련 로직을 재사용하기 위해 hook을 직접 만드는 것도 가능하다.
2. state Hook (useState)
우리가 어떤 작업을 할 때 변수를 정말 많이 다룬다.
리액트에서도 작업을 하게 된다면 분명 변수는 필수적으로 사용하게 되어있다.
그런 변수들을 조금 더 쉽게 다룰 수 있도록 도와주는 hook이 있는데 그게 바로 state Hook이다.
state Hook 즉 useState는 "state 변수"를 선언할 수 있다.
import React, { useState } from 'react';
function Example() {
// 새로운 state 변수를 선언하고, 이것을 count라 부르겠습니다.
const [count, setCount] = useState(0);
위 선언한 변수는 count라고 부르지만 banana처럼 아무 이름으로 지어도 된다.
일반적으로 일반 변수는 함수가 끝날 때 사라지지만, state 변수는 react에 의해 사라지지 않는다.
useState()의 인자로 넘겨주는 값은 state의 초기값이다.
이 인자는 숫자 타입이나 문자 타입을 가질 수 있다.
useState가 반환하는 것은 두 가지 쌍이다.
하나는 state 변수, 다른 하나는 해당 변수를 갱신할 수 있는 함수이다.
공식 문서에서는 아래처럼 정의하는 것을 추천한다.
const [count, setCount] = useState(0);
예제
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위 코드는 button을 누르면 count가 1씩 증가한다.
초기 값을 여러개 설정할 수 도 있다.
function Box() {
const [position, setPosition] = useState({ left: 0, top: 0 });
const [size, setSize] = useState({ width: 100, height: 100 });
useEffect(() => {
function handleWindowMouseMove(e) {
setPosition({ left: e.pageX, top: e.pageY });
}
// ...
3. Effect Hook (useEffect)
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업을 이전에도 종종해봤을 것이다.
react에서는 이러한 모든 동작 모드를 side effects라고 한다.
왜냐하면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문이다.
Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해준다.
useEffect를 사용하면, React는 DOM을 바꾼 뒤에 "effect" 함수를 실행할 것이다.
Effects는 컴포넌트 안에 선언되어있기 때문에 props와 state에 접근할 수 있다.
기본적으로 React는 매 렌더링 이후에 effects를 실행한다.
쉽게말하면 React는 effect가 수행되는 시점에 이미 DOM이 업데이트 되었음을 보장한다.
그럼 이를 어디에 사용하냐 ?
예를 들어 api를 업로드할 때 딱 한 번만 호출해야할 때 사용할 수 있다.
import { useEffect, useState } from 'react'
function App() {
const [counter, setValue] = useState(0);
const onClick = () => setValue((prev) => prev + 1);
const api = () => console.log("Call the API - not useEffect ....")
api()
useEffect(()=> {
console.log("Call the API ....");
}, []);
return (
<div>
<h1>{counter}</h1>
<button onClick={onClick}>button</button>
</div>
)
}
export default App
이 코드를 실행해 보면 아래처럼 된다.
하지만 여기서 버튼을 계속 누른다면?
이렇게 useEffect가 아닌 그냥 밖에서 호출한 함수는 state가 변경될 때마다 계속호출이된다.
따라서 처음에 딱 한 번만 호출하고자 한다면 useEffect안에서 호출해주면 된다.
1) 건너뛰기
여기서 더 나아가서 특정 값이 변할 때 effect 기능을 사용하고 싶다면 useEffect의 두 번째 argument를 사용해주면 된다.
예를 들어 지역이 바뀔때 만 API를 호출하고자 한다면 아래처럼 해주면 된다.
import { useEffect, useState } from 'react'
function App() {
const region = ["서울", "부산", "광주", "울산", "대구", "대전", "인천"]
const [counter, setValue] = useState(0);
const [district, setDistrict] = useState()
const onClick = () => setValue((prev) => prev + 1);
const changeDistrict = () => setDistrict((prev) => region[(region.indexOf(prev) + 1)%region.length])
const api = () => console.log("Call the API - not useEffect ....")
api()
useEffect(()=> {
console.log("Call the API ....");
}, [district]); // district가 변경될 때만 useEffect 동작
return (
<div>
<h1>{counter}</h1>
<h2>{district}</h2>
<button onClick={onClick}>button</button>
<button onClick={changeDistrict}>change district</button>
</div>
)
}
export default App
useEffect의 두 번째 argument는 "deps" 이다.
즉 이 argument가 존재하고 그 값이 변할 때만 effect가 동작하게된다.
쉽게 말해 useEffect( 함수 , [values])에서 values가 변할 때만 useEffect가 동작하게 된다.
이 배열안은 "or" 이 적용된다.
예를들어 useEffect( 함수 , [나이, 성별, 키]) 이렇게 작성하면
나이 or 성별 or 키 값이 변동 될때만 useEffect가 동작한다.
주의 : 만약 useEffect( 함수 ) 이렇게만 한다면 일반 함수와 같이 리렌더링 될 때마다 호출된다. 꼭 뒤에 빈 배열이라도 넣어줘야한다.
그렇게해야 처음 컴포넌트가 생성되었을 때만 호출된다.
ex) useEffect( 함수, [])
2) clean-up
clean-up은 effect를 위한 추가적인 정리 메커니즘이다.
모든 effect는 clean-up을 위한 함수를 반환할 수 있다.
즉 useEffect를 호출한 컴포넌트가 제거될때 무언가를 호출할 수 있다.
import { useEffect, useState } from 'react'
function Subscription() {
useEffect(()=> {
console.log("구독 신청");
return () => console.log("구독 취소")
}, []);
}
function App() {
const [counter, setValue] = useState(0);
const [subscription, setSubscription] = useState(false)
const onClick = () => setValue((prev) => prev + 1);
const changeSubscriptionStatus = () => setSubscription((prev) => !prev)
return (
<div>
<h1>{counter}</h1>
<h2>{subscription ? "구독 중" : "구독 중 아님"}</h2>
<h2>{subscription ? <Subscription /> : null}</h2>
<button onClick={onClick}>button</button>
<button onClick={changeSubscriptionStatus}>구독</button>
</div>
)
}
export default App
위 코드에서 구독중이 아닐 때는 Subscription 컴포넌트가 사라진다.
그때 Subscription 컴포넌트 안에 있는 useEffect의 return이 동작한다.
이 return에는 함수만 작성할 수 있다.
이렇게 useEffect하나의 hook가지고 구독 신청과 취소 로직을 작성할 수 있다.
3) 읽어볼 것
종속성 목록에서 함수 컴포넌트를 생략하는 것이 안전합니까?
함수 컴포넌트가 props, state 또는 파생된 값을 참조하지 않는 경우에만 종속성 목록에서 함수 컴포넌트를 생략하는 것이 안전하다.
잘못된 예
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId); // productId props 사용
const json = await response.json();
setProduct(json);
}
useEffect(() => {
fetchProduct();
}, []); // 🔴 `fetchProduct`가`productId`를 사용하므로 잘못되었습니다
// ...
}
권장하는 예
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
// 이 함수 컴포넌트를 effect 내부로 이동하면 사용하는 값을 명확하게 볼 수 있습니다.
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
setProduct(json);
}
fetchProduct();
}, [productId]); // ✅ 효과는 productId 만 사용하므로 유효합니다
// ...
}
4. Hook 규칙
1. 최상위 (at the Top Level)에서만 Hook을 호출해야 한다.
반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하면 안된다.
이 규칙을 따라야 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것을 보장한다.
조건문을 사용하고 싶다면 조건문 안에 hook을 호출하는 것이아닌 hook 내부에서 조건문을 작성해야 한다.
// 잘못된 예
// 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
// 올바른 예
useEffect(function persistForm() {
// 👍 더 이상 첫 번째 규칙을 어기지 않습니다
if (name !== '') {
localStorage.setItem('formData', name);
}
});
2. 오직 React 함수 내에서 Hook을 호출해야 한다.
이 두가지 규칙을 강제하는 ESLint 플러그인이 있다. (Create React App에 기본적으로 포함되어 있다)
npm install eslint-plugin-react-hooks --save-dev
// ESLint 설정 파일
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
'React-JS > 개념' 카테고리의 다른 글
[React] styled-compoents (1) | 2023.02.20 |
---|---|
[React] react / react-dom (0) | 2023.02.16 |
[React] Function and Class Components (0) | 2023.02.11 |
[React] create react app (0) | 2023.02.10 |
[React - 개념] Props (0) | 2023.02.09 |