반응형
React Native에서 Cheerio 사용하기: TypeScript 타입 선언 완벽 가이드
React Native 프로젝트에서 HTML 파싱을 위해 react-native-cheerio를 사용하다 보면, TypeScript 컴파일 오류가 발생하는 경우가 있습니다. 이는 해당 패키지에 타입 선언이 없기 때문인데, 이 문제를 해결하는 방법을 상세히 알아보겠습니다.
문제 상황
react-native-cheerio 패키지는 JavaScript로만 작성되어 있어서:
- TypeScript 타입 선언이 없음
- @types/react-native-cheerio도 존재하지 않음
- 따라서 TypeScript에서 사용 시 컴파일 오류 발생
해결 방법: 직접 타입 선언 파일 생성
1. 타입 선언 파일 생성
프로젝트 루트에 types/react-native-cheerio.d.ts 파일을 생성합니다.
declare module 'react-native-cheerio' {
// Cheerio 요소의 기본 인터페이스
interface CheerioElement {
[key: string]: any;
}
// Cheerio 인스턴스 (jQuery 스타일 API)
interface CheerioInstance {
// 선택자 메서드
find(selector: string): CheerioInstance;
// 텍스트 조작
text(): string;
text(text: string): CheerioInstance;
// HTML 조작
html(): string;
html(html: string): CheerioInstance;
// 속성 조작
attr(name: string): string;
attr(name: string, value: string): CheerioInstance;
// 반복 처리
each(fn: (index: number, element: CheerioElement) => void): CheerioInstance;
// 배열 스타일 접근
length: number;
[index: number]: CheerioElement;
}
// Cheerio 메인 인터페이스
interface CheerioStatic {
// 메인 함수 호출 시그니처
(selector?: any, context?: any, root?: any, options?: any): CheerioInstance;
// HTML 파싱 메서드
load(html: string, options?: any): CheerioInstance;
// 버전 정보
version: string;
}
const reactNativeCheerio: CheerioStatic;
export default reactNativeCheerio;
}
2. tsconfig.json 설정
tsconfig.json에서 타입 선언 파일을 인식하도록 설정합니다:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
},
"include": [
"types/**/*"
]
}
사용 방법
타입 선언 파일을 생성한 후, 다음과 같이 사용할 수 있습니다:
import cheerio from 'react-native-cheerio';
const htmlString = '<div><p>Hello World</p></div>';
const $ = cheerio.load(htmlString);
// 텍스트 추출
const text = $('p').text(); // "Hello World"
// HTML 조작
$('p').html('<strong>Bold Text</strong>');
// 속성 조작
$('div').attr('class', 'container');
// 반복 처리
$('p').each((index, element) => {
console.log(`Element ${index}:`, element);
});
Import 스타일 선택
권장: Default Import
import cheerio from 'react-native-cheerio';
대안: Namespace Import
import * as cheerio from 'react-native-cheerio';
Default Import를 권장하는 이유:
- 더 명확한 의미: "하나의 주요 기능을 가져온다"
- 현대적 ES Module 스타일
- 혼란 방지: Named exports와 구분됨
TypeScript 타입 선언 우선순위
TypeScript는 다음 순서로 타입 선언을 찾습니다:
- node_modules/@types/react-native-cheerio (DefinitelyTyped)
- react-native-cheerio/package.json의 "types" 필드
- react-native-cheerio/index.d.ts (패키지 루트)
- 프로젝트 내 타입 선언 파일 ← 우리가 만든 파일
- 없으면 'any' 타입으로 추론
프로젝트 구조 권장사항
your-project/
├── types/ # 외부 패키지 타입 선언
│ └── react-native-cheerio.d.ts
├── typings/ # 프로젝트 내부 커스텀 타입
│ └── custom.ts
└── src/
└── ...
마무리
이제 React Native 프로젝트에서 TypeScript의 타입 안정성을 유지하면서 Cheerio를 사용할 수 있습니다. 타입 선언 파일을 통해 IDE의 자동완성과 타입 체크 기능도 정상적으로 작동합니다.
필요에 따라 더 많은 Cheerio 메서드를 타입 선언에 추가할 수 있으며, 이는 프로젝트의 요구사항에 맞춰 점진적으로 확장 가능합니다.
참고: src/types/react-native-cheerio.d.ts 전체 코드
/**
* ================================================================
* react-native-cheerio 모듈에 대한 TypeScript 타입 선언 파일
* ================================================================
*
* 🎯 목적: TypeScript 컴파일 오류 해결
* - react-native-cheerio 패키지는 JavaScript로만 작성됨 (타입 없음)
* - @types/react-native-cheerio도 존재하지 않음
* - 따라서 직접 타입 선언 파일을 생성하여 타입 안정성 확보
*
* 🔍 react-native-cheerio 패키지 분석:
*
* 📁 실제 파일 구조:
* - index.js: exports = module.exports = require('./lib/cheerio');
* - lib/cheerio.js: var Cheerio = module.exports = function(selector, context, root, options) { ... }
*
* 📦 CommonJS 패턴:
* module.exports = function cheerio() { ... }
* cheerio.load = function(html) { ... } // 함수에 메서드 추가
* cheerio.version = "1.0.0" // 함수에 속성 추가
*
* 🔄 CommonJS vs ES Modules 호환성:
*
* 📌 CommonJS (react-native-cheerio의 실제 방식):
* - 내보내기: module.exports = function
* - 특징: 하나의 객체/함수를 내보냄
* - 런타임: 동기적 로딩
*
* 📌 ES Modules (현대적 방식):
* - Named exports: export const func1 = ..., export const func2 = ...
* - Default export: export default function
* - 특징: 정적 분석 가능, 트리 쉐이킹 지원
*
* 🪄 TypeScript/Babel의 호환성 마법:
*
* CommonJS 패키지를 ES Module 스타일로 import 가능:
* - import * as cheerio from 'pkg' → const cheerio = require('pkg')
* - import cheerio from 'pkg' → const cheerio = require('pkg')
* - 결과: 둘 다 동일한 함수를 가져옴!
*
* 🎯 왜 이런 호환성이 필요한가?
* 1. 역사적 이유: CommonJS가 먼저 존재
* 2. 점진적 마이그레이션: 기존 패키지들의 ES Module 전환 지원
* 3. 개발자 편의: 원하는 import 스타일 선택 가능
*
* 🤔 import 스타일 선택 기준:
*
* ❌ 기존 방식: import * as cheerio from 'react-native-cheerio'
* - CommonJS라서 이렇게 써야 한다는 고정관념
* - 실제로는 둘 다 동작함
*
* ✅ 권장 방식: import cheerio from 'react-native-cheerio'
* - 논리적으로 더 명확: "하나의 주요 기능을 가져온다"
* - Named exports와 헷갈리지 않음
* - 현대적 ES Module 스타일
*
* 📊 import 스타일 비교:
*
* Named exports 패키지 (여러 기능):
* export const func1 = ...
* export const func2 = ...
* → import { func1, func2 } from 'pkg' (선택적)
* → import * as all from 'pkg' (전체를 namespace로)
*
* CommonJS 패키지 (하나의 주요 기능):
* module.exports = function
* → import cheerio from 'pkg' (권장)
* → import * as cheerio from 'pkg' (가능하지만 혼란 야기)
*
* 🏗️ 타입 선언 방식 선택:
*
* 옵션 1: export = (CommonJS 스타일)
* declare module 'react-native-cheerio' {
* interface CheerioStatic { ... }
* const cheerio: CheerioStatic;
* export = cheerio; // TypeScript 전용 문법
* }
* 사용: import * as cheerio from 'react-native-cheerio'
*
* 옵션 2: export default (ES Module 스타일) ← 현재 선택
* declare module 'react-native-cheerio' {
* interface CheerioStatic { ... }
* const cheerio: CheerioStatic;
* export default cheerio; // 표준 ES Module 문법
* }
* 사용: import cheerio from 'react-native-cheerio'
*
* 🎯 현재 선택한 이유:
* 1. 논리적 명확성: 하나의 주요 기능 → default import
* 2. 현대적 스타일: ES Module 표준 문법 사용
* 3. 혼란 방지: Named exports와 구분되는 명확한 패턴
* 4. 사용자 편의: cheerio.load() 직관적 사용
*
* 📂 TypeScript 타입 선언 파일 참조 우선순위:
* 1️⃣ node_modules/@types/react-native-cheerio (DefinitelyTyped) - ❌ 없음
* 2️⃣ react-native-cheerio/package.json의 "types" 필드 - ❌ 없음
* 3️⃣ react-native-cheerio/index.d.ts (패키지 루트) - ❌ 없음
* 4️⃣ 프로젝트 내 타입 선언 파일 (이 파일!) - ✅ 여기서 발견!
* 5️⃣ 없으면 'any' 타입으로 추론
*
* 🔥 타입 선언 충돌과 Module Augmentation:
*
* 💥 우선순위에 의한 덮어쓰기:
* - 높은 우선순위가 낮은 우선순위를 완전히 덮어씀 (병합 안됨!)
* - 예: @types/react + 프로젝트/react.d.ts → @types/react만 사용
*
* 🔧 Module Augmentation (모듈 확장):
* - 기존 타입에 새로운 속성/메서드 추가
* - 핵심 요구사항: 파일에 import/export 문이 있어야 함!
*
* 예시:
* // react-augmentation.d.ts
* import React from 'react'; // 이것이 augmentation 모드로 전환!
*
* declare module 'react' {
* interface HTMLAttributes<T> {
* customProp?: string; // 기존 interface에 속성 추가
* }
* }
*
* 🔍 Declaration vs Augmentation 모드:
* - Declaration 모드 (import/export 없음): 새로운 모듈 선언 → 우선순위 적용
* - Augmentation 모드 (import/export 있음): 기존 모듈 확장 → 병합
*
* 📊 타입 선언 우선순위 시스템 상세:
*
* 🎯 모듈별 개별 우선순위 (declare module 기준):
* TypeScript는 각 모듈명에 대해 개별적으로 우선순위를 적용합니다.
*
* 예시 상황:
* // @types/lodash/index.d.ts
* declare module 'lodash' { export function map(): any; }
* declare module 'lodash/map' { export default function(): any; }
*
* // lodash/index.d.ts (패키지 자체)
* declare module 'lodash' { export function filter(): any; }
* declare module 'lodash/debounce' { export default function(): any; }
*
* 🔍 우선순위 적용 결과:
* - 'lodash' 모듈: @types/lodash 버전 사용 (패키지 버전 무시)
* - 'lodash/map' 모듈: @types/lodash 버전 사용
* - 'lodash/debounce' 모듈: lodash 패키지 버전 사용 (@types에 없으므로)
*
* ⚠️ 전역 선언의 충돌 (declare const/function/class/var):
* 모듈 우선순위와 달리 전역 선언은 충돌 시 컴파일 에러 발생!
*
* 충돌 예시:
* // @types/jquery/index.d.ts
* declare const $: JQueryStatic;
*
* // 사용자의 types/jquery.d.ts
* declare const $: MyCustomType; // ❌ 컴파일 에러!
* // Error: Duplicate identifier '$'
*
* 🛡️ 전역 충돌 방지 방법:
* 1. ES Module 사용: export {} 추가하여 파일을 모듈로 만들기
* 2. skipLibCheck: true 설정 (tsconfig.json)
* 3. 전역 이름 피하기: Node, Array, String 등 내장 객체명 사용 금지
* 4. 적절한 타입 정의 설치: @types/node 등
*
* 🏆 2025년 TypeScript 프로젝트 구조 베스트 프랙티스:
*
* 📁 현재 프로젝트 구조 평가:
* ✅ types/ - 외부 패키지 타입 선언 (.d.ts 파일들)
* ✅ typings/ - 프로젝트 내부 커스텀 타입들 (.ts 파일들)
* → 업계 표준과 완벽히 일치하는 구조! 👍
*
* 🎯 결론:
* - react-native-cheerio는 CommonJS 방식의 단일 함수 패키지
* - export default로 타입 선언하여 import cheerio 스타일 사용
* - 논리적으로 명확하고 현대적인 ES Module 패턴 적용
* - TypeScript 컴파일 오류 해결과 동시에 타입 안정성 확보
*/
// 🔍 TypeScript 타입 선언 키워드들:
//
// 📌 declare의 의미:
// - "어딘가에 이미 존재한다"고 TypeScript에게 알려주는 키워드
// - 실제 구현 없이 타입 정보만 선언
// - 컴파일 시점에만 존재, JavaScript로 변환되면 사라짐
//
// 🌟 declare 사용 예시들:
// declare const globalVar: string; // 전역 변수 타입 선언
// declare function globalFunc(): void; // 전역 함수 타입 선언
// declare class GlobalClass { ... } // 전역 클래스 타입 선언
// declare var window: Window; // 브라우저 전역 객체
//
// 📌 module의 의미:
// - 모듈(패키지)의 네임스페이스를 정의
// - 특정 모듈명에 대한 타입 정보를 그룹화
// - import/export와 연결되는 실제 모듈 시스템
//
// 📌 declare module 조합:
// - "이 모듈명으로 import할 때 어떤 타입을 사용할지" 알려주는 선언
// - 실제 패키지 코드와는 무관하게 타입 정보만 제공함
//
// 📌 namespace의 의미:
// - 관련된 타입들을 그룹화하는 TypeScript 개념
// - 전역 스코프 오염을 방지하고 코드를 구조화
// - 점(.) 표기법으로 접근: MyNamespace.SomeType
// - 컴파일 시점에만 존재 (런타임에서 사라짐)
//
// 🌟 namespace 예시:
// declare namespace jQuery {
// interface JQueryStatic {
// (selector: string): JQuery;
// ajax(settings: any): void;
// }
// interface JQuery {
// click(): JQuery;
// hide(): JQuery;
// }
// }
// 사용: const $: jQuery.JQueryStatic = ...;
// const element: jQuery.JQuery = ...;
//
// 🔑 핵심 차이점:
//
// 📦 객체 (런타임에 실제 존재):
// const Utils = {
// formatDate: (date: Date) => date.toString(),
// API_URL: 'https://api.com'
// };
// Utils.formatDate(new Date()); // 실행 가능! 실제 함수 호출
//
// 📦 namespace (컴파일 시점에만 존재):
// namespace Utils {
// export type DateFormat = 'ISO' | 'US';
// export interface ApiResponse { data: any }
// }
// const format: Utils.DateFormat = 'ISO'; // 타입으로만 사용
// Utils.formatDate(new Date()); // ❌ 에러! 런타임에 존재하지 않음
//
// 💡 module vs namespace 차이:
// - module: 실제 파일/패키지와 연결 (import/export 시스템)
// - namespace: 타입들의 논리적 그룹핑 (점 표기법 접근, 타입 전용)
//
// 🏛️ 과거 vs 현재:
// - 과거: namespace를 많이 사용 (TypeScript 초기)
// - 현재: ES Modules이 표준이 되면서 declare module을 더 선호
// - namespace는 여전히 타입 그룹화에 유용
declare module 'react-native-cheerio' {
// Cheerio 요소의 기본 인터페이스
interface CheerioElement {
[key: string]: any;
}
// Cheerio 인스턴스 (jQuery 스타일 API)
interface CheerioInstance {
// 선택자 메서드
find(selector: string): CheerioInstance;
// 텍스트 조작
text(): string;
text(text: string): CheerioInstance;
// HTML 조작
html(): string;
html(html: string): CheerioInstance;
// 속성 조작
attr(name: string): string;
attr(name: string, value: string): CheerioInstance;
// 반복 처리
each(fn: (index: number, element: CheerioElement) => void): CheerioInstance;
// 배열 스타일 접근
length: number;
[index: number]: CheerioElement;
// 필요시 더 많은 jQuery 스타일 메서드 추가 가능
// addClass, removeClass, hasClass, css, etc.
}
// Cheerio 메인 인터페이스 (함수이면서 동시에 객체)
interface CheerioStatic {
// 메인 함수 호출 시그니처 (cheerio('selector') 형태)
(selector?: any, context?: any, root?: any, options?: any): CheerioInstance;
// load 메서드 - HTML 문자열을 파싱하여 Cheerio 인스턴스 반환
load(html: string, options?: any): CheerioInstance;
// version 속성 (package.json에서 export됨)
version: string;
}
// react-native-cheerio를 default export로 선언
// 사용법: import cheerio from 'react-native-cheerio'
const reactNativeCheerio: CheerioStatic;
export default reactNativeCheerio;
}
반응형
'REACT & NODE' 카테고리의 다른 글
| 안드로이드에서는 실제 배포된 앱이 아니면 결제가 실제로 이루어지지 않습니다. (0) | 2025.09.18 |
|---|---|
| directly 'pod install' deprecated, rosetta2, intell x86 vs arm64 (0) | 2025.09.17 |
| .env (0) | 2025.09.17 |
| React Native 0.81 and Expo 54 (0) | 2025.09.16 |
| React Native 0.79 and Expo 53 (2) | 2025.07.02 |