7장
고차 함수
고차 함수는 다른 함수와 컴포넌트를 인자로 전달받거나 리턴하는 함수
컴포넌트 사이의 공통 로직을 분리하고 재사용하기 위해 리액트 훅과 고차 함수를 사용할 수 있음.
==> 컴포넌트를 인자로 받아서 새로운 컴포넌트를 반환하는 함수
고차 함수 예제
import { useEffect, useState } from 'react'
import DateAndTIme from 'date-and-time'
export enum TimeFormatEnum {
HHmmss = "HH:mm:ss",
HHmm = "HH:mm",
HHmmKOR = "HH시 mm분",
HHmmssKOR = "HH시 mm분 ss초",
}
export const connectClockTime = (
TargetComponent: React.ComponentType<any>, // TargetComponent 고차 함수가 감쌀 원래 컴포넌트
timeFormat: TimeFormatEnum,
interval: number
) => {
return (props: any) => { // 반환되는 새 컴포넌트
// 실시간 시간 기능이 추가됨 (currentTime)
// TargetComponent 를 감싸서 시계 기능을 넣어준 것
const [currentTime, setCurrentTime] = useState<string>(DateAndTime.format(new Date(), timeFormat));
// useEffect.. 컴포넌트가 마운트된 후 실행되는 훅
useEffect(() => {
const handle = window.setInterval(() => {
setCurrentTime(DateAndTime.format(new Date(), timeFormat));
}, interval);
// 클린업 함수.. 언마운트될 때나 다음 실행 전에 호출
return () => {
window.clearInterval(handle);
};
}, []); // depsList 이 빈 배열이라 마운트 시 한 번만 실행됨
// 고차 함수를 쓸 때는 원래 컴포넌트가 받던 props를 그대로 전달해야 안전함
return <TargetComponent {...props} currentTime={currentTime} />;
};
};
{...props} 반드시 기존 컴포넌트가 사용하는 속성을 그대로 다시 전달하기 위해 작성해야 한다.
마우스 위치를 획득하는 고차 함수 예제
src/connectMousePos.tsx
import { useEffect, useState } from 'react'
export type PositionType = {
x: number;
y: number;
};
export const connectMousePos = (TargetComponent: React.ComponentType<any>) => {
return (props: any) => {
const [position, setPosition] = useState<PositionType>({ x: 0, y: 0 })'
useEffect(() => {
const onMove = (e: MouseEvent) => setPosition({ x: e.pageX, y: e.pageY });
window.addEventListener("mousemove", onMove);
}, []);
return <TargetComponent {...props} position={position} />;
};
};
반드시 스프레드 연산자를 사용해야 하는 것은 아니지만
원래 컴포넌트가 받던 props를 모두 안전하게 전달하려면 쓰는 게 좋다.
위에서 정의한 두 고차 함수를 사용하는 src/Child.tsx 컴포넌트
import { connectClockTime, TimeFormatEnum } from './connectClockTime'
import { connectMousePos, PositionType } from './connectMousePos'
type PropsType = {
currentTime: string;
position: PositionType;
};
const Child = (props: PropsType) => {
return (
<div>
<h2>고차 컴포넌트 사용</h2>
<div>현재 시각: {props.currentTime}</div>
<hr />
<div>
마우스 위치 : {props.position.x}, {props.position.y}
</div>
</div>
);
};
export default connectMousePos (
connectClockTime(Child, TimeFormatEnu,.HHmmssKOR, 5000)
);
대상 컴포넌트에서 여러 개의 고차 함수를 사용하기 위해 한 고차 함수의 리턴 값(새로운 기능이 추가된 컴포넌트)을 다시 고차 함수 인자로 전달한다.
src/App.tsx 고차 함수가 적용된 Child 컴포넌트를 컴포넌트 트리에 포함하도록 작성.
import Child from './Child'
const App = () => {
return (
<div>
<h2>고차 함수 테스트</h2>
<hr />
<Child />
</div>
);
};
export default App;
클래스 컴포넌트의 공통 로직을 분리하는 경우라면 고차 함수를 사용할 수 밖에 없지만 함수 컴포넌트를 사용한다면 사용자 정의 훅을 작성하여 공통의 로직을 분리하는 것이 권장됨.
* 함수 컴포넌틍에서 고차 함수 방식 권장하지 않는 이유
- 한 컴포넌트에 여러 고차 함수를 적용할 때 동일한 이름의 속성을 사용하고 있으면 충돌
- 인자로 전달되는 컴포넌트의 속성이 무엇일지 알 수 없으므로 암묵적으로 any 타입을 사용할 수 밖에 없음. 즉, 타입스크립트와 같은 정적 타입 언어를 적용할 때 어려움이 있다.
export const connectMousePos = (TargetComponent: React.ComponentType<any>) => {
}
React.memo 고차 함수
리액트 라이브러리로 기본 제공되는 고차 함수
컴포넌트가 동일한 상태나 속성을 가지고 있다면 얕은 비교를 수행하도록 하여 불필요한 렌더링을 방지한다. p253
제공 기능은 PureComponent와 유사
PureComponent와 마찬가지로 React.memo 고차함수도 불변성을 가진 상태의 변경이 필수적임
const Child = (...) => {
...
}
export default React.memo(Child);
또는
const Child = React.memo( (...) => {
...
}
export defult Child;
고차함수 적용
// TodoListItem 컴포넌트의 마지막 행을 다음과 같이 변경
export default React.memo(TodoListItem);
// TodoList 컴포넌트의 마지막 행을 다음과 같이 변경
export defualt React.memo(TodoList)
React.memo 고차 함수 적용 결과
p269
8장
Context API
컴포넌트 트리에서 속성을 전달하지 않고 필요한 데이터를 컴포넌트에 전달하는 방법을 제공하는 API
React의 Context를 하위 컴포넌트에게 데이터를 공급하는 컴포넌트
Provider을 이용해 공유하는 데이터를 제공.
데이터를 필요로 하는 컴포넌트와 useContext 훅 이용해 데이터에 접근한다. 이 구조로 사용하면 속성 이용한 전달을 반복하지 않아도 됨.
사용 단계
1. Context 객체가 관리할 데이터의 타입을 정의
2. React.createContext() 함수 이용해 Context 객체 생성
const TodoContext = React.createContext<TodoListContextValueType | null>(null);
첫 번째 null: 타입스크립트를 위한 것
두 번째 null: React의 런타임 동작을 위한 초기값..
null을 허용하는 이유는 Context 생성할 때 null로 초기화 하기 때문
3. 상태와 상태 변경 함수를 관리할 Provider 컴포넌트 작성
return (
<TodoContext.Provider value={value}>
{props.children}
</TodoContext.Provider>;
)
{value}는 하위에서 읽을 context 값
4. 자식 컴포넌트에서는 useContext 훅을 이용해 데이터 객체(value) 리턴 받아서 상태와 상태 변경 함수 사용 p275
const values = useContext(TodoContext);
9장
리액트 라우터
라우터는 SPA를 쉽게 작성할 수 있게 함.
라우터는 요청 경로에 따라 화면을 쉽게 전환할 수 있는 기능을 제공한다.
SPA(단일 페이지 애플리케이션)는 하나의 HTML 페이지로 여러 개의 화면을 전환할 수 있음.
SPA는 하나의 HTML 페이지에서 요청된 URI 경로를 이용해 화면 전환을 하기 때문에 화면 전환을 위해 웹 서버로부터 새로운 페이지를 로딩하지 않음.
화면 전환에 필요한 모든 코드는 첫 화면 로딩 시 한꺼번에 서버에서 로딩한다.
URL이 아니라 URI인 이유
위치 의미가 아니라 어떤 컴포넌트를 렌더링할지 구분하기 위한 식별자로써 경로 사용
<Link to={이동시킬 경로}>글자..</Link>
<Routes>
<Route path="/" element={<Home />}>
</Routes>
Link to="/home"이면 localhost:8080/home 이런 식으로 이동함. 링크가 /home으로 이동 <a href="">와 비슷
라우터에 쓰이는 path는
Link에서 home으로 이동했을 때 그 경로에서 보여줄 화면임.
라우터의 경로는 화면을 보여주기 위함. URI와 비교해서 렌더링을 결정
중첩 라우트 (nested route)
상위(부모) 경로까지 매칭
<Route /> 컴포넌트에 의해 렌더링된 컴포넌트에 기존 Route의 중첩된 <Route />의 컴포넌트가 나타나도록 구성
한 페이지 안에서 또다른 하위 경로 렌더링
/songs로 요청: SongList 컴포넌트 렌더링
/songs/:id 로 요청: SongList 컴포넌트와 Player 컴포넌트 렌더링
...
<Route path="/songs" element={SongList songs={songs} />}>
<Route path=":id" element={<Player songs={songs} />} />
</Route>
index 라우트
특정 부모 라우트가 렌더링될 때, 하위 경로가 따로 지정되지 않은 경우 기본으로 보여줄 화면
부모 경로까지만 매칭되는 경우에도 자식 컴포넌트를 렌더링 가능
<Route path="/parents" element={ <Parent />}>
<Route index element={ <DefaultChild> } />
<Route path=":param" element={ <Child1 /> } />
</Route>
parents로 요청: Parent, DefaultChild 컴포넌트 렌더링
parents/:param으로 요청: Parent, Child1 컴포넌트 렌더링
useMatch 훅
현재 요청된 URI 경로가 인자로 전달한 경로 패턴과 매칭하는지 확인하고 pathMatch 객체를 리턴.
// 경로 패턴에는 <Route /> 컴포넌트의 path 속성에 지정하던 경로 형태를 전달한다.
const pathMatch = useMatch(경로패턴);
params: URI 경로 파라미터
pathname: 요청된 경로
pattern: 요청된 경로 패턴
import { Link, Outlet, useMatch } from 'react-router-dom'
...
const SongList = (props: Props) => {
// 현재 경로가 /songs/:id 패턴과 일치하는지 확인
const pathMatch = useMatch("/songs/:id");
// URL에서 id를 꺼내서 number로 변환 (없으면 -1)
const param_id: number = pathMatch?.param?.id ? parseInt(pathMatch.params.id, 10) : -1;
// props 로 전달된 노래 리스트를 렌더링
const list = props.songs.map((song) => {
const cn = "list-group-item";
cn += param_id === song.id ? " list-group-item-secondary" : "";
return (
<li className={cn} key={song.id}>
...
</li>
);
};
...
};
export default SongList;
useSearchParams 훅
요청 시 전달하는 쿼리 문자열 정보를 읽어내거나 실행하는 기능
// searchParams 쿼리 문자열을 읽을 수 있는 전용의 객체
// ?a=1&b=2 와 같이 요청한 경우 searchParams.get("a")와 같이 값에 접근할 수 있다.
// setSearchParms 쿼리 문자열을 설정할 수 있는 기능을 제공하는 함수
// setSearchParams({ a:3, b:4}) 와 같이 설정할 수 있다.
const [searchParams, setSearchParams] = useSearchParams();
쿼리 문자열 ex. /products?page=2&keyword=apple&sort=price
사용자의 선택이나 입력값을 URL에 반영해서 공유/유지하고 싶을 때 쓰인다.
useNavigate 훅
useNavigate 훅을 호출하면 URI 경로 이동할 수 있는 navigate 함수가 리턴됨
// navigate(to, options)
// to: 이동하려는 경로
// options: 경로 이동 시 지정 옵션
const navigate = useNavigate();
options.. 인자에서 사용할 수 있는 속성
1. replace: 내부적으로 이용하는 브라우저 히스토리의 현재 항목을 교체할 것인지 true/false로 지정. false가 기본값.
2. state: 네비게이션할 때 전달할 상태 정보
경로 이동이 완료된 후 location 객체의 state 속성을 이용해 접근 p320
ex.
/a/b/c/d
navigate("/e", {replace: false}) => /a/b/c/d/e
navigate("/e", {replace: true}) => /a/b/c/e
useLocation
경로 이동 후 state 정보를 액세스하려면 useLocation 훅을 사용
const location = useLocation();
pathname: 현재 요청된 경로
search: 쿼리 문자열
state: navigate()로 이동할 때 전달된 상태 정보
useOutletContext
부모-자식 라우트 간 데이터 공유를 쉽게 하기 위해 사용
중첩된 라우터 사용할 때 상위 경로의 <Outlet /> 컴포넌트를 이용해 상태 정보를 저장해 두고 하위 경로에서 접근할 수 있는 기능을 제공한다.
사용 방법
1. 상위 라우트가 렌더링하는 컴포넌트에서 상태 또는 속성을 <Outlet /> 컴포넌트의 context에 지정하여 전달.
2. 중첩 라우트의 자식 컴포넌트에서 useOutletContext 훅 이용해 context 객체 받아서 사용.
// 상위 라우트 컴포넌트에서 상태를 context로 전달하는 경우
const parentComponent = () => {
const [title, setTitle] = React.useState("Hello, React!");
return (
...
<Outlet context={ {title} } />
...
)
};
export default Home;
// 중첩 라우트 컴포넌트에서 sueOutletContext 훅으로 context 객체를 이용하는 경우
type ContextStateType = { title: string; };
const childComponent = () => {
const { title } = useOutletContext<ContextStateType>();
...
}
상위 라우트에서 <Outlet context={...}>로 상태나 데이터 던지고
하위 라우트에서 useOutletContext()로 가져 옴
Router 컴포넌트
- Browser Router
HTML5 History API를 사용하여 URI와 UI를 동기화한 상태를 유지할 수 있는 기능
URI 경로를 사용해 브라우저의 주소를 저장하고 브라우저 history 객체의 스택을 사용해..
- Hash Router
URI의 해시 정보를 이용해서
URI 경로와 UI를 동기화한 상태를 유지하....
- Memory Router
애플리케이션의 메모리 영역에 배열을 만들어 라우팅 정보를 저장하고 UI와 동기화 시킴
URI 경로가 브라우저 주소창에...
- 브라우저에서 일반 SPA → BrowserRouter
- 서버 설정 못 하는 정적 호스팅 → HashRouter 주소창에 # 포함.. fallback UI를 지원하지 않으면 HashRouter 사용하면 됨 에러 안 남
- 테스트/모바일/주소창 없는 환경 → MemoryRouter
404 라우트
경로가 * 임
<Route path="*" element={ <Not Found /> } />
NavLink 컴포넌트
현재 요청된 경로와의 일치 여부에 따라 각기 다른 스타일을 부여할 수 있는 Link 컴포넌트
Link 컴포넌트와 비교
// Link 컴포넌트
<Link className="btn btn-success menu" to="/about">
About
</Link>
NavLink 컴포넌트
// style 동적으로 부여
<NavLink to="/blog" style={ ({ isActive }) => {
return isActive ? activeStyle : undefined
}}>
Blog
</NavLink>
// className에 동적으로 부여
<NavLink to="/catalogs" className={({ isActive }) => {
return isActive ? activeClassName : undefined
}}>
Catalogs
</NavLink>
레이지 로딩
SPA의 문제점.. 빌드된 파일은 모든 컴포넌트를 묶은 것으로, 파일의 크기가 크다. -> 화면 로딩 시간이 길어짐
이를 해결하기 위한 것이 레이지 로딩 기법
레이지 로딩 기법은 특정 화면이 필요할 때 관련된 컴포넌트들을 포함하고 있는 JS 파일을 웹 서버에 요청하여 받아오는 것
적용 방법
import Home from './Home'
const Home = React.lazy(()=> import("/Home"));
Home은 실제로 필요할 때 로딩 -> 별도의 chunk 파일 생성.
빌드 후 /dist/main.js 외에 다른 chunk 파일 생성 가능
chunk 파일.. 빌드된 것 중 JS 코드가 분리되어 생성된 파일
즉, 애플리케이션 전체 코드를 한 파일로 만들지 않고 작은 파일들로 나눈 것.
나누는 이유
: 페이지 로딩 속도 향상, 필요한 코드만 로딩하므로 초기 로딩 속소 최적화, 브라우저가 한 번에 처리하는 JS 용량 감소
이 JS 중에서도 코드 스플리팅으로 이루어진 조각들이 chunk이다.
Suspense 컴포넌트
리액트에서 비동기적으로 로딩되는 컴포넌트를 처리할 때 사용하는 컴포넌트
비동기를 다루기 위함
컴포넌트가 비동기를 기다리는 동안 로딩 상태를 깔끔하게 처리할 수 있게 도와줌.
청크 파일을 필요로 할 때 로딩 시 지연이 발생할 수 있음. 지연 시간 동안 보여줄 UI(fallback UI)를 지정할 수 있게 해 준다.
fallback UI 속성: 로딩 중 표시할 UI 지정
레이지 로딩과 함께 사용 React.lazy로 동적 import 한 컴포넌트를 감싼다.
훅
: 함수형 컴포넌트 안에서 상태나 생명 주기 같은 기능을 사용할 수 있게 하는 것
Produce immer 라이브러리 함수
: 직접 상태를 변경하지 않으면서, 변경된 새 상태를 생성
불변성을 쉽게 유지하면서 상태 업데이트 가능
작동 원리
import produce from 'immer';
const nextState = produce(baseState, draft => {
draft.push(...); // draft를 마음껏 수정
});
baseState.. 원본 상태
recipe.. 초안(draft) 받아서 변경하는 부분. proxy 객체는 감싸진 가짜복사본이다.
draft 복사본.. draft.push(...) 할 일 추가
1. produce가 baseState를 proxy 객체(draft)로 감쌈.. draft를 수정하면, 실제 원본은 변경되지 않고 proxy가 기록
2. recipe 함수 안에서 draft를 수정
3. recipe 종료 후 immer가 변경 사항을 적용한 새로운 객체 생성
4. 원본 상태(baseState)는 그대로 유지, 새로운 상태를 반환
여기서 사용되는 proxy란?
draft를 감싸고 있는 가짜 객체
draft를 수정하면 proxy가 내부에서 어떤 속성이 바뀌었는지 추적, 최종적으로 변경된 부분만 반영한 새 객체를 생성한다. 원본은 수정되지 않음
-> proxy는 draft를 감싸서 수정을 감시하는 역할
11장
모크
: 실제 객체를 만들어서 테스트하기 힘든 경우 의존하는 모듈 컴포넌트, API를 테스트 모형으로 만든 것.
모크를 만드는 것을 '모킹 한다.'라고 함
오리진
오리진은 웹페이지에서 '출처'를 의미
1. 프로토콜 http, https
2. 도메인 google.com
3. 포트 80
이 3가지가 같아야 같은 오리진임. 1바이트만 달라도 다른 오리진
크로스 오리진
: 현재 웹 페이지의 오리진과 요청을 보내는 서버의 오리진이 다를 때 생기는 상황
크로스 오리진 문제는 웹 브라우저에 내장된 SOP(Same Origin Policy) 보안 정책 때문에 발생
다른 오리진으로 요청을 보내면 기본적으로 차단됨
크로스 오리진 문제 해결 방법
1. 백엔드 API 서버 측에서 CORS(Cross Origin Resource Sharing) 기능 제공
2. 프론트엔트 애플리케이션을 호스팅 하는 웹 서버에 프록시를 설정하는 방법
CORS (백엔드 측에서 제공해야 함)
: 브라우저가 다른 오리진에 요청을 보낼 때 서버가 이를 허용할지 여부를 HTTP 헤더로 정의하는 정책
- Access-Control-Allow-Origin: 허용할 오리진
- Access-Control-Allow-Methods: 허용할 HTTP 메서드
- Access-Control-Allow-Headers: 허용할 요청 헤더
프록시를 이용한 우회 (프엔에서 해결 가능)
: 브라우저가 백엔드 API 서버와 직접 통신하는 것이 아니라
프론트엔드 애플리케이션을 호스팅 하는 서버(프런트 서버)에 프록시를 설치하여 프런트 서버의 프록시를 거쳐 백엔드 API 측과 통신하게 하여 브라우저 측에서는 오리진과 통신하도록 함
프록시: 클라이언트(브라우저)와 서버 사이에서 요청과 응답을 대신 전달해주는 중간 서버
[브라우저] - [프록시 서버] - [실제 서버]
->>브라우저는 프록시 서버에만 요청, 프록시 서버가 백엔드 서버와 통신, 응답을 브라우저로 전달
'React' 카테고리의 다른 글
| [React] 리액트 개념 정리 #6 (0) | 2025.10.29 |
|---|---|
| [React] 리액트 개념 정리 #5 (0) | 2025.10.28 |
| [React] 리액트 개념 정리 #3 (0) | 2025.10.26 |
| [React] 리액트 개념 정리 #2 (0) | 2025.10.26 |
| [React] 리액트 개념 정리 #1 (0) | 2025.10.26 |