최근 프로젝트 위주로 개발을 진행했고, 특정 기술만 주로 사용하다보니 시야가 좁아진다는 느낌을 많이 받았습니다. 그래서 이번 기회에 React Hooks부터 JS, HTML 태그들, CSS까지 전체적으로 복습해보며 시야를 넓히는 연습을 해볼려고 합니다. 이 과정을 공유하여 개발을 공부하는 분들에게도 도움이 되었으면 하는 마음에 이렇게 블로그에 남기게 되었습니다!
처음 다루어 볼 녀석은 React의 Hook들입니다. 이 글을 읽고 있는 여러분들도 알고 계시겠지만 Hook이 나오게 된 배경을 아주 간단하게 이야기 하고 바로 본론으로 넘어가겠습니다. 지금부터 편하게 리액트와 훅이라고 부르겠습니당 하핳
리액트는 처음에 컴포넌트를 상속 받는 클래스형으로 컴포넌트로 만들었고, state와 리액트의 라이프 사이클을 이용했습니다. 하지만 매번 컴포넌트를 상속 받아서 컴포넌트를 만들어야 하는 방식은 불편했고, 이에 대한 해결책으로 나온 것이 바로 함수형 컴포넌트입니다. 여기서 문제는 함수형 컴포넌트는 기존 클래스형에서 사용하던 state와 리액트의 라이프사이클을 이용하기에는 어려움이 있었다는 것입니다. 이러한 문제를 해결해 준 주인공이 바로 Hook들입니다. 대표적으로 state를 함수형 컴포넌트에서 사용할 수 있는 useState와 리액트 라이프사이클을 편리하게 이용할 수 있는 useEffect 등이 있습니다.
이제 본격적으로 리액트 훅들에 대해 공부해보도록 하겠습니다.
그 전에 주의할 점 짚고 시작하겠습니다.
- JSX 구문이 컴포넌트를 구분할 수 있도록 컴포넌트 이름의 앞 첫글자는 무조건 대문자로 작성해야한다.
- 컴포넌트에서 return 구문 아래에는 하나의 태그만 존재해야한다.
1. useState
먼저 state란 컴포넌트가 가질 수 있는 상태를 말합니다. useState는 이러한 state를 간편하게 생성하고 값을 설정할 수 있도록 해줍니다.
import {useState} from 'react'
export default function MyComponent () {
// js
const [state, setState] = useState(초기값);
// ts
const [state, setState] = useStatr<타입 지정>(초기값);
// number, string, boolean, number[], string[] 등등
return <></>
}
위와 같이 import를 해주고 초기값과 값을 가지고 있는 state, 값을 설정할 수 있는 setState를 지정해줍니다.
그리고 여기서 중요한 점은 state 값을 변경할 경우 해당 컴포넌트는 재렌더링 된다는 것입니다.
또한 state를 변경할 때는 무조건 setState를 사용해야 합니다. 만약에 state가 배열이나 객체일 경우에는 스프레드 연산자를 이용해 state를 복사한 후 값을 변경하고 복사한 녀석을 setState의 인자값으로 넣어주면 됩니다.
import {useStat} from 'react'
export default function MyComponent () {
const [stateArr, setStateArr] = useState<string[]>(["깁밥", "콩나물", "완두콩"]);
const [state, setState] = useState<string>("");
const deleteState = (state:string) => {
// state 값 복사
let copy:string[] = [...state];
// 삭제할려는 데이터가 아닌 요소만 가진 배열 만들기
const delete:string[] = copy.filter((data)=>{
return data !== state;
})
setState(delete);
}
// 이전 값에 간단한 추가를 하는 경우에는
// setState 내부에서 콜백 함수를 사용하여 편리하게 작업할 수 있다.
const updataData = () => {
setStateArr((prev)=>{
return [state, ...prev];
})
}
return (
<div>
<input type='text' value={state} onChange={(e)=>{setState(e.target.value)}}
<button onClick = {updateData}>업데이트</button>
{stateArr && stateArr.map((state, index)=>{
return <p key={index} onClinck={() => deleteState(state)}>{state}</p>
})}
</div>
)
}
그리고 state에 들어가는 초기값이 어떤 무거운 작업을 시행한 후 반환되는 값이라면, 처음 렌더링 될때 한번만 작업을 시행하는 것이 좋습니다. 하지만 그냥 useState(무거운작업함수) 이렇게 넣어버리면 state가 변경될때마다 무거운 작업이 수행되기 때문에 콜백 함수를 사용하여 처음 렌더링 될때만 state의 초기값을 설정하는 함수를 작동시키는 것이 좋습니다.
import {useState} from 'react'
const havyWork = () => {
console.log('무거운 작업');
return []
}
export default function MyComponent () {
const [state, setState] = useState(() => {
return havyWork
});
return (
<div>
<p> {state} </p>
</div>
)
}
2. useEffect
리액트의 라이프 사이클을 이용할 수 있도록 도와주는 Hook입니다.
리액트의 라이프 사이클을 크게 3개로 나눌 수 있는데 Mount(처음 렌더링) > Update(재렌더링) > Unmount(화면에서 사라짐)입니다. useEffect를 통해 원하는 시점에 원하는 작업을 수행할 수 있습니다.
1. 처음 렌더링 될때
useEffect(()=>{
//원하는 작업
},[])
2. 렌더링이 될때마다 실행
useEffect(()=>{
//원하는 작업
})
3. 특정 state가 변경될 때
useState(()=>{
//원하는 작업
},[특정 값])
여기서 특정 값을 작성하는 배열은 '의존성 배열'이라고 부릅니다. 이러한 useEffect에서의 clean up에 대해 한 번 알아봅시다. clean up은 useEffect를 통해 특정 작업을 한 후 해당 작업에 대한 정리 작업을 실행하는 것을 이야기합니다.
여기서 의존성 배열에 아무것도 존재하지 않는 다면, 컴포넌트가 마운트 됐을때 부수 작업이 실행되고, 언마운트될때 정리 작업이 실행됩니다.
import {useEffect} from 'reat';
export default function Timer () {
useEffect(()=>{
// 부수 작업
const timer = setInterval(()=>{
console.log("타이머 실행 중");
}, 1000);
// useEffect에서 return을 사용하면 해당 컴포넌트가 Unmount 됐을 때 실행이 됩니다.
return () => {
// 정리 작업
clearInterval(timer);
console.log("타이버 종료")
}
},[]);
return (
<div>
<span> 타이머 컴포넌트여 </span>
</div>
)
}
만약에 의존성 배열에 특정 값이 존재한다면, 해당 값이 처음 변경 되었을 때는 부수 작업을 실행하고, 다음 작업부터 기존 state에 대한 정리 작업을 진행한 후 부수 작업을 진행합니다.
그리고 브라우저 종속성이 있는 작업을 진행하는 경우 useEffect로 묶는데 그 이유는 리액트가 가상 DOM을 사용하기 때문에 직접적인 DOM 조작은 렌더링 이후에 이루어져야합니다. useEffect는 렌더링 후에 실행되기 때문에 브라우저 종속성이 있는 작업을 실행할 때 useEffect로 감싸는 것입니다.
useEffect의 실행 시점
- 초기 렌더링 후: 컴포넌트가 처음 렌더링된 직후에 useEffect의 부수 효과 코드가 실행됩니다.
- 리렌더링 후: 의존성 배열에 포함된 값이 변경되어 컴포넌트가 리렌더링될 때, 이전의 정리 함수가 실행된 후에 새로운 부수 효과 코드가 실행됩니다.
- 언마운트 직전: 컴포넌트가 언마운트되기 직전에 useEffect의 정리 함수가 실행됩니다.
3. useRef
ref 객체를 반환해주는 Hook입니다. useRef를 이용해 설정하는 초기값은 ref 객체의 current에 저장됩니다.
이러한 ref는 크게 두 가지 용도로 사용됩니다.
1. 저장 공간
ref의 값을 변경해도 리렌더링 되지 않기 때문에 컴포넌트 내의 변수들의 값을 유지한 채 특정 값을 변경하고 저장하고 싶을 때 유용합니다. 그리고 컴포넌트가 렌더링 되어도 ref의 값은 유지됩니다. 즉, ref의 값은 컴포넌트가 마운트 된 이후 언마운트될 때까지 값이 유지되는 것입니다.
버튼을 누르면 값을 증가시켜 주는 예제를 통해 차이를 비교해보겠습니다.
import {useState, useRef} from 'react';
export default function CountComponent () {
const [state, setState] = useState<number>(0);
const ref = useRef<number>(0)
// 요소를 참조할 경우 const ref = useRef<HTML요소이름Element>(null);
console.log("렌더링 ")
return (
<div>
<p> state: {state} </p>
<p> ref : {ref} </p>
<button onClick = {() => { setState( state + 1) }}> state 값 올리기 </button>
<button onClick = {() => { ref.current = ref.current + 1} }> ref 값 올리기 </button>
</div>
)
}
위의 코드를 실행하고 'state 값 올리기' 버튼을 올리면 "렌더링"이 콘솔 창에 버튼을 누를 때마다 뜨고 변경된 값이 바로바로 화면에 적용되어 보일 것입니다, 하지만 'ref 값 올리기'를 클리하면 렌더링이 일어나지 않기 때문에 ref의 값이 버튼을 클리할 때마다 1씩 증가하고 있지만 그 변화가 화면에 반영되어 나타나지는 않습니다. 이 때 'state 값 올리기' 버튼을 클릭할 경우 증가한 ref의 값도 반영되어 화면에 표시될 것입니다.
때문에 값이 자주 바뀌는 경우 ref를 사용하는 게 성능상 유리할 것입니다. 그리고 변화를 감지해야 하지만 변화를 감지할 때 렌더링을 발생시키지 않아야하는 경우에 사용하기 유용할 것입니다.
2. DOM 요소 접근
ref를 지정한 요소에 접근할 수 있고, ref.current.focus(), ref.current.value, ref.current.style.backgroundColor, ref.current.checked 등 기존 document.querySelector로 불러와서 할 수 있는 작업들을 동일하게 할 수 있습니다.
이런식으로 사용하기 위해선 태그의 ref 속성으로 useRef를 넣어주면 DOM 요소에 쉽게 접근할 수 있는 것입니다.
이러한 ref를 사용하는 다양한 예제를 보면서 이해하면 좋을 것 같습니다.
1. 렌더링 될 때, 특정 input 요소에 포커싱
import { useRef, useEffect } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // input 요소에 포커스를 설정
}, []);
return <input ref={inputRef} type="text" />;
}
2. DOM 요소의 값을 읽고 설정
import { useRef } from 'react';
// input의 value 가지고 와서 설정하기
export default function ReadWriteInput() {
const inputRef = useRef(null);
const handleClick = () => {
alert(inputRef.current.value); // input 요소의 현재 값을 읽음
inputRef.current.value = 'Hello, World!'; // input 요소의 값을 설정
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Show and Set Value</button>
</div>
);
}
// input이 체크되어 있는지 확인하고 변경하기
export default function RadioComponent() {
const radioRef = useRef(null);
const handleClick = () => {
// 현재 체크 상태를 읽음
console.log('Radio button is checked:', radioRef.current.checked);
// 체크 상태를 설정
radioRef.current.checked = !radioRef.current.checked;
};
return (
<div>
<input ref={radioRef} type="radio" name="example" />
<button onClick={handleClick}>Toggle Radio Button</button>
</div>
);
}
3. DOM 요소 스타일 변경
import { useRef } from 'react';
export default function ChangeStyle() {
const divRef = useRef(null);
const handleClick = () => {
divRef.current.style.backgroundColor = 'yellow'; // div 요소의 배경색 변경
};
return (
<div>
<div ref={divRef} style={{ width: '100px', height: '100px', backgroundColor: 'blue' }}></div>
<button onClick={handleClick}>Change Color</button>
</div>
);
}
4. 스크롤 조작
import React, { useRef } from 'react';
export default function ScrollBox() {
const boxRef = useRef(null);
const handleScrollToBottom = () => {
boxRef.current.scrollTop = boxRef.current.scrollHeight; // 스크롤을 가장 아래로 이동
};
return (
<div>
<div
ref={boxRef}
style={{ width: '200px', height: '100px', overflow: 'auto', border: '1px solid black' }}
>
<p>Some content here...</p>
<p>More content here...</p>
<p>Even more content...</p>
<p>And more...</p>
<p>More and more...</p>
<p>Last content...</p>
</div>
<button onClick={handleScrollToBottom}>Scroll to Bottom</button>
</div>
);
}
5. 타미어 및 애니메이션 제어
import React, { useRef, useEffect } from 'react';
export default function TimerComponent() {
const timerIdRef = useRef(null);
useEffect(() => {
// setInterval을 사용하여 1초마다 로그를 찍는 타이머 설정
timerIdRef.current = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => {
// 컴포넌트 언마운트 시 타이머 제거
clearInterval(timerIdRef.current);
};
}, []);
return <div>Timer is running. Check the console for ticks.</div>;
}
이렇게 하는 이유는 컴포넌트가 다시 렌더링되어도 해당 타이머 및 애니메이션이 유지될 수 있도록 하기 위함입니다.
위와 같이 useRef를 이용해 다양한 작업들을 할 수 있습니다. 해당 글을 여기서 마무리하도록 하고 다음 글에 이어서 useContext, useMemo, useCallback, useReducer에 대해 정리하겠습니다.
'React Hooks 다시보기..!!' 카테고리의 다른 글
[React] React Hooks 다시보기..!! (useCallback, useReducer) (1) | 2024.06.03 |
---|---|
[React] React Hooks 다시보기..!! (useContext, useMemo) (0) | 2024.06.02 |