React로 데이터 다루기

이찬영·2024년 12월 15일

React와 JavaScript

useEffect
**하는일** : react 컴포넌트가 렌더링 된 후 실행되어 부수효과(sideEffect)를 처리하는 훅이다. 여기서 부수효과란, 데이터요청, DOM조작, 이벤트등록 등 컴포넌트 내부 외부의 상태나 데이터를 변경하는 작업이다.
**동작원리**
1. Dependency Array를 통해 useEffect의 실행 조건을 제어한다. 의존성 배열이 비어있으면, 컴포넌트가 처음 렌더링 될때만 실행한다. 배열의 값이 있으면 해당하는 그 값이 변경될때 실행된다.
2. 정리함수 (Clean Function)를 통해 이전의 부수효과들을 제거한다. return으로 정리 함수를 반환하여, 컴포넌트가 언마운트 되거나 업데이트 되기 전에 실행한다.

useEffect(() => {
	// 실행할 코드
  	return () => {
    	// 정리할 코드
    }
}, [dependencies]);

useState
하는일 : 컴포넌트에서 상태를 관리하는데 사용하는 훅이다. 상태값과 이를 업데이트하는 함수를 제공한다.
동작원리 :
1. 초기값 설정 : const [state, setState] = useState(initialValue);
state : 현재 상태값 / setState : 상태를 변경하는 함수
2. 이전 상태를 참조하는 경우
setState((prevState) => prevState + 1); 콜백함수를 통해 항상 최신 상태값을 참조한다. (콜백함수의 첫번째 인자는 무조건 setter함수가 실행되기전 값이 온다.)

조건부 렌더링
하는일 : 조건에 따라 다른 UI를 보여주는 방식
UI를 렌더링할 때 불필요한 작업을 줄이고, 조건에 따라 동적으로 변경
동작원리 :

// if 조건문
if(isLoggedIn) {
	return <Welcome />
}
// && 연산자
{isLoggedIn && <Welcome />} // isLogged가 true일때만 <Welcome />이 렌더링

파일 업로드 및 FormData
FormData
하는일 : HTML폼 데이터를 키-값 쌍으로 관리하는 객체이다.
텍스트와 파일 데이터를 함께 전송할 때 유용하다.
동작원리 :
1. new FormData()로 객체 생성
2. 데이터를 append로 추가

const formData = new FormData();
formData.append('file', 파일객체);
  1. 서버로 데이터 전송
axiosClient.post('/upload', formData, {
	headers : {'Content-Type' : 'multipart/form-data'}
});

Ref와 DOM조작
useRef
하는일 :
react에서 특정 DOM요소나 값에 대한 참조를 제공하는 Hook이다. 렌더링 간에 값(상태)을 유지하거나, DOM요소를 직접 조작할 때 사용한다. react에서는 대부분 DOM을 직접 조작하지 않고, 상태(state)를 통해 UI를 제어하지만, 아래와 같이 DOM에 직접 접근해야할 때가 있다.
1. 특정 DOM요소의 크기/위치를 가져오고 싶을때
2. 포커스를 설정하거나 스크롤 동작을 제어할때
3. 외부 라이브러리와 연동할때

import {useRef} from 'react';
function InputFocus() {
	const inputRef = useRef(); // ref생성
  	
  	const handleFocus = () => {
    	inputRef.current.focus(); // DOM 노드에 직접 접근
    };
  return (
  	<div>
    	<input ref={inputRef} type="text" placeholder="클릭하면 포커스"/>
    	<button onClick={handleFocus}>포커스 이동</button>
    </div>
  )
}

원리 :
useRef()로 Ref객체를 생성하고, ref프롭에 inputRef를 전달하여 <input>요소와 연결한다. 버튼 클릭시 inputRef.current.focus()를 호출하여 해당 입력 필드에 포커스를 설정한다.
ref의 주요 특징 :
1. react의 상태(useState)와 달리 Ref의 값이 변경되어도 컴포넌트가 리렌더링 되지 않는다.
2. Ref의 값은 ref.current에 저장되며, 이는 DOM요소나 값을 직접 가리킨다.

useCallback

하는일 : 함수를 메모이제이션 하여 컴포넌트가 불필요하게 재렌더링 되는 문제를 방지한다.

동작원리 :
1. 기본 문법

const memoizedCallback = useCallback(() => {
	// 실행할 그 함수의 로직
}, [dependencies]);
  1. 동일한 의존성을 가지면, 이전에 생성된 함수를 재사용한다.

CustomHook

하는일 : 여러 컴포넌트에서 중복되는 로직을 하나로 묶어 재사용할 수 있는 사용자 정의 Hook이다. React에서 제공하는 기본 hook을 조합해 새로운 기능을 만든다.

동작원리 : 일반함수처럼 정의되지만, 이름이 반드시 use로 시작해야 한다. 내부에서 React Hook을 호출할 수 있고, 반환값을 통해 컴포넌트에서 사용할 데이터와 함수를 제공한다.

특징 :
1. 컴포넌트와 마찬가지로, custom hook에서도 react hook과 같이 최상위에서 호출해야 한다.
2. 재사용이 가능하다. 즉, 비슷한 로직이 반복되는 경우 Custom hook으로 정리하여 가독성과 유지보수성을 높인다.
3. 상태관리와 부수효과를 결합하여, 로직을 하나의 단위로 캡슐화 시킨다.

예시 : 데이터를 서버로 부터 받아올때 사용, 윈도우 크기 관리, 폼 입력관리(여러 컴포넌트에서 폼 입력값을 관리할때 Custom Hook으로 처리)

로딩처리

로딩상태 관리
작업(api호출 등)이 완료될 때까지 사용자에게 로딩상태를 표시한다.

동작원리 :
1. 로딩 상태를 useState로 관리한다.
const [isLoading, setIsLoading] = useState(false);

  1. 작업 시작시, true, 완료시 false로 설정
setIsLoading(true);
//작업수행
setIsLoading(false);

그래서 try, catch, finally문으로 try에 true, finally에 false를 쓴다.

상태에 따라 UI를 조건부 렌더링

{isLoading ? <LoadingSpinner /> : <Content />}

에러처리

하는일 : API호출이나 입력 검증 실패시, 에러 메세지를 사용자에게 보여준다.
동작 원리 :
1. 에러 상태를 useState로 관리
const [error, setError] = useState(null);
2. 작업 중 에러 발생 시 에러메세지 설정

try {
	// 작업수행
}catch(e) {
	setError(e.message);
}
  1. 상태에 따라 에러 메세지 표시
    {error && <p>{error}</p>}

React에서 사이드 이펙트와 정리 함수

사이드 이펙트

하는일 : 함수 내부에서 함수 외부의 값을 변경하거나 부수적인 작업을 실행한다.
예 : DOM조작, 네트워크 요청, 타이머 설정

동작원리 : useEffect를 사용하여 특정 상태나 props에 동기화된 작업을 실행한다.

useEffect(() => {
	document.title = `Count : ${count}`;
}, [count])
정리 함수

하는일 : 컴포넌트가 사라지거나 업데이트 되기 전에 부수효과를 정리한다.
예 : 이벤트 리스너 제거, 타이머 해제
동작원리 :
useEffect의 콜백에서 return으로 정리함수 반환

useEffect(() => {
	const timeId = setInterval(() => {
    	setCount((prevCount) => prevCount + 1);
    }, 1000);
  
  	return () => clearInterval(timeId); // 타이머 정리
}, []);

즉,
1. 처음 실행
useEffect가 처음 실행될때, useEffect의 콜백함수가 실행된다. 이때 return으로 제공된 정리함수는 실행되지 않는다.
2. 의존성값 변경(또는 리렌더링)
의존성배열에 포함된 값이 변경되면, 이전에 실행된 useEffect의 정리함수(return콜백)가 실행된다. 이후 새로운 useEffect의 콜백함수가 실행된다.

알게된것과 트러블슈팅
  1. 리터럴 문법에서의 주의점
    -객체 리터럴 반환 시 소괄호 사용이 필요하다.
setValues((prevValues) => ({
	...prevValues,
  	[name] : value,
}))

이유 : 소괄호로 감싸지 않으면 javascript는 중괄호를 함수의 본문으로 오해한다.

  1. 리액트의 폼 관리
  • 입력 폼의 onChange이벤트
    HTML의 oninput과 유사하지만, react에서는 onChange 프롭을 사용한다.
    입력값 변경 시 핸들러 함수 실행.

  • htmlFor 속성
    react에서 label태그는 for대신 htmlFor속성을 사용해야 한다. for는 javascript의 예약어로 겹칠 수 있다.

  • 기본적인 폼 관리 방법

  1. State생성 : 입력값을 관리하기 위해 State를 정의.
  2. 값 변경 : e.target.value를 이용
  3. 핸들러 연결
    <input name="location" value={values.location} onChange={handleChange} />
  • 폼 값을 객체로 관리
    e.target.name과 e.target.value를 활용해 동적으로 state를 업데이트 한다.
const handleChange = (e) => {
	const {name, value} = e.target;
  	setValues((prevValues) => ({
    	...prevValues,
      	[name] : value,
    }))
}
  • 기본 Submit동작 막기
    기본적으로 form태그는 Submit시 페이지를 새로 고친다.
    e.preventDefault();로 이를 방지할 수 있다.
const handleSubmit = (e) => {
	e.preventDefault();
};

겉보기엔 폼 제출시 페이지가 새로고침 되면서 새로운 아이템이 추가되고 바로 반영될 수 있겠다고 생각할 수 있지만, e.preventDefault()가 중요한 이유가 있다.

  1. 기본동작을 막아야하는 이유 : react의 상태(state)와 UI를 새로고침 없이 동기화 하기 위해.
  2. 막지않으면 어떻게 되는가 : 페이지가 새로고침되어 react의 모든 상태가 초기화 되고, 사용자는 불편함을 겪는다. (UX끊김)
  3. 막으면 어떤게 좋은가 : 새로고침없이 상태 업데이트와 SPA의 부드러운 사용자 경험을 제공한다.
    *SPA : 한 페이지에서 모든 화면 전환이 이루어지고, 새로고침 없이 동적으로 데이터를 업데이트 하는 웹 어플리케이션

제어 컴포넌트
react에서 value를 state로 관리하는 컴포넌트다. 입력값은 react의 상태에 의해 제어되며, 입력값 변경은 이벤트 핸들러를 통해 상태를 업데이트 한다.

동작방식
1. 상태(state)와 입력값 연결
input, textarea, select등 입력 요소의 value속성을 react상태 값으로 설정한다.
2. onChange이벤트로 상태 업데이트 :
입력값이 변경될때 마다 onChange이벤트 핸들러에서 상태를 업데이트한다.

이 방식은 예측 가능성과 다른 컴포넌트와의 연동성이 뛰어나다.

비제어 컴포넌트
value를 react에서 관리하지 않는 경우. DOM에서 직접 값을 가져오거나, 제어하며, react의 상태와 동기화 되지 않는다.

동작방식 :
1. DOM참조
ref를 사용해서 DOM요소를 직접 참조하여 값을 읽거나 설정한다.

  1. react와 독립적으로 동작한다.
    값 변경이 react의 상태와 동기화되지 않는다.

대표적인 예로 파일입력(<input type="file" />)이 있다.

react의 상태 관리와 렌더링
상태 변화로 인한 렌더링
-state또는 props가 변경될때 컴포넌트는 재렌더링된다.

useEffect동작 원리
useEffect는 렌더링 이후 특정 작업을 실행하기 위해 사용한다.
디펜던시 리스트에 포함된 값이 변경될 때만 실행된다.

옵셔널 체이닝
안전하게 객체 속성에 접근하는 문법이다.

const user = {name : 'John', address : {city : 'land'}};
console.log(user?.address?.city); // land출력
console.log(user?.age); // undefined출력

객체가 null이나 undefined일때도 런타임에러없이 undefined를 반환해준다.

배열관련 메서드
forEach : 반환값없이 해당 배열을 순회함
map : 배열을 돌며 반환값이 필요할 때 사용
filter : 연산 후 연산에 올바른것만 배열로 반환함.

React의 props전달

하위 컴포넌트에서 상위 컴포넌트의 state를 변경하려면
1. 상위 컴포넌트에서 state와 setter함수를 생성한다.
2. 상위 컴포넌트가 하위 컴포넌트에서 setter함수를 props로 전달해준다.

기타 메모

React상태관리 : 기존 배열을 직접 변경하지 말고, 무조건 새로운 배열을 생성해야 한다.
로딩상태처리 : 조건부 렌더링으로 스타일을 변경시킬 수 있다.
tip : 빈배열과 빈객체 [], {}는 truthy한 값이다.
button의 타입 : type="submit"일 경우, form태그의 onSubmit이벤트 핸들러가 실행된다.

Context

react Context
기존엔 Props만을 사용해서 데이터들을 공유해왔다. 이렇게 Props만 사용하다 보면, 컴포넌트의 단계가 많을때 여러번 반복해서 Prop을 내려주어야 하는데 (Prop Drilling) Context는 이런 프롭 드릴링을 해결하기 위해 사용하는 기능이다.

Context 만들기

Context는 createContext라는 함수를 통해 만들 수 있다.

import {createContext} from 'react';

const LocaleContext = createContext();

Context 적용하기

Context를 쓸 때는 반드시 값을 공유할 범위를 정하고 써야한다.
이때 범위는 Context객체에 있는 Provider라는 컴포넌트로 정해줄 수 있다. 이때 Provider의 value프롭으로 공유할 값을 내려주면 된다.

import {createContext} from 'react';

const LocaleContext = createContext();

function App() {
	return (
    	<div>
      		/// 바깥의 컴포넌트에선 LocaleContext사용불가
      	</div>
      		<LocaleContext.Provider value={'ko'}>
      			/// Provider안의 컴포넌트에서는 LocaleContext사용가능. 즉, LocaleContext.Provider안에 있는 애들은 value를 넘겨줄 수 있다는 뜻.
      		</LocaleContext.Provider>
    )
}

Context 값 사용하기

useContext라는 Hook을 사용하면 값을 가져와 사용할 수 있다.
이때 아규먼트로는 사용할 Context를 넘겨주면 된다.

import {createContext, useContext} from 'react';

const LocaleContext = createContext();

function Board () {
	const loacle = useContext(LocaleContext); // Provider에 value로 넣어준 부분이 사용가능해짐.
  	return <div>언어 : {locale}</div>
}

function App () {
	return (
    	<div>
      		<LocaleContext.Provider value="en">
      			<Board />
      		</LocaleContext.Provider>
      	</div>
    )
}

State, Hook과 함께 활용하기

Provider 역할을 해줄 컴포넌트를 하나 만들고, 여기서 State를 만들어서 value로 넘겨줄 수 있다. (컴포넌트 디렉토리 말고, Contexts디렉토리 하나 만드는것이 일반적이다.) 그리고 아래의 useLocale같이 useContext를 사용해서 값을 가져오는 커스텀 Hook을 만들수도 있다. 이렇게 하면 Context에서 사용하는 State값은 반드시 우리가 만든 함수를 통해서만 쓸 수 있기 때문에 안전한 코드를 작성하는데 도움이 된다.

import {createContext, useContext, useState} from 'react';

const LocaleContext = createContext({});

export function LocaleProvider({children}) {
	const [locale, setLocale] = useState();
  
  	return (
    	<LocaleContext.Provider value={{
      	locale : locale,
      	setLocale : setLocale,
      	}}>
      	{childrend}
		</LocaleContext.Provider>
    )
}

export function useLocale() {
	const context = useContext(LocaleContext);
  	
  	if(!context) throw new Error('반드시 LocaleProvider안에서 사용해야 합니다.');
  
  	return context.locale;
}

export function useSetLocale() {
	const context = useContext(LocaleContext);
  
  	if(!context) throw new Error('반드시 LocaleProvider안에서 사용해야 합니다.');
  
  	return context.setLocale;
}

0개의 댓글