useState 📖
가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가집니다.
사용 방법
구조분해할당으로 상태값 `state`와 상태를 변경하는 함수 `setState`를 사용할 수 있습니다.
const [state, setState] = useState(initialState);
함수형 업데이트
기존 업데이트 방식과 `함수형 업데이트 방식`이 있습니다.
// 기존에 우리가 사용하던 방식
setState(number + 1);
// 함수형 업데이트
setState(() => {});
함수형 업데이트 방식을 이용하면 setState `() 안에`함수를 넣을 수 있고 인자로 현재의 state를 가져올 수 있습니다.
두 방식의 차이점
const [state, setState] = useState(0);
onClick={() => {
setState(state + 1);
setState(state + 1);
setState(state + 1);
// 결과 : 1
}}
onClick={() => {
setState((prev) => prev + 1);
setState((prev) => prev + 1);
setState((prev) => prev + 1);
// 결과 : 3
}}
두 결과값이 다르게 나오는 걸 확인할 수 있습니다.
❓왜 다르게 동작할까
일반 업데이트 방식은 setState가 각각 실행되는 것이 아니라 배치(batch)로 처리합니다. 리액트는 명령을 하나로 모아 최종적으로 한 번만 실행을 시킵니다. 그래서 1번만 실행됩니다.
함수형 업데이트 방식은 3번을 동시에 명령을 내리면 명령을 모아 순차적으로 1번씩 실행시킵니다.
ex)
0->1
1->2
2->3
이라는 결과가 나오게 됩니다.
useEffect 📖
리액트 컴포넌트가 렌더링 된 이후 특정 작업을 수행하도록 설정할 수 있는 hook입니다.
사용 방법
컴포넌트가 화면에 렌더링 된 이후 useEffect 첫 번째 인자인 함수에 console.log()가 실행됩니다.
useEffect(() => {
// 이 부분이 실행된다.
console.log("hello useEffect");
});
그러나 아래 코드는 input 태그에 onChange가 발생하면 화면이 리렌더링 되면서 `hello useEffect`를 계속 출력하게 됩니다.
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
useEffect(() => {
console.log("hello useEffect");
});
return (
<div>
<input
type="text"
value={value}
onChange={(event) => {
setValue(event.target.value);
console.log("value => ", value);
}}
/>
</div>
);
}
export default App;
이런 문제를 useEffect의 두 번째 인자인 의존성 배열로 해결할 수 있습니다.
의존성 배열
의존성 배열에 있는 값이 바뀔 때만 useEffect만 실행시킬 수 있습니다.
useEffect(()=>{
// 실행하고 싶은 함수
}, [count])
`빈 배열 일 경우` : 최소 렌더링 이후 한 번만 실행되고, 그 이후로는 실행되지 않습니다.
`배열에 값이 있는 경우` : 의존성 배열에 있는 값이 변하게 되면 useEffect 실행
clean up
컴포넌트가 unmount(=사라질 때) 될 때 동작하는 함수이고 return 부분이 실행이 됩니다.
useEffect(()=>{
return () => {
console.log('hi');
}
}, [])
라이프 사이클
`클래스형 컴포넌트`를 사용했을 때 생애주기와 관련된 여러 메서드가 존재했지만
`함수형 컴포넌트`를 사용할 때는 useEffect를 사용해 핸들링합니다.
useRef 📖
useRef는 저장공간 또는 DOM 요소에 접근하기 위해 사용되는 hook입니다. 특정 DOM 요소에 접근이 가능하고, 리렌더링을 하지 않습니다.
사용 방법
import "./App.css";
import { useRef } from "react";
function App() {
const ref = useRef("초기값");
console.log("ref", ref);
return (
<div>
<p>useRef에 대한 이야기에요.</p>
</div>
);
}
export default App;
console.log(ref)를 출력하게 되면 `current`를 가지고 있는 객체를 출력하게 됩니다.
변경도 가능합니다.
ref.current = "바꾼 값";
console.log("ref 1", ref);
용도
useState와 비슷한 역할을 하지만 state는 변화가 일어나면 리렌더링이 일어나지만 ref에 저장한 값은 렌더링이 발생하지 않습니다.
❗여기서 제어 컴포넌트 & 비제어 컴포넌트 개념이 등장합니다.
`제어 컴포넌트` : React에 의해 값이 제어되는 컴포넌트
`비제어 컴포넌트`: React에 의해 값이 제어되지 않는 컴포넌트
✨제어 컴포넌트 예시
import React, { useState } from 'react';
function Input() {
const [state, setState] = useState(null);
const handleChange = (e) => {
setState(e.target.value);
};
return <input onChange={(e) => handleChange(e)} value={state} />;
}
input의 값을 state로 관리하고 사용자가 값을 입력할 때마다 handleChange를 통해 state 값을 업데이트해주는 코드입니다. 값이 바뀔 때마다 화면이 렌더링 됩니다.
✨비제어 컴포넌트 예시
function Input() {
const ref = useRef(null);
return <input ref={ref} type="text" />;
}
사용자만이 input값을 상호작용할 수 있고 필요한 시점에 이벤트 핸들러를 통해 ref에 저장된 엘리먼트의 값을 가져와 활용합니다. 비제어 컴포넌트는 state로 값을 관리하지 않기 때문에 리렌더링이 발생하지 않습니다.
일반적으로 제어 컴포넌트를 사용하는 것이 좋습니다.
useContext 📖
prop drilling 문제점
부모 -> 자식 컴포넌트로 데이터를 전달할 때 깊이가 너무 깊어지면 데이터 흐름 파악이 어려워지고 오류 추적이 힘들어집니다.
그래서 contextAPI가 등장했고 전역 데이터 관리를 할 수 있습니다.
사용 방법
`FamilyContext.js` 파일을 만들고 createContext를 return 해줍니다.
import { createContext } from "react";
export const FamilyContext = createContext(null); // null은 초기값
`FamilyContext.Provider`로 태그를 만들어 데이터를 전달할 컴포넌트를 감싸줍니다. 그리고 value 속성에는 전달할 데이터 값을 작성해 줍니다.
import React from "react";
import Father from "./Father";
import { FamilyContext } from "../context/FamilyContext";
function GrandFather() {
const houseName = "곰돌이";
const pocketMoney = 10000;
return (
<FamilyContext.Provider value={{ houseName, pocketMoney }}>
<Father />
</FamilyContext.Provider>
);
}
export default GrandFather;
`FamilyContext.Provider`로 감싼 컴포넌트들은 value로 보낸 데이터를 공유해서 사용할 수 있습니다.
context를 사용할 해당 컴포넌트에서 import를 해주고 `useContext()`를 사용해 반환받은 값을 사용합니다.
import { FamilyContext } from '위치 경로';
const data = useContext(FamilyContext);
주의 사항
useContext를 사용할 때 Provider에서 제공한 value가 달라지면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 되기 때문에 렌더링 이슈가 생길 수 있습니다.
Custom hooks 📖
import React from "react";
import { useState } from "react";
const App = () => {
// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [title, setTitle] = useState("");
const onChangeTitleHandler = (e) => {
setTitle(e.target.value);
};
// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [body, setBody] = useState("");
const onChangeBodyHandler = (e) => {
setBody(e.target.value);
};
return (
<div>
<input
type="text"
name="title"
value={title}
onChange={onChangeTitleHandler}
/>
<input
type="text"
name="title"
value={body}
onChange={onChangeBodyHandler}
/>
</div>
);
};
export default App;
위 코드는 input 개수가 증가하면 useState, 이벤트 핸들러도 같이 증가해서 중복이 생기게 됩니다. 그래서 중복되는 코드를 커스텀 훅을 통해 관리할 수 있습니다.
useState와 핸들러를 따로 빼놓고 커스텀 훅을 구성했습니다.
import React, { useState } from "react";
const useInput = () => {
// 2. value는 useState로 관리하고,
const [value, setValue] = useState("");
// 3. 핸들러 로직도 구현합니다.
const handler = (e) => {
setValue(e.target.value);
};
// 1. 이 훅은 [ ] 을 반환하는데, 첫번째는 value, 두번째는 핸들러를 반환합니다.
return [value, handler];
};
export default useInput;
리팩토링 후 동일하게 동작하지만 중복코드를 제거해서 코드의 양도 감소했습니다.
import useInput from "./hooks/useInput";
const [title, onChangeTitleHandler] = useInput();
const [body, onChangeBodyHandler] = useInput();
출처 🏷️
'공부 > react' 카테고리의 다른 글
라이브러리 없이 라우터(Router) 만들기 (0) | 2024.08.27 |
---|---|
React는 Hooks를 배열로 관리하는 이유 (0) | 2024.08.19 |
React 숙련 4 (react-router-dom) (0) | 2024.08.16 |
React 숙련 3 (Memoization) (0) | 2024.08.16 |
React 숙련 1 (styled-components) (0) | 2024.08.16 |
React 입문 6 (DOM, VirtualDOM) (0) | 2024.08.09 |
React 입문 5 (명령형, 선언형) (0) | 2024.08.09 |
React 입문 4 (불변성) (0) | 2024.08.09 |