React 개발환경 설정하기: 린팅, 포맷팅, 타입스크립트, 리액트 컴파일러
리액트 설정하기
리액트 개발환경 설정하기
- 에디터 기능 - 린팅 & 포맷팅
- 리액트에서 타입스크립트 사용하기
- 리액트 컴파일러 사용하기
[리액트v19 공식문서-설정하기] https://ko.react.dev/learn/setup
1️⃣ 에디터 설정하기
에디터 종류
- VS Code
- WebStorm
- Sublime Text
- Vim
에디터 기능 - Linting & Formatting
✅ 린팅(ESLint)
ESLint? 코드의 품질을 검사해주는 도구
eslint-config-react-app
- Create React App 에서 기본적으로 적용하는 ESLint 규칙 모음
- React 관련 권장 설정 포함 (eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y)
- 꼭 eslint-config-react-app 이 아닌 airbnb, standard, prettier 등의 설정하거나 .eslintrc.json 을 직접 커스텀해서 프로젝트에 맞는 룰을 설정할 수 있다.
eslint-plugin-react-hooks
-
React Hooks의 올바른 사용을 보장하는 ESLint 플러그인으로 리액트 프로젝트에서 필수적이다.
useState
,useEffect
,useMemo
와 같은 Hooks를 사용할 때 잘못된 사용 패턴을 감지하고 경고해준다. - rules-of-hooks
- Hooks는 반드시 컴포넌트 최상위에서 호출해야한다.
- 조건문, 반복문, 중첩 함수 내부에서 사용하면 안된다.
- exhaustive-deps
- useEffect, useCallback, useMemo의 의존성 배열이 올바르게 설정되어있는지 검사
✅ 포매팅(Prettier)
Prettier? 설정한 규칙에 맞게 코드를 포맷팅 해주는 도구
- 저장 시점에 포매팅하기 설정에서 기본 포매터가 prettier - code formatter 로 설정되어있는지 확인 ⇒ 설정에서 format on save 옵션 활성화
eslint-config-prettier
ESLint 규칙과 Prettier 규칙의 충돌 방지를 위해 ESLint 프리셋의 모든 포매팅 규칙을 비활성화하고 Prettier 규칙을 우선시한다.
✅ React + Vite + Typescript 프로젝트에서 ESLint & Prettier 설정하기
- Vite기반 프로젝트를 생성할 때 기본적으로 ESLint도 같이 포합되어 설치되기 때문에 따로 설치할 필요 없음
- eslint, eslint-plugin-react-hooks, eslint-plugin-react-refresh, typescript-eslint 포함
{
"name": "my-app-setup",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
- prettier 설치
npm install --save-dev --save-exact prettier
eslint-config-prettier
설치
npm install --save-dev eslint-config-prettier
- ESLint에서 Prettier와 충돌할 수 있는 규칙을 무시함으로써 포맷팅할 때 Prettier의 규칙이 우선시 하도록함.
-
Prettier가 ESLint처럼 오류를 출력해 주는 하게 하고 싶으면
eslint-puglin-prettier
라이브러리를 같이 사용할 수 있다.npm install --save-dev eslint-plugin-prettier
- 프로젝트 루트에
.prettierrc
파일 생성 및 규칙 추가
{
"semi": false,
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 100
}
eslint.config.js
prettier 플러그인 활성화
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import prettier from "eslint-plugin-prettier";
export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [
js.configs.recommended,
...tseslint.configs.recommended,
"plugin:prettier/recommended", // Prettier 설정 추가
],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
prettier, // Prettier 플러그인 추가
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"prettier/prettier": "warn", // Prettier 룰 적용
},
}
);
2️⃣ 타입스크립트 사용하기
타입스크립트 설치
- 기존 리액트 프로젝트에 타입스크립트 추가하기
npm install @types/react @types/react-dom
타입스크립트 관련 설정
-
컴파일러 옵션 설정
- dom은 lib에 포함되어야함(기본값, 별도로 지정X)
- jsx를 유효한 옵션 중 하나로 설정
-
React + Vite + Typescript 프로젝트 설정
tsconfig.json
기본 설정 및 공통 설정 관리
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
tsconfig.app.json
리액트 애플리케이션을 위한 구체적인 컴파일러 설정
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 브라우저 환경을 위한 라이브러리 설정
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler", // 번들러(Vite/Webpack 등)와 호환성 강화
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true, // 별도 .js파일을 생성하지 않고 번들러가 트랜스파일 담당
"jsx": "react-jsx", // React 최신 JSX 변환 방식 사용(React 17+)
/* Linting */
"strict": true, // 타입 검사 강화
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
tsconfig.node.json
Node.js 실행 파일을 위한 설정 관리
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"] // Vite 설정 파일은 Node.js 환경에서 실행됨
}
📌 tsconfig.json + tsconfig.app.json + tsconfig.node.json 구조의 장점
- 다양한 빌드 환경 지원
- tsconfig.app.json → React 클라이언트 코드 전용
- tsconfig.node.json → Node.js 관련 코드
- 공통 설정과 개별 설정 분리 가능
- tsconfig.json에서 공통적인 설정을 관리
- 각 환경에 맞는 세부 설정 적용
- 빠른 빌드 및 모노레포 지원
- tsconfig.json이 여러 설정을 참조하므로 프로젝트 빌드 속도 최적화
- 대규모 프로젝트에서는 tsconfig.app.json과 tsconfig.node.json을 분리해 독립적으로 타입 검사 수행 가능
✅리액트 컴포넌트에서 타입스크립트 사용하기
- jsx를 포함하는 모든 파일은
.tsx
파일 확장자 사용
props
interface MyButtonProps {
title: string;
disabled: boolean
}
function MyButton({ title, disabled }: MyButtonProps) {
return (...)
}
useState
-
@types/react
타입 정의에 내장 Hooks에 대한 타입이 포함되어 있어 추가 설정 없이 추론된 타입을 얻을 수 있다.// enabled는 'boolean'으로 추론됨 // setEnabled는 boolean 인수나 boolean을 반환하는 함수를 받도록 추론 const [enabled, setEnabled] = useState(false);
-
state에 대한 타입을 명시적으로 제공해야할 때 ex)유니언 타입일 때
type Status = "loading" | "success" | "error"; const [status, setStatus] = useState < Status > "idle";
useReducer
-
reducer 함수의 타입은 state 초기값에서 추론됨
import { useReducer } from 'react' interface State { count: number } type CounterAction = | { type: 'reset' } | { type: 'setCount'; value: State['count'] } const initialState = { count: 0 }; function stateReducer(state: State, action: CounterAction): State { switch (action.type) { case 'reset': return initialState; case 'setCount': return { ...state, count: action.value }; default: throw new Error('Unknow action'); } } export default function App() { const [state, dispatch] = useReducer<State>(stateReducer, initialState); ... }
📌 interface는 객체 타입 정의에 적합
- 객체의 구조를 정의하는데 주로 사용
- 확장(extends)가능 → 기존 타입을 확장해서 사용 가능
- 중복 선언 가능(같은 이름의 interface를 여러번 선언하면 병합됨)
type은 더 유연하게 사용 가능
- 객체 뿐 아니라 유니온 타입, 튜플, 기본 타입 등 다양한 형태로 정의 가능
- 확장이 필요하지 않은 경우 주로 사용
useContext
- context에서 제공한 값의 타입은 createContext 호출에서 전달된 값에서 추론됨
- Context는 기본적으로 아무 값도 없기 때문에
ContextShape | null
을 명시적으로 설정 -
⇒ Provider없이 useContext() 를 사용하는 오류 방지
const UserContext = createContext<{ name: string; age: number } | null>(null);
useMemo
-
useMemo를 호출한 결과는 첫번째 매개변수에 있는 함수의 반환값에서 추론됨
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
useCallback
- Strict 모드에서는 useCallback에 매개변수를 위한 타입 추가
-
⇒ 콜백의 타입은 함수의 반환 값에서 추론되고 매개변수 없이는 타입을 완전히 이해할 수 없기 때문
const handleChange = useCallback < React.ChangeEventHandler < HTMLInputElement >> ((event) => { setValue(event.currentTarget.value); }, [setValue]);
DOM 이벤트
import { useState } from "react";
export default function Form() {
const [value, setValue] = useState("Change me");
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setValue(event.currentTarget.value);
}
return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}
- onChange = (event) => handleCange(event.currentTarget.value) 로 작성하면 onChange 내부의 event는 TypeScript가 자동으로 React.ChangeEvent
타입으로 추론한다. - handleChange를 별도의 함수로 분리하면 TypeScript가 event의 타입을 추론하지 못하기 때문에,
React.ChangeEvent
를 명시적으로 지정해야 한다.
Children
React.ReactNode
: jsx에서 자식으로 전달할 수 있는 모든 가능한 타입의 조합React.ReactElement
: jsx 엘리먼트, 원시값 포함 안함
Style Props
React.CSSProperties
: 리액트 컴포넌트에 인라인 스타일 정의할 때
3️⃣ 리액트 컴파일러(React Compiler)
리액트 컴파일러? 빌드 타입 전용 도구로 리액트 앱을 자동으로 최적화하는 컴파일러. 아직 베타 단계이며 리액트 17버전 이상에서 사용할 수 있다.
리액트 컴파일러의 역할
- 자바스크립트와 리액트 규칙을 활용해 자동으로 컴포넌트와 Hook 내부의 값을 메모이제이션 ⇒
useMemo
,useCallback
,React.memo
와 같은 역할을 컴파일러가 알아서 판단하여 해주는 것임 - 리액트 규칙에 위반될 경우 해당 컴포넌트/Hook 은 최적화하지 않고 건너뜀(코드 전체를 최적화X)
리액트 컴파일러의 주요 기능
- 리렌더링 최적화
- 부모 컴포넌트만 변경되었음에도 컴포넌트 트리 내 여러 컴포넌트가 리렌더링 되는 경우
- ⇒ 상태 변경 시 앱에서 관련된 부분만 리렌더링되도록 자동으로 적용
- 리액트 외부에서 비용이 많이 드는 계산 건너뛰기
주의
리액트 컴포넌트와 Hook만 메모제이션 하며, 모든 함수를 메모제이션 하지 않음- 리액트 컴파일러의 메모제이션은 여러 컴포넌트와 Hook사이에서 공유되지 않음
리액트 컴파일러 설치
babel-plugin-react-compiler
eslint-plugin-react-compiler
설치
npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
- ESLint 설정
eslint.config.js
에디터에서 리액트 규칙 위반 사항 표시
import reactCompiler from "eslint-plugin-react-compiler";
export default [
{
plugins: {
"react-compiler": reactCompiler,
},
rules: {
"react-compiler/react-compiler": "error",
},
},
];
- Babel 설정(Vite 프로젝트)
vite.config.js
import path from "path";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vite.dev/config/
export default defineConfig({
plugins: [
react({
babel: {
plugins: [["babel-plugin-react-compiler"]],
{
include: ['src/path/to/dir/**/*.tsx', 'src/path/to/dir/**/*.jsx'], // 일부 파일에만 적용하고 싶을 때
},
},
}),
tailwindcss(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
코드베이스에 컴파일러 적용하기
- 리액트 v17-18 버전에서 최적화된 코드를 실행하기 위해서는 아래 패키지 추가 설치 필요
npm install react-compiler-runtime@beta
-
리액트 컴파일러 적용 후 성능 최적화
-
리액트 컴파일러가 자동으로 컴포넌트와 Hook을 메모제이션해 불필요한 리렌더링을 최소화
- 최적화가 전혀 되지 않은 프로젝트에 리액트 컴파일러를 적용했더니 렌더링 소요시간이 5.6ms => 1ms 로 줄어듦
- 컴파일러를 적용하고 싶지 않은 부분이 있으면 ‘use no memo’지시어를 사용하면 해당 컴포넌트만 제외시킬 수 있음
실무에서 프로젝트에 전체적으로 적용하기에는 아직 안정되지 않아 위험 부담도 있는 것 같다. 최적화가 중요한 부분에 일부 적용하면 좋은 효과를 얻을 수 있을 것 같음
Leave a comment