🔨 기초 세팅 : 타입스크립트와 타입 버전 eslint 설치
1. 타입스크립트 설치
npm install --save typescript @types/node @types/react @types/react-dom @types/jest # 패키지 설치
npx tsc --init # tsconfig.json 파일 생성(jsconfig.json은 삭제한다)
2. tsconfig 설정 (애플코딩 참고)
플젝의 tsconfig는 아래와 같이 설정했고, 그 외로 설정할 수 있는 속성들은 그 아래에 작성했다
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"module": "esnext", // 리액트 프로젝트에서 추천(최신 ECMAScript 모듈)
"target": "es5", // 타스를 어떤버전의 자스 파일로 바꿀건지
"lib": ["dom", "es2015"],
"allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지
"checkJs": false, // 일반 js 파일에서도 에러체크 여부
"skipLibCheck": true,
"strict": true, //strict 관련, noimplicit 어쩌구 관련 모드 전부 켜기
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx", // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
"forceConsistentCasingInFileNames": true,
"allowImportingTsExtensions": true
},
"include": ["src"]
}
3. eslint 타입스크립트 설치 및 .eslintrc.js 수정
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended', // TypeScript 권장 규칙 추가
'airbnb',
'airbnb/hooks',
'prettier',
],
parser: '@typescript-eslint/parser', // TypeScript 파서 추가
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'prettier'], // TypeScript 플러그인 추가
rules: {
'import/extensions': [ // import 시 js, ts 안붙여도 에러 안나도록
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'react/react-in-jsx-scope': 'off',
'react/require-default-props': 'off',
'react/function-component-definition': [
2,
{ namedComponents: ['arrow-function', 'function-declaration'] },
],
'react/jsx-props-no-spreading': 0,
'max-params': ['error', 3],
'no-var': 'error',
'no-alert': 'off',
'no-console': 'off',
'no-unused-vars': 'off', // 함수타입에서 매개변수를 사용하지 않으면 에러 발생하는 것 해제
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'react/no-array-index-key': 'off',
'react/prop-types': 'off',
'react/no-unstable-nested-components': 'off',
'react-hooks/exhaustive-deps': 'off',
'no-restricted-syntax': 'off',
'react/jsx-filename-extension': 'off',
'import/prefer-default-export': 'off',
'consistent-return': 'off',
},
settings: {
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
ignorePatterns: ['build/'],
};
🪡 라이브러리 ts 버전 재설치
사용하고 있는 라이브러리가 여러개인데 타입스크립트 버전이 필요한 라이브러리들은 따로 설치를 해줘야 했다.
보통은 @types 를 붙여 타스 버전으로 재설치해준다.
1. AG-Grid
기존에는 ag-grid-react만 설치되어 있었는데, 타입을 사용하려면 ag-grid-community가 필요해서 설치해주었다. 현재 공식문서에는 ag-grid-react를 설치하면 커뮤니티가 자동으로 설치된다고 한다. (Install the ag-grid-react package, which also installs ag-grid-community)
2. react-date-range
타입스크립트 버전을 설치했다가 의존성 충돌로 타입스크립트 버전을 낮추었다.
npm install --save @types/react-date-range
npm install typescript@4.9.5 --save-dev # 버전 낮추기
🔄 코드 변경 과정
객체의 타입 지정
객체의 속성에 접근할 때 타입스크립트가 객체에 속성이 있는지 확신하지 못하여 에러를 내는 경우가 많았다. 그때 여러 방식으로 객체에게 속성이 있음을 명시해주었는데, 상황에 따라 맞는 방법을 사용해야 한다.
- 객체의 키에 동적으로 접근할 때 : 보통 객체의 키를 문자열로 받아서 접근하게 되면 string이 다 오는 게 아니라 data의 키만 와야해! 라며 에러를 발생시킨다. 그럴 때 [key as keyof typeof data]를 사용하여 키가 data의 키라는 것을 명확히 알려준다.
data[key as keyof typeof data]
- 객체에 해당 속성이 있음을 확신하지 못할 때 : 옵셔널 속성의 경우 타입스크립트가 속성이 있다고 확신하지 못한다. 이때 “속성“ in 객체로 해당 속성이 있을 때 수행하도록 하고, 만약 데이터(객체)가 undefined또는 null인 경우 에러가 발생하므로 데이터가 있는지 확인하는 작업도 같이 조건문에 넣는다.
if(data && "name" in data) { }
- 객체의 타입이 여러개일 경우 또는 리턴 타입을 일치시킬 때 : 유니언 타입을 사용하거나 데이터가 없어서 빈객체를 내보내야할 때, as 타입을 사용하여 데이터의 타입은 정확히 이거다 라고 타입 단언한다.
// 데이터가 유니언이고 각각의 타입일 경우의 속성을 사용할때
const data: AType | BType;
const A = (data as AType)?.name;
const B = (data as BType)?.age;
// data가 없을 때 리턴 타입을 일치시켜야 할 때
if (!data) {
return {} as CType;
}
- reduce로 chkboxArray의 id 값을 acc의 속성으로 지정하면서 새로운 객체를 만들고 있다. reduce가 새로 반환하는 값의 타입을 지정해줘야 한다. <Record<string, boolean>> 를 사용하여 객체 타입을 지정해주었다.
const chkboxObject = chkboxArray.reduce<Record<string, boolean>>((acc, { id }) => {
acc[id] = true;
return acc;
}, {});
Axios 요청에서
- interceptor를 사용해 header의 엑세스토큰을 변경해주는 작업을 했었는데, 기존에는
const { headers, ...restConfig } = config; 이렇게 기존 config에서 header를 분리한 후
headers: { ...headers, 'x-access-token': window.sessionStorage.getItem('accessToken'), }
이런 식으로 엑세스토큰을 지정해주었다. 그런데 이렇게 되면 AxiosHeaders 타입이 아닌 일반 객체가 되어버린다.
그래서 타입불일치로 에러가 발생한다.
이 때는 기존 config 객체를 얕은 복사를 한 후 아래와 같이 해당 속성을 수정하면 타입이 그대로 유지된다.
instance.interceptors.request.use(
config => {
const newConfig = { ...config }; // 기존 config 객체를 복사 (얕은 복사)
newConfig.headers['x-access-token'] = window.sessionStorage.getItem('accessToken');
return newConfig;
}
Ag-Grid
- column에 ColDef 타입 설정
cellRenderer는 마크업을 직접 리턴 할 수 없다. 컴포넌트 분리해서 전달 받도록 수정
import { ColDef } from 'ag-grid-community';
import CancelBtn from 'components/grid/CancelBtn';
const column: ColDef[] = [
{
field: 'no',
checkboxSelection: true,
width: 100,
headerName: 'No',
headerCheckboxSelection: true,
showDisabledCheckboxes: true,
},
{
field: 'IDX',
headerName: '취소',
cellRenderer: CancelBtn,
},
];
- ag-grid 테이블에서 컬럼명 가져오기
기존에는 e.column.colId를 사용했으나 e.colDef.field로 이벤트 객체가 변경
const onCellClicked = (e: CellClickedEvent) => {
if (e.colDef.field === 'IDX') {
if (window.confirm('취소 하시겠습니까?')) {
}
}
};
React-Query
- useQuery에서는 useQuery<DataType[]> 이렇게 반환 타입을 지정할 수 있다. 나는 select를 사용하여 응답 데이터의 rows 속성의 값을 반환했고 그것이 DataType[]이기에 위와 같이 설정했다. 그런데 useQuery의 제네릭은 초기 응답을 기준으로 동작한다. 그러니까 { rows: DataType[] } 이 타입이 반환타입이라고 생각한다. 하지만 select로 반환 데이터를 변경했기 때문에 실제 데이터는 DataType[]이 반환 되면서 타입 불일치에러가 발생한다. 이럴때는 select의 응답 데이터에 타입을 지정해주어야 한다. select: (res: { rows: DataType[] }) =>
const { data, refetch } = useQuery(
queryKyes,
() =>
request({
url: 'withdraw',
method: 'get',
params: { ...date },
}),
{
select: (res: { rows: DataType[] }) =>
res.rows.map((item, index) => ({
...item,
id: index + 1,
})),
},
);
기타 작업
- 아래와 같이 bannerLabels에 BannerLabel[] 타입을 지정하고 map으로 돌릴때 item을 BannerLabel로 타입을 한번더 지정해주면


이렇게 no-unused-prop-types 에러가 발생한다

- 세션스토리지에 데이터를 저장해두고 그 값을 json으로 파싱해서 가져올 때
기존에는 const storedQeuryData = JSON.parse(sessionStorage.getItem("data")); 이렇게 사용했지만
세션스토리지에 data 값이 없는 경우의 반환값도 만들어줘야 한다.
const storedQeuryData = sessionStorage.getItem('data');
const parsedData = storedQeuryData ? JSON.parse(storedQeuryData) : null;
- 숫자배열을 만들 때 기존에는 Array(24) .fill() .map((_, i) => ( 이렇게 사용했으나
Array(24)는 24칸의 빈 배열을 만드는 것이다.
그래서 필수로 fill()를 호출하는데, 이는 타입을 any로 추론한다.
- 이를 대신하여 Array.from({ length: 24 }, (_, i) => ( 를 사용하는데 이 코드는
24칸의 배열을 만들거야~ 하지만! 요소를 다 채우면서 만들어볼게~
이기 때문에 빈 배열이 아닌, 다 채워진 배열을 만드므로 타입 에러가 나지 않는다.
Array(24) .fill() .map((_, i) => (i)); // 에러
Array.from({ length: 24 }, (_, i) => (i)); // 권장
- 기존 배열에 데이터를 추가할 때 객체의 속성이 정확히 일치해야 한다. 타입이 일치하지 않은 경우 필요한 속성만 가지고 있는 객체 배열을 새로 만들고, 새로운 배열에 객체를 추가한다.
// 필요한 속성만 있는 객체 배열을 만들고
const corpList = datalist?.map(({ DEPOSIT_YN, S_ID, S_NAME }) => ({
DEPOSIT_YN,
S_ID,
S_NAME,
}));
// 그 객체에 새로운 데이터를 추가
corpList?.push({
DEPOSIT_YN: 'Y',
S_ID: 'hana',
S_NAME: '하나은행',
});
- onClick 함수는 (event: MouseEvent<HTMLButtonElement>) => void
refetch 함수는 (options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined) => Promise<QueryObserverResult<any, unknown>> 이다.
매개 변수의 타입불일치로 에러가 발생하는데 이 경우 onClick에 () => refetch()를 사용하면 이벤트 객체를 전달하지 않고 refetch()만 실행할 수 있다.


<button onClick={refetch}>Refetch</button> // 기존 js 방식에서는 타입을 확인하지 않으니 에러가 나지 않음
<button onClick={() => refetch()}>Refetch</button>
3주간의 타입스크립트 리팩토링 작업이 끝났다. 기존 코드와 타입스크립트를 이해하는 과정을 함께 거치다 보니 생각보다 시간이 걸렸다. 이렇게 js를 ts로 바꾸는 작업이 앞으로 많지는 않겠지만, 하게 된다면 util, hooks 등 공통 함수들 먼저 작업을 하고 각 페이지, 컴포넌트로 넘어가야겠다. (쉬워보이는 페이지와 컴포넌트부터 하다보니 나중에 util, hook을 변환하면 작업했던 파일을 다시 작업해야할 일이 생겼었다) 이젠 ts 프로젝트에 어느정도 익숙해진것 같지만 그럼에도 아직 공부해야할게 많다고 생각된다. ts 기초뿐만아니라 js 기초도 잘 잡아가면서 공부를 해야겠다.
'React' 카테고리의 다른 글
| [react-router] CSR, SSR, loader, HydrationBoundary (framwork) (0) | 2026.08.07 |
|---|---|
| [react-query] 좋아요 낙관적 업데이트 (0) | 2025.09.29 |
| react-hook-form을 알아보자 (2) | 2025.08.13 |
| useState에서 상태 변경 시 바로 재렌더링 일어나지 않는 버그 (2) | 2025.08.13 |
| throttle과 debounce 사용하기 (2) | 2025.06.21 |