Webpack을 사용하여 최신 React 프로젝트를 위한 TypeScript 설정

게시 됨: 2022-03-10
빠른 요약 ↬ 이 기사에서는 개발자 코드로 일반적인 오류를 찾아내기 위한 정적 유형 기능을 제공하는 JavaScript의 위 첨자인 Typescript를 소개합니다. 이는 성능을 향상시켜 강력한 엔터프라이즈 애플리케이션을 만듭니다. 또한 Money Heist Episode Picker 앱을 빌드하고 TypeScript, useReducer, useContext 및 Reach Router와 같은 React 후크를 탐색하면서 React 프로젝트에서 TypeScript를 효율적으로 설정하는 방법을 배우게 됩니다.

이 소프트웨어 개발 시대에 JavaScript는 거의 모든 유형의 앱을 개발하는 데 사용할 수 있습니다. 그러나 JavaScript가 동적으로 유형이 지정된다는 사실은 느슨한 유형 검사 기능으로 인해 대부분의 대기업에서 문제가 될 수 있습니다.

다행스럽게도 Ecma Technical Committee 39가 JavaScript에 정적 유형 시스템을 도입할 때까지 기다릴 필요가 없습니다. 대신 TypeScript를 사용할 수 있습니다.

동적으로 유형이 지정되는 JavaScript는 해당 변수가 런타임에 인스턴스화될 때까지 변수의 데이터 유형을 인식하지 못합니다. 대규모 소프트웨어 프로그램을 작성하는 개발자는 경고나 문제 없이 이전에 선언된 변수를 다른 유형의 값으로 재할당하는 경향이 있어 종종 간과되는 버그가 발생할 수 있습니다.

이 튜토리얼에서는 TypeScript가 무엇이며 React 프로젝트에서 TypeScript로 작업하는 방법을 배웁니다. 결국 우리는 TypeScript와 현재 React와 유사한 후크( useState , useEffect , useReducer , useContext )를 사용하여 TV 쇼 Money Heist 의 에피소드 선택기 앱으로 구성된 프로젝트를 구축할 것입니다. 이 지식을 바탕으로 자신의 프로젝트에서 TypeScript로 실험을 계속할 수 있습니다.

이 기사는 TypeScript에 대한 소개가 아닙니다. 따라서 TypeScript와 JavaScript의 기본 구문은 다루지 않습니다. 그러나 우리는 KISS 원칙(간단하고 어리석게 유지)을 따르려고 노력할 것이기 때문에 따라하기 위해 이러한 언어의 전문가가 될 필요는 없습니다.

점프 후 더! 아래에서 계속 읽기 ↓

TypeScript 란 무엇입니까?

2019년 TypeScript는 GitHub에서 7번째로 가장 많이 사용되는 언어이자 5번째로 빠르게 성장하는 언어로 선정되었습니다. 그러나 TypeScript는 정확히 무엇입니까?

공식 문서에 따르면 TypeScript는 일반 JavaScript로 컴파일되는 JavaScript의 유형이 지정된 상위 집합입니다. Microsoft와 오픈 소스 커뮤니티에서 개발 및 유지 관리합니다.

이 컨텍스트에서 "Superset"은 언어에 JavaScript의 모든 기능과 기능이 포함된 다음 일부가 포함되어 있음을 의미합니다. TypeScript는 유형이 지정된 스크립팅 언어입니다.

개발자는 유형 주석, 클래스 및 인터페이스를 통해 코드 기반을 더 많이 제어할 수 있으므로 개발자가 콘솔에서 성가신 버그를 수동으로 수정하지 않아도 됩니다.

TypeScript는 JavaScript를 변경하기 위해 만들어지지 않았습니다. 대신 가치 있는 새 기능으로 JavaScript를 확장합니다. Node.js의 크로스 플랫폼 모바일 앱과 백엔드를 포함하여 일반 JavaScript로 작성된 모든 프로그램도 TypeScript에서 예상대로 실행됩니다.

이것은 이 튜토리얼에서 하는 것처럼 TypeScript로 React 앱을 작성할 수도 있음을 의미합니다.

왜 TypeScript인가?

아마도 TypeScript의 장점을 받아들이는 데 확신이 없으실 것입니다. 몇 가지 장점을 고려해 보겠습니다.

버그 감소

코드의 모든 버그를 제거할 수는 없지만 줄일 수는 있습니다. TypeScript는 컴파일 타임에 유형을 확인하고 변수 유형이 변경되면 오류를 발생시킵니다.

이러한 명백하지만 빈번한 오류를 일찍 발견할 수 있으면 유형이 있는 코드를 훨씬 쉽게 관리할 수 있습니다.

리팩토링이 더 쉽습니다

당신은 아마도 꽤 많은 것들을 리팩토링하고 싶을 것입니다. 그러나 그것들은 너무 많은 다른 코드와 많은 다른 파일들을 다루기 때문에 그것들을 수정하는 것을 조심해야 합니다.

TypeScript에서는 통합 개발 환경(IDE)에서 "기호 이름 바꾸기" 명령을 클릭하기만 하면 이러한 항목을 리팩토링할 수 있습니다.

앱 이름을 expApp으로 변경(큰 미리보기)

JavaScript와 같은 동적으로 유형이 지정된 언어에서 여러 파일을 동시에 리팩터링하는 유일한 방법은 정규식(RegExp)을 사용하는 기존의 "검색 및 바꾸기" 기능을 사용하는 것입니다.

TypeScript와 같은 정적으로 유형이 지정된 언어에서는 "검색 및 바꾸기"가 더 이상 필요하지 않습니다. "모든 항목 찾기" 및 "기호 이름 바꾸기"와 같은 IDE 명령을 사용하면 앱에서 개체 인터페이스의 주어진 함수, 클래스 또는 속성의 모든 항목을 볼 수 있습니다.

TypeScript는 리팩토링된 비트의 모든 인스턴스를 찾고 이름을 바꾸고 리팩토링 후 코드에 유형 불일치가 있는 경우 컴파일 오류로 경고하는 데 도움이 됩니다.

TypeScript는 여기에서 다룬 것보다 훨씬 더 많은 이점이 있습니다.

TypeScript의 단점

TypeScript는 위에서 강조한 유망한 기능을 감안하더라도 확실히 단점이 없는 것은 아닙니다.

잘못된 보안 감각

TypeScript의 유형 검사 기능은 종종 개발자들 사이에서 잘못된 보안 감각을 만듭니다. 유형 검사는 실제로 코드에 문제가 있을 때 경고합니다. 그러나 정적 유형은 전체 버그 밀도를 줄이지 않습니다.

따라서 유형은 개발자가 작성하고 런타임에 확인하지 않기 때문에 프로그램의 강도는 TypeScript 사용에 따라 달라집니다.

버그를 줄이기 위해 TypeScript를 찾고 있다면 대신 테스트 주도 개발을 고려하십시오.

복잡한 타이핑 시스템

타이핑 시스템은 여러 면에서 훌륭한 도구이지만 때때로 약간 복잡할 수 있습니다. 이 단점은 JavaScript와 완벽하게 상호 운용 가능하기 때문에 더 복잡해질 여지가 있습니다.

그러나 TypeScript는 여전히 JavaScript이므로 JavaScript를 이해하는 것이 중요합니다.

언제 TypeScript를 사용합니까?

다음과 같은 경우 TypeScript를 사용하는 것이 좋습니다.

  • 장기간 유지되는 애플리케이션을 구축하려는 경우 TypeScript로 시작하는 것이 좋습니다. TypeScript는 자체 문서화 코드를 촉진하여 다른 개발자가 코드 기반에 합류할 때 코드를 쉽게 이해할 수 있도록 도와주기 때문입니다. .
  • 라이브러리 를 생성해야 하는 경우 TypeScript로 작성하는 것이 좋습니다. 코드 편집자가 라이브러리를 사용하는 개발자에게 적절한 유형을 제안하는 데 도움이 됩니다.

마지막 몇 섹션에서는 TypeScript의 장단점을 균형 있게 조정했습니다. 오늘의 비즈니스로 넘어가 보겠습니다. 최신 React 프로젝트에서 TypeScript를 설정합니다 .

시작하기

React 프로젝트에서 TypeScript를 설정하는 방법에는 여러 가지가 있습니다. 이 튜토리얼에서는 두 가지만 다룰 것입니다.

방법 1: React 앱 + TypeScript 만들기

약 2년 전 React 팀은 TypeScript를 지원하는 Create React App 2.1을 출시했습니다. 따라서 TypeScript를 프로젝트에 적용하기 위해 무거운 작업을 수행할 필요가 없을 수도 있습니다.

Create React App의 TypeScript 발표(큰 미리보기)

새로운 Create React App 프로젝트를 시작하려면 다음을 실행할 수 있습니다.

 npx create-react-app my-app --folder-name

… 아니면 이거:

 yarn create react-app my-app --folder-name

Create React App 프로젝트에 TypeScript를 추가하려면 먼저 해당 프로젝트와 해당 @types 를 설치합니다.

 npm install --save typescript @types/node @types/react @types/react-dom @types/jest

… 또는:

 yarn add typescript @types/node @types/react @types/react-dom @types/jest

다음으로 파일 이름을 변경하고(예: index.jsindex.tsx 로 변경) 개발 서버를 다시 시작하세요 !

그게 빨랐어, 그렇지?

방법 2: Webpack으로 TypeScript 설정

Webpack은 JavaScript 애플리케이션을 위한 정적 모듈 번들러입니다. 애플리케이션에서 모든 코드를 가져와 웹 브라우저에서 사용할 수 있도록 합니다. 모듈은 앱의 JavaScript, node_modules , 이미지 및 CSS 스타일로 빌드된 재사용 가능한 코드 덩어리이며 웹사이트에서 쉽게 사용할 수 있도록 패키징됩니다.

새 프로젝트 만들기

프로젝트에 대한 새 디렉토리를 만드는 것으로 시작하겠습니다.

 mkdir react-webpack cd react-webpack

npm을 사용하여 프로젝트를 초기화합니다.

 npm init -y

위의 명령은 일부 기본값이 있는 package.json 파일을 생성합니다. webpack, TypeScript 및 일부 React 관련 모듈에 대한 종속성을 추가해 보겠습니다.

패키지 설치

마지막으로 필요한 패키지를 설치해야 합니다. 명령줄 인터페이스(CLI)를 열고 다음을 실행합니다.

 #Installing devDependencies npm install --save-dev @types/react @types/react-dom awesome-typescript-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader typescript webpack webpack-cli webpack-dev-server #installing Dependencies npm install react react-dom

react-webpack 폴더 아래에 몇 가지 다른 파일과 폴더를 수동으로 추가해 보겠습니다.

  1. webpack.config.js 를 추가하여 webpack 관련 구성을 추가합니다.
  2. 모든 TypeScript 구성에 대해 tsconfig.json 을 추가합니다.
  3. 새 디렉토리 src 를 추가하십시오.
  4. src 폴더에 새 디렉토리 components 를 만듭니다.
  5. 마지막으로 components 폴더에 index.html , App.tsxindex.tsx 를 추가합니다.

프로젝트 구조

따라서 폴더 구조는 다음과 같습니다.

 ├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.html

일부 코드 추가 시작

index.html 부터 시작하겠습니다.

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-Webpack Setup</title> </head> <body> <div></div> </body> </html>

그러면 ID가 output 인 빈 div 가 있는 HTML이 생성됩니다.

React 구성 요소 App.tsx 에 코드를 추가해 보겠습니다.

 import * as React from "react"; export interface HelloWorldProps { userName: string; lang: string; } export const App = (props: HelloWorldProps) => ( <h1> Hi {props.userName} from React! Welcome to {props.lang}! </h1> );

인터페이스 개체를 만들고 이름을 HelloWorldProps 로 지정했으며 userNamelang 에는 string 유형이 있습니다.

App 구성 요소에 props 을 전달하고 내보냈습니다.

이제 index.tsx 의 코드를 업데이트해 보겠습니다.

 import * as React from "react"; import * as ReactDOM from "react-dom"; import { App } from "./App"; ReactDOM.render( <App userName="Beveloper" lang="TypeScript" />, document.getElementById("output") );

App 구성 요소를 index.tsx 로 가져왔습니다. webpack은 확장자가 .ts 또는 .tsx 인 파일을 발견하면 Awesome-typescript-loader 라이브러리를 사용하여 해당 파일을 변환합니다.

TypeScript 구성

그런 다음 tsconfig.json 에 몇 가지 구성을 추가합니다.

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] }

tsconfig.json 에 추가한 다양한 옵션도 살펴보겠습니다.

  • compilerOptions 옵션 다른 컴파일러 옵션을 나타냅니다.
  • jsx:react .tsx 파일에서 JSX에 대한 지원을 추가합니다.
  • lib 라이브러리 파일 목록을 컴파일에 추가합니다(예: es2015 를 사용하면 ECMAScript 6 구문을 사용할 수 있음).
  • module 모듈 코드를 생성합니다.
  • noImplicitAny 암시적 any 유형이 있는 선언에 대해 오류를 발생시킵니다.
  • outDir 출력 디렉토리를 나타냅니다.
  • sourceMap 앱 디버깅에 매우 유용할 수 있는 .map 파일을 생성합니다.
  • target 코드를 변환할 대상 ECMAScript 버전을 나타냅니다(특정 브라우저 요구 사항에 따라 버전을 추가할 수 있음).
  • include 할 파일 목록을 지정하는 데 사용됩니다.

웹팩 구성

webpack.config.js 에 일부 웹팩 구성을 추가해 보겠습니다.

 const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/components/index.tsx", target: "web", mode: "development", output: { path: path.resolve(\__dirname, "build"), filename: "bundle.js", }, resolve: { extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "awesome-typescript-loader", }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader", }, { test: /\.css$/, loader: "css-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(\__dirname, "src", "components", "index.html"), }), new MiniCssExtractPlugin({ filename: "./src/yourfile.css", }), ], };

webpack.config.js 에 추가한 다양한 옵션을 살펴보겠습니다.

  • entry 이것은 우리 앱의 진입점을 지정합니다. 빌드에 포함하려는 단일 파일 또는 파일 배열일 수 있습니다.
  • output 여기에는 출력 구성이 포함됩니다. 앱은 프로젝트에서 디스크로 번들 코드를 출력하려고 할 때 이것을 확인합니다. 경로는 코드가 출력될 출력 디렉터리를 나타내며 파일 이름은 같은 파일 이름을 나타냅니다. 일반적으로 이름이 bundle.js 입니다.
  • resolve Webpack은 이 속성을 보고 파일을 묶을지 건너뛸지 결정합니다. 따라서 우리 프로젝트에서 webpack은 번들링을 위해 확장자가 .js , .jsx , .json , .ts.tsx 인 파일을 고려합니다.
  • module 로더를 사용하여 앱이 요청할 때 웹팩이 특정 파일을 로드하도록 할 수 있습니다. 다음을 지정하는 규칙 개체가 필요합니다.
    • 확장자가 .tsx 또는 .ts 로 끝나는 모든 파일은 로드할 Awesome awesome-typescript-loader 를 사용해야 합니다.
    • .js 확장자로 끝나는 파일은 source-map-loader 로 로드해야 합니다.
    • .css 확장자로 끝나는 파일은 css-loader 로 로드해야 합니다.
  • plugins Webpack은 고유한 한계가 있으며 이를 극복하고 기능을 확장할 수 있는 플러그인을 제공합니다. 예를 들어, html-webpack-plugin./src/component/index.html 디렉토리의 index.html 파일에서 브라우저로 렌더링되는 템플릿 파일을 생성합니다.

MiniCssExtractPlugin 은 앱의 상위 CSS 파일을 렌더링합니다.

package.json에 스크립트 추가하기

package.json 파일에 React 앱을 빌드하기 위해 다른 스크립트를 추가할 수 있습니다.

 "scripts": { "start": "webpack-dev-server --open", "build": "webpack" },

이제 CLI에서 npm start 를 실행합니다. 모든 것이 잘 되었다면 다음이 표시되어야 합니다.

React-Webpack 설정 출력(큰 미리보기)

웹팩에 소질이 있는 경우 이 설정을 위한 저장소를 복제하고 프로젝트 전체에서 사용하십시오.

파일 생성

src 폴더와 index.tsx 파일을 생성합니다. 이것은 React를 렌더링하는 기본 파일이 될 것입니다.

이제 npm start 를 실행하면 서버가 실행되고 새 탭이 열립니다. npm run build 를 실행하면 프로덕션용 웹팩이 빌드되고 빌드 폴더가 생성됩니다.

Create React App 및 webpack 구성 방법을 사용하여 처음부터 TypeScript를 설정하는 방법을 살펴보았습니다.

TypeScript를 완전히 이해하는 가장 빠른 방법 중 하나는 기존의 기본 React 프로젝트 중 하나를 TypeScript로 변환하는 것입니다. 불행히도 기존의 바닐라 React 프로젝트에서 TypeScript를 점진적으로 채택하는 것은 모든 파일을 꺼내거나 이름을 바꿔야 하기 때문에 스트레스를 받습니다. 프로젝트가 대규모 팀에 속한 경우 충돌과 거대한 pull 요청이 발생할 수 있습니다.

다음으로 React 프로젝트를 TypeScript로 쉽게 마이그레이션하는 방법을 살펴보겠습니다.

기존 Create React 앱을 TypeScript로 마이그레이션

이 프로세스를 보다 쉽게 ​​관리할 수 있도록 개별 청크로 마이그레이션할 수 있는 단계로 나눕니다. 다음은 프로젝트를 마이그레이션하기 위해 수행할 단계입니다.

  1. TypeScript 및 유형을 추가합니다.
  2. tsconfig.json 을 추가합니다.
  3. 작게 시작하세요.
  4. 파일 확장명을 .tsx 로 바꿉니다.

1. 프로젝트에 TypeScript 추가

먼저 프로젝트에 TypeScript를 추가해야 합니다. React 프로젝트가 Create React App으로 부트스트랩되었다고 가정하면 다음을 실행할 수 있습니다.

 # Using npm npm install --save typescript @types/node @types/react @types/react-dom @types/jest # Using Yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest

TypeScript는 아직 아무것도 변경하지 않았습니다. 프로젝트를 로컬로 시작하는 명령( npm start 또는 yarn start )을 실행하면 아무 것도 변경되지 않습니다. 그렇다면 훌륭합니다! 다음 단계를 위한 준비가 되었습니다.

2. tsconfig.json 파일 추가

TypeScript를 활용하기 전에 tsconfig.json 파일을 통해 구성해야 합니다. 시작하는 가장 간단한 방법은 다음 명령을 사용하여 스캐폴드하는 것입니다.

 npx tsc --init

이렇게 하면 많은 주석이 달린 코드와 함께 몇 가지 기본 사항이 제공됩니다. 이제 tsconfig.json 의 모든 코드를 다음으로 바꿉니다.

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }

TypeScript 구성

tsconfig.json 에 추가한 다양한 옵션도 살펴보겠습니다.

  • compilerOptions 옵션 다른 컴파일러 옵션을 나타냅니다.
    • target 최신 JavaScript 구문을 ECMAScript 5와 같은 이전 버전으로 변환합니다.
    • lib 라이브러리 파일 목록을 컴파일에 추가합니다(예: es2015를 사용하면 ECMAScript 6 구문을 사용할 수 있음).
    • jsx:react .tsx 파일에서 JSX에 대한 지원을 추가합니다.
    • lib 라이브러리 파일 목록을 컴파일에 추가합니다(예: es2015를 사용하면 ECMAScript 6 구문을 사용할 수 있음).
    • module 모듈 코드를 생성합니다.
    • noImplicitAny 암시적 any 유형의 선언에 대해 오류를 발생시키는 데 사용됩니다.
    • outDir 출력 디렉토리를 나타냅니다.
    • sourceMap 앱 디버깅에 매우 유용할 수 있는 .map 파일을 생성합니다.
    • include 할 파일 목록을 지정하는 데 사용됩니다.

구성 옵션은 프로젝트 수요에 따라 달라집니다. 프로젝트에 적합한 것이 무엇인지 파악하기 위해 TypeScript 옵션 스프레드시트를 확인해야 할 수도 있습니다.

준비를 위해 필요한 조치만 취했습니다. 다음 단계는 파일을 TypeScript로 마이그레이션하는 것입니다.

3. 간단한 구성 요소로 시작

점차적으로 채택되는 TypeScript의 기능을 활용하십시오. 원하는 속도로 한 번에 한 파일씩 이동합니다. 귀하와 귀하의 팀에 의미 있는 일을 하십시오. 한 번에 모든 것을 해결하려고 하지 마십시오.

이것을 제대로 변환하려면 두 가지 작업을 수행해야 합니다.

  1. 파일 확장자를 .tsx 로 변경합니다.
  2. 유형 주석을 추가합니다(일부 TypeScript 지식이 필요함).

4. 파일 확장명을 .tsx 로 변경

대규모 코드 기반에서는 파일 이름을 개별적으로 변경하는 것이 피곤해 보일 수 있습니다.

macOS에서 여러 파일 이름 바꾸기

여러 파일의 이름을 바꾸는 것은 시간 낭비일 수 있습니다. 다음은 Mac에서 수행하는 방법입니다. 이름을 바꾸려는 파일이 포함된 폴더를 마우스 오른쪽 버튼으로 클릭(또는 Ctrl 를 누른 채 클릭하거나, MacBook을 사용하는 경우 트랙패드에서 두 손가락으로 동시에 클릭)합니다. 그런 다음 "Finder에 표시"를 클릭하십시오. Finder에서 이름을 바꾸려는 파일을 모두 선택합니다. 선택한 파일을 마우스 오른쪽 버튼으로 클릭하고 "X 항목 이름 바꾸기..."를 선택하면 다음과 같이 표시됩니다.

Mac에서 파일 이름 바꾸기(큰 미리보기)

찾고자 하는 문자열과 찾은 문자열을 대체할 문자열을 삽입하고 "이름 바꾸기"를 누르십시오. 완료.

Windows에서 여러 파일 이름 바꾸기

Windows에서 여러 파일의 이름을 바꾸는 것은 이 자습서의 범위를 벗어나지만 전체 가이드를 사용할 수 있습니다. 일반적으로 파일 이름을 바꾼 후 오류가 발생합니다. 유형 주석을 추가하기만 하면 됩니다. 문서에서 이에 대해 설명할 수 있습니다.

React 앱에서 TypeScript를 설정하는 방법을 다루었습니다. 이제 TypeScript를 사용하여 Money Heist 용 에피소드 선택 앱을 빌드해 보겠습니다.

TypeScript의 기본 유형은 다루지 않습니다. 이 튜토리얼을 계속하기 전에 문서를 살펴봐야 합니다.

구축 시간

이 프로세스가 덜 힘들게 느껴지도록 하기 위해 이 단계를 단계로 나누어 개별 청크로 앱을 빌드할 수 있습니다. 다음은 Money Heist 에피소드 선택기를 구축하기 위해 수행할 모든 단계입니다.

  • Create React 앱을 스캐폴드합니다.
  • 에피소드를 가져옵니다.
    • interface.ts 에서 에피소드에 적합한 유형과 interface.ts 를 만듭니다.
    • store.tsx 에서 에피소드를 가져오기 위해 저장소를 설정합니다.
    • action.ts 에서 에피소드를 가져오기 위한 작업을 만듭니다.
    • 가져온 에피소드를 포함하는 EpisodeList.tsx 구성 요소를 만듭니다.
    • React Lazy and Suspense 를 사용하여 EpisodesList 구성 요소를 홈 페이지로 가져옵니다.
  • 에피소드를 추가합니다.
    • store.tsx 에 에피소드를 추가하도록 store를 설정하세요.
    • action.ts 에 에피소드를 추가하기 위한 작업을 만듭니다.
  • 에피소드를 제거합니다.
    • store.tsx 에서 에피소드를 삭제하기 위한 저장소를 설정합니다.
    • action.ts 에서 에피소드 삭제를 위한 작업을 만듭니다.
  • 좋아하는 에피소드.
    • 좋아하는 에피소드의 EpisodesList 구성 요소를 가져옵니다.
    • 좋아하는 EpisodesList 내에서 에피소드 목록을 렌더링합니다.
  • 탐색을 위해 도달 라우터를 사용합니다.

반응 설정

React를 설정하는 가장 쉬운 방법은 Create React App을 사용하는 것입니다. Create React App은 공식적으로 지원되는 단일 페이지 React 애플리케이션을 만드는 방법입니다. 구성이 없는 최신 빌드 설정을 제공합니다.

우리는 그것을 사용하여 우리가 구축할 애플리케이션을 부트스트랩할 것입니다. CLI에서 아래 명령을 실행합니다.

 npx create-react-app react-ts-app && cd react-ts-app

설치가 성공하면 npm start 를 실행하여 React 서버를 시작합니다.

React 시작 페이지(큰 미리보기)

Typescript의 인터페이스와 유형 이해하기

TypeScript의 인터페이스는 객체 속성에 유형을 제공해야 할 때 사용됩니다. 따라서 인터페이스를 사용하여 유형을 정의합니다.

 interface Employee { name: string, role: string salary: number } const bestEmployee: Employee= { name: 'John Doe', role: 'IOS Developer', salary: '$8500' //notice we are using a string }

위의 코드를 컴파일할 때 "부동산 salary 유형이 호환되지 않습니다. 유형 string 은 유형 number 에 할당할 수 없습니다."

이러한 오류는 속성이나 변수에 정의된 유형이 아닌 유형이 할당될 때 TypeScript에서 발생합니다. 특히 위의 스니펫은 salary 속성에 number 유형 대신 string 유형이 할당되었음을 의미합니다.

src 폴더에 interface.ts 파일을 생성합시다. 이 코드를 복사하여 붙여넣습니다.

 /** |-------------------------------------------------- | All the interfaces! |-------------------------------------------------- */ export interface IEpisode { airdate: string airstamp: string airtime: string id: number image: { medium: string; original: string } name: string number: number runtime: number season: number summary: string url: string } export interface IState { episodes: Array<IEpisode> favourites: Array<IEpisode> } export interface IAction { type: string payload: Array<IEpisode> | any } export type Dispatch = React.Dispatch<IAction> export type FavAction = ( state: IState, dispatch: Dispatch, episode: IEpisode ) => IAction export interface IEpisodeProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> } export interface IProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> }

인터페이스 이름에 "I"를 추가하는 것이 좋습니다. 코드를 읽을 수 있게 해줍니다. 그러나 귀하는 이를 제외하기로 결정할 수 있습니다.

IEpisode 인터페이스

API는 airdate , airstamp , airtime , id , image , name , number , runtime , season , summary , url 과 같은 속성 집합을 반환합니다. 따라서 IEpisode 인터페이스를 정의하고 적절한 데이터 유형을 개체 속성에 설정했습니다.

IState 인터페이스

IState 인터페이스에는 각각 episodesfavorites 속성과 Array<IEpisode> 인터페이스가 있습니다.

IA액션

IAction 인터페이스 속성은 payloadtype 입니다. type 속성에는 문자열 유형이 있고 페이로드에는 Array | any Array | any .

Array | any Array | any 는 에피소드 인터페이스 또는 모든 유형의 배열을 의미합니다.

Dispatch 유형은 React.Dispatch<IAction> 인터페이스로 설정됩니다. @types/react 코드 기반에 따르면 React.Dispatchdispatch 함수의 표준 유형이고 <IAction> 은 Interface 작업의 배열입니다.

또한 Visual Studio Code에는 TypeScript 검사기가 있습니다. 따라서 코드를 강조 표시하거나 마우스를 가져가면 적절한 유형을 제안할 수 있습니다.

즉, 앱 전체에서 인터페이스를 사용하려면 인터페이스를 내보내야 합니다. 지금까지 객체의 유형을 보유하는 저장소와 인터페이스가 있습니다. 이제 스토어를 생성해 보겠습니다. 다른 인터페이스는 설명된 것과 동일한 규칙을 따릅니다.

에피소드 가져오기

상점 만들기

에피소드를 가져오려면 데이터의 초기 상태를 유지하고 리듀서 기능을 정의하는 저장소가 필요합니다.

우리는 그것을 설정하기 위해 useReducer 후크를 사용할 것입니다. src 폴더에 store.tsx 파일을 만듭니다. 다음 코드를 복사하여 붙여넣습니다.

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

다음은 상점을 만들기 위해 취한 단계입니다.

  • 스토어를 정의할 때 React의 useReducer 후크와 createContext API가 필요하기 때문에 가져왔습니다.
  • ./types/interfaces 에서 IStateIAction 을 가져 ./types/interfaces .
  • IState 유형으로 initialState 객체를 선언했고, 에피소드 및 즐겨찾기 속성은 각각 빈 배열로 설정되었습니다.
  • 다음으로 createContext 메소드를 보유하고 initialState 를 전달하는 Store 변수를 생성했습니다.

createContext 메소드 유형은 <IState | any> <IState | any> , 이는 <IState> 또는 any 의 유형이 될 수 있음을 의미합니다. 이 기사에서 자주 사용되는 any 유형을 볼 것입니다.

  • 다음으로 reducer 함수를 선언하고 stateaction 을 매개변수로 전달했습니다. reducer 함수에는 action.type 값을 확인하는 switch 문이 있습니다. 값이 FETCH_DATA 이면 상태 (...state) 및 작업 페이로드를 보유하는 에피소드 상태의 복사본이 있는 객체를 반환합니다.
  • switch 문에서 default 상태를 반환합니다.

리듀서 함수의 stateaction 매개변수에는 각각 IStateIAction 유형이 있습니다. 또한 reducer 함수에는 IState 유형이 있습니다.

  • 마지막으로 StoreProvider 함수를 선언했습니다. 이렇게 하면 앱의 모든 구성 요소가 스토어에 액세스할 수 있습니다.
  • 이 함수는 children 을 소품으로 사용하고 StorePrivder 함수 내에서 useReducer 후크를 선언했습니다.
  • 우리는 statedispatch 를 ​​분해했습니다.
  • 모든 구성 요소가 스토어에 액세스할 수 있도록 하기 위해 statedispatch 를 포함하는 객체 값을 전달했습니다.

에피소드와 즐겨찾기 state 를 포함하는 상태는 다른 구성 요소에서 액세스할 수 있게 되며 dispatch 는 상태를 변경하는 함수입니다.

  • StoreStoreProvider 를 내보내어 애플리케이션 전체에서 사용할 수 있습니다.

Action.ts 만들기

사용자에게 표시될 에피소드를 가져오려면 API에 요청해야 합니다. 이것은 작업 파일에서 수행됩니다. Action.ts 파일을 만들고 다음 코드를 붙여넣습니다.

 import { Dispatch } from './interface/interfaces' export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) }

먼저 이 파일에서 사용할 수 있도록 인터페이스를 가져와야 합니다. 작업을 생성하기 위해 다음 단계를 수행했습니다.

  • fetchDataAction 함수는 dispatch 소품을 매개변수로 사용합니다.
  • 함수가 비동기식이므로 asyncawait 를 사용합니다.
  • API 엔드포인트를 보유하는 변수( URL )를 생성합니다.
  • API의 응답을 보유하는 data 라는 또 다른 변수가 있습니다.
  • 그런 다음 data.json() 을 호출하여 JSON 형식의 응답을 얻은 후 JSON 응답을 dataJSON 에 저장합니다.
  • 마지막으로 type 속성과 FETCH_DATA 문자열을 가진 디스패치 함수를 반환합니다. 또한 payload() 가 있습니다. _embedded.episodesendpoint 의 에피소드 객체 배열입니다.

fetchDataAction 함수는 엔드포인트를 가져와 JSON 객체로 변환하고 스토어에서 이전에 선언된 상태를 업데이트하는 디스패치 함수를 반환합니다.

내보낸 디스패치 유형은 React.Dispatch 로 설정됩니다. React.Dispatch@types/react 코드 기반에 따른 디스패치 함수의 표준 유형이고 <IAction> 은 Interface Action의 배열입니다.

에피소드 목록 구성 요소

앱의 재사용성을 유지하기 위해 가져온 모든 에피소드를 별도의 파일에 보관한 다음 homePage 구성 요소에서 파일을 가져옵니다.

components 폴더에서 EpisodesList.tsx 파일을 만들고 다음 코드를 복사하여 붙여넣습니다.

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => { const { episodes } = props return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Season: {episode.season} Number: {episode.number} </div> <button type='button' > Fav </button> </section> </section> ) }) } export default EpisodesList
  • interfaces.tsx 에서 IEpisodeIProps 를 가져옵니다.
  • 다음으로 소품을 사용하는 EpisodesList 함수를 만듭니다. 소품의 유형은 IProps 이고 함수의 유형은 Array<JSX.Element> 입니다.

Visual Studio Code에서는 함수 유형을 JSX.Element[] 로 작성할 것을 제안합니다.

Visual Studio Code에서 제안하는 유형(큰 미리 보기)

Array<JSX.Element>JSX.Element[]Array<JSX.Element> 는 일반 ID라고 합니다. 따라서 이 기사에서는 일반 패턴을 자주 사용합니다.

  • 함수 내에서 IEpisode 를 유형으로 갖는 props 에서 episodes 를 구조화합니다.

일반 ID에 대해 읽으십시오. 이 지식은 계속 진행하는 동안 필요합니다.

  • 우리는 episodes 소품을 반환하고 이를 통해 매핑하여 몇 가지 HTML 태그를 반환했습니다.
  • 첫 번째 섹션에는 key episode.id 와 나중에 생성될 episode-boxclassName 이 있습니다. 우리는 에피소드에 이미지가 있다는 것을 알고 있습니다. 따라서 이미지 태그입니다.
  • 이미지에는 episode.image 또는 episode.image.medium 이 있는지 확인하는 삼항 연산자가 있습니다. 그렇지 않으면 이미지가 발견되지 않으면 빈 문자열을 표시합니다. 또한 div에 episode.name 을 포함했습니다.

section 에서는 에피소드가 속한 시즌과 번호를 보여줍니다. 텍스트가 Fav 인 버튼이 있습니다. 앱 전체에서 사용할 수 있도록 EpisodesList 구성 요소를 내보냈습니다.

홈 페이지 구성 요소

홈페이지에서 API 호출을 트리거하고 우리가 만든 EpisodesList 구성 요소를 사용하여 에피소드를 표시하기를 원합니다. components 폴더 내에서 HomePage 구성 요소를 만들고 다음 코드를 복사하여 붙여넣습니다.

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { fetchDataAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch } } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
  • React에서 useContext , useEffect , lazy , Suspense 를 가져옵니다. 가져온 앱 구성 요소는 다른 모든 구성 요소가 스토어의 가치를 받아야 하는 기반입니다.
  • 또한 각각의 파일에서 Store , IEpisodePropsFetchDataAction 을 가져옵니다.
  • React 16.6에서 사용할 수 있는 React.lazy 기능을 사용하여 EpisodesList 구성 요소를 가져옵니다.

React 지연 로딩은 코드 분할 규칙을 지원합니다. 따라서 EpisodesList 구성 요소는 한 번에 로드되는 대신 동적으로 로드되므로 앱의 성능이 향상됩니다.

  • 우리는 state 를 구조화하고 Store 에서 소품으로 dispatch 합니다.
  • useEffect 후크의 앰퍼샌드(&&)는 에피소드 상태가 empty 있는지(또는 0과 같은지) 확인합니다. 그렇지 않으면 fetchDataAction 함수를 반환합니다.
  • 마지막으로 App 구성 요소를 반환합니다. 그 안에서 우리는 Suspense 래퍼를 사용하고 loading 텍스트가 있는 div로 fallback 을 설정합니다. 이것은 API의 응답을 기다리는 동안 사용자에게 표시됩니다.
  • EpisodesList 구성 요소는 데이터를 사용할 수 있을 때 마운트되며 episodes 를 포함할 데이터는 우리가 여기에 퍼뜨립니다.

Index.txs 설정

Homepage 구성 요소는 StoreProvider 의 자식이어야 합니다. index 파일에서 이를 수행해야 합니다. index.js 의 이름을 index.tsx 로 바꾸고 다음 코드를 붙여넣습니다.

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store' import HomePage from './components/HomePage' ReactDOM.render( <StoreProvider> <HomePage /> </StoreProvider>, document.getElementById('root') )

각각의 파일에서 StoreProvider , HomePageindex.css 를 가져옵니다. StoreProvider 에서 HomePage 구성 요소를 래핑합니다. 이렇게 하면 이전 섹션에서 본 것처럼 Homepage 구성 요소가 저장소에 액세스할 수 있습니다.

우리는 먼 길을 왔습니다. Let's check what the app looks like, without any CSS.

App without CSS (Large preview)

Create Index.css

Delete the code in the index.css file and replace it with this:

 html { font-size: 14px; } body { margin: 0; padding: 0; font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .episode-layout { display: flex; flex-wrap: wrap; min-width: 100vh; } .episode-box { padding: .5rem; } .header { display: flex; justify-content: space-between; background: white; border-bottom: 1px solid black; padding: .5rem; position: sticky; top: 0; }

Our app now has a look and feel. Here's how it looks with CSS.

(큰 미리보기)

Now we see that our episodes can finally be fetched and displayed, because we've adopted TypeScript all the way. Great, isn't it?

Add Favorite Episodes Feature

Let's add functionality that adds favorite episodes and that links it to a separate page. Let's go back to our Store component and add a few lines of code:

Note that the highlighted code is newly added:

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload }
 case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider> }

To implement the “Add favorite” feature to our app, the ADD_FAV case is added. It returns an object that holds a copy of our previous state, as well as an array with a copy of the favorite state , with the payload .

We need an action that will be called each time a user clicks on the FAV button. Let's add the highlighted code to index.tx :

 import { IAction, IEpisode, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON._embedded.episodes }) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }

We create a toggleFavAction function that takes dispatch and episodes as parameters, and any and IEpisode|any as their respective types, with IAction as our function type. We have an object whose type is ADD_FAV and that has episode as its payload. Lastly, we just return and dispatch the object.

우리는 EpisodeList.tsx 에 몇 가지 스니펫을 더 추가할 것입니다. 강조 표시된 코드를 복사하여 붙여넣습니다.

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => {
 const { episodes, toggleFavAction, favourites, store } = props const { state, dispatch } = store

 return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist - ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Seasion: {episode.season} Number: {episode.number} </div> <button type='button'
 onClick={() => toggleFavAction(state, dispatch, episode)} > {favourites.find((fav: IEpisode) => fav.id === episode.id) ? 'Unfav' : 'Fav'}
 </button> </section> </section> ) }) } export default EpisodesList

우리는 togglefavaction , favoritesstore 를 소품으로 포함하고 state , store의 dispatch 를 ​​구조화합니다. 좋아하는 에피소드를 선택하기 위해 onClick 이벤트에 toggleFavAction 메서드를 포함하고 state , dispatchepisode props를 함수에 인수로 전달합니다.

마지막으로 fav.id (즐겨찾기 ID)가 episode.id 와 일치하는지 확인하기 위해 favorite 상태를 반복합니다. 그렇다면 UnfavFav 텍스트 사이를 전환합니다. 이렇게 하면 사용자가 해당 에피소드를 즐겨찾기했는지 여부를 알 수 있습니다.

막바지에 다다르고 있습니다. 그러나 사용자가 홈 페이지에서 에피소드를 선택할 때 좋아하는 에피소드를 연결할 수 있는 페이지가 여전히 필요합니다.

여기까지 왔다면 등을 두드려 주세요.

즐겨찾기 구성 요소

components 폴더에서 FavPage.tsx 파일을 만듭니다. 다음 코드를 복사하여 붙여넣습니다.

 import React, { lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { toggleFavAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) export default function FavPage(): JSX.Element { const { state, dispatch } = React.useContext(Store) const props: IEpisodeProps = { episodes: state.favourites, store: { state, dispatch }, toggleFavAction, favourites: state.favourites } return ( <App> <Suspense fallback={<div>loading...</div>}> <div className='episode-layout'> <EpisodesList {...props} /> </div> </Suspense> </App> ) }

좋아하는 에피소드를 선택하는 논리를 만들기 위해 약간의 코드를 작성했습니다. React에서 lazySuspense 를 가져옵니다. 또한 각각의 파일에서 Store , IEpisodePropstoggleFavAction 을 가져옵니다.

React.lazy 기능을 사용하여 EpisodesList 구성 요소를 가져옵니다. 마지막으로 App 구성 요소를 반환합니다. 그 안에서 우리는 Suspense 래퍼를 사용하고 로딩 텍스트가 있는 div로 폴백을 설정합니다.

이것은 Homepage 구성 요소와 유사하게 작동합니다. 이 구성 요소는 사용자가 즐겨찾는 에피소드를 얻기 위해 스토어에 액세스합니다. 그런 다음 에피소드 목록이 EpisodesList 구성 요소에 전달됩니다.

HomePage.tsx 파일에 몇 가지 스니펫을 더 추가해 보겠습니다.

../ActionstoggleFavAction 을 포함합니다. 또한 toggleFavAction 메서드를 소품으로 포함합니다.

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces'
import { fetchDataAction, toggleFavAction } from '../Actions'
const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch },
 toggleFavAction, favourites: state.favourites
 } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage

FavPage 를 연결해야 하므로 App.tsx 의 헤더에 링크가 필요합니다. 이를 위해 React Router와 유사한 라이브러리인 Reach Router를 사용합니다. William Le는 Reach Router와 React Router의 차이점을 설명합니다.

CLI에서 npm install @reach/router @types/reach__router 를 실행합니다. 도달 라우터 라이브러리와 reach-router 유형을 모두 설치하고 있습니다.

성공적으로 설치되면 @reach/router 에서 Link 를 가져옵니다.

 import React, { useContext, Fragment } from 'react' import { Store } from './tsx'
import { Link } from '@reach/router'
 const App = ({ children }: { children: JSX.Element }): JSX.Element => {
 const { state } = useContext(Store)
return ( <Fragment> <header className='header'> <div> <h1>Money Heist</h1> <p>Pick your favourite episode</p> </div>
 <div> <Link to='/'>Home</Link> <Link to='/faves'>Favourite(s): {state.favourites.length}</Link> </div>
 </header> {children} </Fragment> ) } export default App

useContext 에서 저장소를 구조화합니다. 마지막으로, 우리 집에는 Link/ 에 대한 경로가 있고, 우리가 즐겨찾는 경로에는 /faves 에 대한 경로가 있습니다.

{state.favourites.length} 는 즐겨찾기 상태의 에피소드 수를 확인하여 표시합니다.

마지막으로 index.tsx 파일에서 FavPageHomePage 구성 요소를 각각 가져와서 Router 에 래핑합니다.

강조 표시된 코드를 기존 코드에 복사합니다.

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store'
import { Router, RouteComponentProps } from '@reach/router' import HomePage from './components/HomePage' import FavPage from './components/FavPage' const RouterPage = ( props: { pageComponent: JSX.Element } & RouteComponentProps ) => props.pageComponent
ReactDOM.render( <StoreProvider>
 <Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router>
 </StoreProvider>, document.getElementById('root') )

이제 구현 ADD_FAV 가 어떻게 작동하는지 봅시다.

"즐겨찾기 추가" 코드 작동(큰 미리보기)

즐겨찾는 기능 제거

마지막으로 "에피소드 제거 기능"을 추가하여 버튼을 클릭하면 좋아하는 에피소드를 추가하거나 제거할 수 있습니다. 헤더에 추가되거나 제거된 에피소드 수가 표시됩니다.

가게

"좋아하는 에피소드 제거" 기능을 만들기 위해 스토어에 다른 케이스를 추가합니다. 따라서 Store.tsx 로 이동하여 강조 표시된 코드를 추가합니다.

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 case 'REMOVE_FAV': return { ...state, favourites: action.payload }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

REMOVE_FAV 라는 또 다른 케이스를 추가하고 initialState 의 복사본을 포함하는 객체를 반환합니다. 또한 favorites 상태에는 작업 페이로드가 포함됩니다.

동작

다음 강조 표시된 코드를 복사하여 action.ts 에 붙여넣습니다.

  { IAction, IEpisode, IState, Dispatch } from './types/interfaces' import
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) } //Add IState withits type
export const toggleFavAction = (state: IState, dispatch: any, episode: IEpisode | any): IAction => { const episodeInFav = state.favourites.includes(episode)
 let dispatchObj = { type: 'ADD_FAV', payload: episode }
 if (episodeInFav) { const favWithoutEpisode = state.favourites.filter( (fav: IEpisode) => fav.id !== episode.id ) dispatchObj = { type: 'REMOVE_FAV', payload: favWithoutEpisode }
 } return dispatch(dispatchObj) }

./types/interfaces 에서 IState 인터페이스를 가져옵니다. 이는 toggleFavAction 함수에서 state props에 유형으로 전달해야 하기 때문입니다.

favorites 상태에 있는 에피소드가 있는지 확인하기 위해 episodeInFav 변수가 생성됩니다.

즐겨찾기 상태를 필터링하여 즐겨찾기 ID가 에피소드 ID와 같지 않은지 확인합니다. 따라서 dispatchObj 에는 favWithoutEpisode 유형과 REMOVE_FAV 페이로드가 재할당됩니다.

앱의 결과를 미리 보겠습니다.

결론

이 기사에서는 React 프로젝트에서 TypeScript를 설정하는 방법과 기본 React에서 TypeScript로 프로젝트를 마이그레이션하는 방법을 살펴보았습니다.

또한 TypeScript와 React로 앱을 빌드하여 React 프로젝트에서 TypeScript가 어떻게 사용되는지 확인했습니다. 나는 당신이 몇 가지를 배울 수 있다고 믿습니다.

아래 의견 섹션에서 TypeScript에 대한 피드백과 경험을 공유하십시오. 나는 당신이 무엇을 생각해내는지 보고 싶습니다!

이 문서의 지원 리포지토리는 GitHub에서 사용할 수 있습니다.

참고문헌

  1. "React 앱을 TypeScript로 마이그레이션하는 방법" Joe Previte
  2. "React 앱에서 TypeScript를 사용하는 이유와 방법", Mahesh Haldar