การตั้งค่า TypeScript สำหรับโปรเจ็กต์ React สมัยใหม่โดยใช้ Webpack
เผยแพร่แล้ว: 2022-03-10ในยุคของการพัฒนาซอฟต์แวร์นี้ JavaScript สามารถใช้ในการพัฒนาแอพได้แทบทุกประเภท อย่างไรก็ตาม ความจริงที่ว่า JavaScript ถูกพิมพ์แบบไดนามิกอาจเป็นปัญหาสำหรับบริษัทองค์กรขนาดใหญ่ส่วนใหญ่ เนื่องจากคุณลักษณะการตรวจสอบประเภทหลวม
โชคดีที่เราไม่ต้องรอจนกว่า Ecma Technical Committee 39 จะแนะนำระบบประเภทสแตติกใน JavaScript เราสามารถใช้ TypeScript แทนได้
จาวาสคริปต์ที่พิมพ์แบบไดนามิกจะไม่ทราบชนิดข้อมูลของตัวแปรจนกว่าตัวแปรนั้นจะถูกสร้างอินสแตนซ์ตอนรันไทม์ นักพัฒนาที่เขียนโปรแกรมซอฟต์แวร์ขนาดใหญ่อาจมีแนวโน้มที่จะกำหนดตัวแปรใหม่ ซึ่งประกาศไว้ก่อนหน้านี้เป็นค่าประเภทอื่น โดยไม่มีคำเตือนหรือปัญหาใดๆ เลย ส่งผลให้จุดบกพร่องมักถูกมองข้าม
ในบทช่วยสอนนี้ เราจะเรียนรู้ว่า TypeScript คืออะไรและจะใช้งานมันอย่างไรในโครงการ React ในตอนท้าย เราจะได้สร้างโปรเจ็กต์ที่ประกอบด้วยแอพตัวเลือกตอนสำหรับรายการทีวี Money Heist โดยใช้ TypeScript และ React-like hooks ปัจจุบัน ( useState
, useEffect
, useReducer
, useContext
) ด้วยความรู้นี้ คุณสามารถทดลองกับ TypeScript ในโครงการของคุณเองได้
บทความนี้ไม่ใช่ข้อมูลเบื้องต้นเกี่ยวกับ TypeScript ดังนั้น เราจะไม่พูดถึงรูปแบบพื้นฐานของ TypeScript และ JavaScript อย่างไรก็ตาม คุณไม่จำเป็นต้องเป็นผู้เชี่ยวชาญในภาษาใดๆ เหล่านี้เพื่อปฏิบัติตาม เพราะเราจะพยายามปฏิบัติตามหลักการของ KISS (พูดง่ายๆ ว่าโง่)
TypeScript คืออะไร?
ในปี 2019 TypeScript อยู่ในอันดับที่เจ็ดของภาษาที่ใช้มากที่สุดและภาษาที่เติบโตเร็วที่สุดอันดับที่ห้าบน GitHub แต่ TypeScript คืออะไรกันแน่?
ตามเอกสารอย่างเป็นทางการ TypeScript เป็นจาวาสคริปต์ประเภท superset ที่คอมไพล์เป็น JavaScript ธรรมดา ได้รับการพัฒนาและดูแลโดย Microsoft และชุมชนโอเพ่นซอร์ส
“Superset” ในบริบทนี้หมายความว่าภาษานั้นมีคุณสมบัติและฟังก์ชันการทำงานทั้งหมดของ JavaScript และบางส่วน TypeScript เป็นภาษาสคริปต์ที่พิมพ์
ช่วยให้นักพัฒนาสามารถควบคุมฐานโค้ดของตนได้มากขึ้นผ่านคำอธิบายประกอบ คลาส และอินเทอร์เฟซ ซึ่งช่วยให้นักพัฒนาไม่ต้องแก้ไขจุดบกพร่องที่น่ารำคาญในคอนโซลด้วยตนเอง
TypeScript ไม่ได้ถูกสร้างขึ้นเพื่อแก้ไข JavaScript แต่จะขยายบน JavaScript ด้วยคุณสมบัติใหม่ที่มีคุณค่า โปรแกรมใดๆ ที่เขียนด้วย JavaScript ธรรมดาจะทำงานตามที่คาดไว้ใน TypeScript รวมถึงแอปมือถือข้ามแพลตฟอร์มและส่วนหลังใน Node.js
ซึ่งหมายความว่าคุณสามารถเขียนแอป React ใน TypeScript ได้เช่นเดียวกับที่เราจะทำในบทช่วยสอนนี้
ทำไมต้องเป็น TypeScript?
บางทีคุณอาจไม่เชื่อมั่นในความดีของ TypeScript ลองพิจารณาข้อดีบางประการของมัน
แมลงน้อยลง
เราไม่สามารถกำจัดจุดบกพร่องทั้งหมดในโค้ดของเราได้ แต่เราสามารถลดจุดบกพร่องเหล่านั้นได้ TypeScript ตรวจสอบประเภทในเวลาคอมไพล์และแสดงข้อผิดพลาดหากประเภทตัวแปรเปลี่ยนแปลง
ความสามารถในการพบข้อผิดพลาดที่เห็นได้ชัดแต่เกิดขึ้นบ่อยครั้งในช่วงเริ่มต้นนี้ ทำให้การจัดการโค้ดของคุณด้วยประเภทต่างๆ ง่ายขึ้นมาก
การรีแฟคเตอร์ทำได้ง่ายขึ้น
คุณมักจะต้องการ refactor หลายๆ อย่าง แต่เนื่องจากมันสัมผัสกับโค้ดอื่นๆ และไฟล์อื่นๆ มากมาย คุณจึงควรระมัดระวังในการแก้ไข
ใน TypeScript สิ่งเหล่านี้มักจะถูกปรับโครงสร้างใหม่ด้วยการคลิกคำสั่ง “เปลี่ยนชื่อสัญลักษณ์” ในสภาพแวดล้อมการพัฒนาแบบรวม (IDE) ของคุณ

ในภาษาที่พิมพ์แบบไดนามิก เช่น JavaScript วิธีเดียวในการจัดโครงสร้างใหม่หลายไฟล์พร้อมกันคือการใช้ฟังก์ชัน "ค้นหาและแทนที่" แบบเดิมโดยใช้นิพจน์ทั่วไป (RegExp)
ในภาษาที่พิมพ์แบบสแตติก เช่น TypeScript ไม่จำเป็นต้องใช้ "ค้นหาและแทนที่" อีกต่อไป ด้วยคำสั่ง IDE เช่น "ค้นหาเหตุการณ์ทั้งหมด" และ "เปลี่ยนชื่อสัญลักษณ์" คุณสามารถดูการเกิดขึ้นทั้งหมดในแอปของฟังก์ชัน คลาส หรือคุณสมบัติของอินเทอร์เฟซวัตถุที่กำหนด
TypeScript จะช่วยคุณค้นหาอินสแตนซ์ทั้งหมดของบิตที่ปรับโครงสร้างใหม่ เปลี่ยนชื่อ และแจ้งเตือนคุณเกี่ยวกับข้อผิดพลาดในการคอมไพล์ ในกรณีที่โค้ดของคุณมีประเภทที่ไม่ตรงกันหลังจากการรีแฟคเตอร์
TypeScript มีข้อดีมากกว่าที่เราได้กล่าวไว้ที่นี่
ข้อเสียของ TypeScript
TypeScript นั้นไม่มีข้อเสียอย่างแน่นอน แม้จะให้คุณสมบัติที่น่าสนใจที่เน้นไว้ด้านบน
ความรู้สึกปลอดภัยที่ผิดพลาด
คุณลักษณะการตรวจสอบประเภทของ TypeScript มักจะสร้างความรู้สึกผิด ๆ ด้านความปลอดภัยในหมู่นักพัฒนา การตรวจสอบประเภทจะเตือนเราอย่างแน่นอนเมื่อมีบางอย่างผิดปกติกับรหัสของเรา อย่างไรก็ตาม ประเภทสแตติกจะไม่ลดความหนาแน่นของจุดบกพร่องโดยรวม
ดังนั้น จุดแข็งของโปรแกรมของคุณจะขึ้นอยู่กับการใช้งาน TypeScript เนื่องจากประเภทต่าง ๆ ถูกเขียนขึ้นโดยผู้พัฒนาและไม่ได้ตรวจสอบในขณะใช้งานจริง
หากคุณต้องการใช้ TypeScript เพื่อลดจุดบกพร่อง โปรดพิจารณาการพัฒนาที่ขับเคลื่อนด้วยการทดสอบแทน
ระบบการพิมพ์ที่ซับซ้อน
ระบบการพิมพ์แม้ว่าจะเป็นเครื่องมือที่ยอดเยี่ยมในหลายๆ ด้าน แต่บางครั้งอาจซับซ้อนเล็กน้อย ข้อเสียนี้เกิดจากการสามารถทำงานร่วมกันได้อย่างสมบูรณ์กับ JavaScript ซึ่งทำให้มีที่ว่างมากขึ้นสำหรับความซับซ้อน
อย่างไรก็ตาม TypeScript ยังคงเป็น JavaScript ดังนั้นการทำความเข้าใจ JavaScript จึงมีความสำคัญ
เมื่อใดควรใช้ TypeScript?
ฉันจะแนะนำให้คุณใช้ TypeScript ในกรณีต่อไปนี้:
- หากคุณต้องการสร้างแอปพลิเคชันที่จะคงอยู่เป็น เวลานาน ฉันขอแนะนำอย่างยิ่งให้เริ่มด้วย TypeScript เพราะมันส่งเสริมรหัสการจัดทำเอกสารด้วยตนเอง ซึ่งช่วยให้นักพัฒนาคนอื่นๆ เข้าใจรหัสของคุณได้อย่างง่ายดายเมื่อพวกเขาเข้าร่วมฐานรหัสของคุณ .
- หากคุณต้องการสร้าง ไลบรารี ให้ลองเขียนใน TypeScript จะช่วยให้ผู้แก้ไขโค้ดแนะนำประเภทที่เหมาะสมกับนักพัฒนาที่ใช้ไลบรารีของคุณ
ในสองสามส่วนสุดท้าย เราได้ปรับสมดุลข้อดีและข้อเสียของ TypeScript ไปที่ธุรกิจของวันนี้กัน: การตั้งค่า TypeScript ในโครงการ React ที่ทันสมัย
เริ่มต้น
มีหลายวิธีในการตั้งค่า TypeScript ในโครงการ React ในบทช่วยสอนนี้ เราจะพูดถึงแค่สองข้อเท่านั้น
วิธีที่ 1: สร้างแอป React + TypeScript
ประมาณสองปีที่แล้วทีม React ได้เปิดตัว Create React App 2.1 พร้อมรองรับ TypeScript ดังนั้น คุณอาจไม่ต้องดำเนินการใดๆ เพื่อนำ TypeScript มาสู่โปรเจ็กต์ของคุณ

ในการเริ่มโครงการ Create React App ใหม่ คุณสามารถเรียกใช้สิ่งนี้...
npx create-react-app my-app --folder-name
… หรือสิ่งนี้:
yarn create react-app my-app --folder-name
ในการเพิ่ม TypeScript ให้กับโปรเจ็กต์ Create React App ก่อนอื่นให้ติดตั้งและ @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.js
เป็น index.tsx
) และ รีสตาร์ทเซิร์ฟเวอร์การพัฒนาของคุณ !
นั่นเร็วใช่มั้ย
วิธีที่ 2: ตั้งค่า TypeScript ด้วย Webpack
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
ของเรา:
- เพิ่ม
webpack.config.js
เพื่อเพิ่มการกำหนดค่าที่เกี่ยวข้องกับ webpack - เพิ่ม
tsconfig.json
สำหรับการกำหนดค่า TypeScript ทั้งหมดของเรา - เพิ่มไดเร็กทอรีใหม่
src
. - สร้างไดเร็กทอรีใหม่
components
ในโฟลเดอร์src
- สุดท้าย เพิ่ม
index.html
,App.tsx
และindex.tsx
ในโฟลเดอร์components
โครงสร้างโครงการ
ดังนั้น โครงสร้างโฟลเดอร์ของเราจะมีลักษณะดังนี้:
├── 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>
สิ่งนี้จะสร้าง HTML โดยมี div
ว่างที่มี ID ของ output
มาเพิ่มโค้ดลงใน React component 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
โดยที่ userName
และ lang
มีประเภท string
เราส่งต่อ props
ไปยังองค์ประกอบ App
ของเราและส่งออก
ตอนนี้มาอัปเดตรหัสใน 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
เพิ่มการรองรับ JSX ในไฟล์ ..tsx
-
lib
เพิ่มรายการไฟล์ไลบรารีในการคอมไพล์ (เช่น การใช้es2015
ช่วยให้เราใช้ไวยากรณ์ ECMAScript 6) -
module
สร้างรหัสโมดูล -
noImplicitAny
เกิดข้อผิดพลาดสำหรับการประกาศโดยนัยany
ประเภท -
outDir
แสดงถึงไดเร็กทอรีเอาต์พุต -
sourceMap
สร้างไฟล์.map
map ซึ่งมีประโยชน์มากสำหรับการดีบักแอป -
target
หมายถึงรุ่น ECMAScript เป้าหมายที่จะแปลงรหัสของเราลงไป (เราสามารถเพิ่มรุ่นตามความต้องการเฉพาะของเบราว์เซอร์) -
include
ใช้เพื่อระบุรายการไฟล์ที่จะรวม
การกำหนดค่า Webpack
มาเพิ่มการกำหนดค่า webpack ให้กับ 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
เราสามารถเปิดใช้งาน webpack เพื่อโหลดไฟล์เฉพาะเมื่อแอพร้องขอโดยใช้ตัวโหลด ใช้วัตถุกฎที่ระบุว่า:- ไฟล์ใดๆ ที่ลงท้ายด้วยนามสกุล .
.tsx
หรือ.ts
ควรใช้awesome-typescript-loader
เพื่อโหลด - ไฟล์ที่ลงท้ายด้วยนามสกุล .
.js
ควรโหลดด้วยsource-map-loader
; - ไฟล์ที่ลงท้ายด้วยนามสกุล
.css
ควรโหลดด้วยcss-loader
- ไฟล์ใดๆ ที่ลงท้ายด้วยนามสกุล .
-
plugins
Webpack มีข้อ จำกัด ของตัวเองและมีปลั๊กอินเพื่อเอาชนะและเพิ่มขีดความสามารถ ตัวอย่างเช่นhtml-webpack-plugin
จะสร้างไฟล์เทมเพลตที่แสดงผลไปยังเบราว์เซอร์จากไฟล์index.html
ในไดเร็กทอรี ../src/component/index.html
MiniCssExtractPlugin
แสดงผลไฟล์ CSS
หลักของแอป
การเพิ่มสคริปต์ลงใน package.json
เราสามารถเพิ่มสคริปต์ต่างๆ เพื่อสร้างแอป React ในไฟล์ package.json
ของเรา:
"scripts": { "start": "webpack-dev-server --open", "build": "webpack" },
ตอนนี้ให้เรียกใช้ npm start
ใน CLI ของคุณ หากทุกอย่างเป็นไปด้วยดี คุณควรเห็นสิ่งนี้:

หากคุณมีความสามารถพิเศษด้าน webpack ให้โคลนที่เก็บสำหรับการตั้งค่านี้ และใช้ในโปรเจ็กต์ของคุณ
การสร้างไฟล์
สร้างโฟลเดอร์ src
และไฟล์ index.tsx
นี่จะเป็นไฟล์ฐานที่แสดง React
ตอนนี้ถ้าเรารัน npm start
มันจะเปิดเซิร์ฟเวอร์ของเราและเปิดแท็บใหม่ การรัน npm run build
จะสร้าง webpack สำหรับการผลิตและจะสร้างโฟลเดอร์ build ให้เรา
เราได้เห็นวิธีตั้งค่า TypeScript ตั้งแต่เริ่มต้นโดยใช้ Create React App และวิธีการกำหนดค่า webpack
วิธีที่เร็วที่สุดวิธีหนึ่งในการทำความเข้าใจ TypeScript อย่างเต็มรูปแบบคือการแปลงโปรเจ็กต์ vanilla React ที่มีอยู่เป็น TypeScript น่าเสียดายที่การนำ TypeScript มาใช้ในโปรเจ็กต์ vanilla React แบบค่อยเป็นค่อยไปนั้นเป็นเรื่องที่เครียดเพราะต้องดีดออกหรือเปลี่ยนชื่อไฟล์ทั้งหมด ซึ่งจะส่งผลให้เกิดข้อขัดแย้งและคำขอดึงขนาดยักษ์หากโปรเจ็กต์นั้นเป็นของทีมขนาดใหญ่
ต่อไป เราจะมาดูวิธีการโยกย้ายโปรเจ็กต์ React ไปยัง TypeScript อย่างง่ายดาย
โยกย้ายแอปสร้าง React ที่มีอยู่ไปยัง TypeScript
เพื่อให้กระบวนการนี้สามารถจัดการได้มากขึ้น เราจะแบ่งขั้นตอนออกเป็นขั้นตอน ซึ่งจะทำให้เราสามารถย้ายข้อมูลเป็นส่วนๆ ได้ ต่อไปนี้คือขั้นตอนที่เราจะดำเนินการเพื่อย้ายโครงการของเรา:
- เพิ่ม TypeScript และประเภท
- เพิ่ม
tsconfig.json
- เริ่มเล็ก.
- เปลี่ยนชื่อนามสกุลไฟล์เป็น .
.tsx
1. เพิ่ม TypeScript ให้กับโครงการ
ขั้นแรก เราจะต้องเพิ่ม TypeScript ในโครงการของเรา สมมติว่าโปรเจ็กต์ React ของคุณถูกบูทด้วยแอป Create React เราสามารถเรียกใช้สิ่งต่อไปนี้:
# 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
เพิ่มการรองรับ JSX ในไฟล์ ..tsx
-
lib
เพิ่มรายการไฟล์ไลบรารีในการคอมไพล์ (เช่น การใช้ es2015 ช่วยให้เราใช้ไวยากรณ์ ECMAScript 6) -
module
สร้างรหัสโมดูล -
noImplicitAny
ใช้เพื่อทำให้เกิดข้อผิดพลาดสำหรับการประกาศโดยนัยany
ประเภท -
outDir
แสดงถึงไดเร็กทอรีเอาต์พุต -
sourceMap
สร้างไฟล์.map
map ซึ่งมีประโยชน์มากสำหรับการดีบักแอปของเรา -
include
ใช้เพื่อระบุรายการไฟล์ที่จะรวม
-
ตัวเลือกการกำหนดค่าจะแตกต่างกันไป ตามความต้องการของโครงการ คุณอาจต้องตรวจสอบสเปรดชีตตัวเลือก TypeScript เพื่อค้นหาว่าอะไรจะเหมาะกับโครงการของคุณ
เราได้ดำเนินการที่จำเป็นเพื่อเตรียมของให้พร้อมเท่านั้น ขั้นตอนต่อไปของเราคือการย้ายไฟล์ไปยัง TypeScript
3. เริ่มต้นด้วยส่วนประกอบอย่างง่าย
ใช้ประโยชน์จากความสามารถของ TypeScript ที่จะค่อยๆ นำไปใช้ ไปทีละไฟล์ตามที่คุณต้องการ ทำในสิ่งที่เหมาะสมกับคุณและทีมของคุณ อย่าพยายามจัดการทั้งหมดพร้อมกัน
เพื่อแปลงสิ่งนี้ให้ถูกต้อง เราต้องทำสองสิ่ง:
- เปลี่ยนนามสกุลไฟล์เป็น .
.tsx
- เพิ่มคำอธิบายประกอบประเภท (ซึ่งจะต้องมีความรู้เกี่ยวกับ TypeScript)
4.เปลี่ยนชื่อนามสกุลไฟล์เป็น .tsx
ในฐานรหัสขนาดใหญ่ การเปลี่ยนชื่อไฟล์ทีละไฟล์อาจดูน่าเบื่อหน่าย
เปลี่ยนชื่อไฟล์หลายไฟล์บน macOS
การเปลี่ยนชื่อหลายไฟล์อาจทำให้เสียเวลา นี่คือวิธีที่คุณสามารถทำได้บน Mac คลิกขวา (หรือ Ctrl
+ คลิก หรือคลิกด้วยสองนิ้วพร้อมกันบนแทร็คแพดหากคุณใช้ MacBook) ในโฟลเดอร์ที่มีไฟล์ที่คุณต้องการเปลี่ยนชื่อ จากนั้นคลิก "เปิดเผยใน Finder" ใน Finder ให้เลือกไฟล์ทั้งหมดที่คุณต้องการเปลี่ยนชื่อ คลิกขวาที่ไฟล์ที่เลือก แล้วเลือก “เปลี่ยนชื่อรายการ X…” จากนั้นคุณจะเห็นสิ่งนี้:

แทรกสตริงที่คุณต้องการค้นหา และสตริงที่คุณต้องการแทนที่สตริงที่พบ แล้วกด "เปลี่ยนชื่อ" เสร็จแล้ว.
เปลี่ยนชื่อไฟล์หลายไฟล์ใน Windows
การเปลี่ยนชื่อหลายไฟล์บน Windows อยู่นอกเหนือขอบเขตของบทช่วยสอนนี้ แต่มีคำแนะนำฉบับสมบูรณ์ คุณมักจะได้รับข้อผิดพลาดหลังจากเปลี่ยนชื่อไฟล์ คุณเพียงแค่ต้องเพิ่มคำอธิบายประกอบประเภท คุณสามารถทำความเข้าใจเรื่องนี้ได้ในเอกสารประกอบ
เราได้กล่าวถึงวิธีตั้งค่า TypeScript ในแอป React แล้ว ตอนนี้ มาสร้างแอปเลือกตอนสำหรับ Money Heist โดยใช้ TypeScript
เราจะไม่ครอบคลุมประเภทพื้นฐานของ TypeScript ต้องอ่านเอกสารก่อนดำเนินการต่อในบทช่วยสอนนี้
ถึงเวลาสร้าง
เพื่อให้กระบวนการนี้รู้สึกกังวลน้อยลง เราจะแบ่งขั้นตอนนี้ออกเป็นขั้นตอน ซึ่งจะช่วยให้เราสร้างแอปเป็นชิ้นๆ ได้ นี่คือขั้นตอนทั้งหมดที่เราจะทำเพื่อสร้างเครื่องมือเลือกตอนของ Money Heist :
- นั่งร้านสร้างแอป React
- ดึงตอน
- สร้างประเภทและอินเทอร์เฟซที่เหมาะสมสำหรับตอนของเราใน
interface.ts
- ตั้งค่าร้านค้าสำหรับการดึงตอนต่างๆ ใน
store.tsx
- สร้างการดำเนินการสำหรับการดึงตอนใน
action.ts
- สร้างองค์ประกอบ
EpisodeList.tsx
ที่ดึงตอนต่างๆ - นำเข้าองค์ประกอบ
EpisodesList
ไปยังโฮมเพจของเราโดยใช้React Lazy and Suspense
- สร้างประเภทและอินเทอร์เฟซที่เหมาะสมสำหรับตอนของเราใน
- เพิ่มตอน
- ตั้งค่าร้านค้าเพื่อเพิ่มตอนใน
store.tsx
- สร้างการดำเนินการเพื่อเพิ่มตอนใน
action.ts
- ตั้งค่าร้านค้าเพื่อเพิ่มตอนใน
- ลบตอน
- ตั้งค่าร้านค้าสำหรับการลบตอนใน
store.tsx
- สร้างการดำเนินการสำหรับการลบตอนใน
action.ts
- ตั้งค่าร้านค้าสำหรับการลบตอนใน
- ตอนโปรด.
- นำเข้าส่วนประกอบ
EpisodesList
ในตอนโปรด - Render
EpisodesList
ภายในตอนที่ชื่นชอบ
- นำเข้าส่วนประกอบ
- การใช้ Reach Router สำหรับการนำทาง
ตั้งค่า React
วิธีที่ง่ายที่สุดในการตั้งค่า React คือการใช้ Create React App Create React App เป็นวิธีที่ได้รับการสนับสนุนอย่างเป็นทางการในการสร้างแอปพลิเคชัน React แบบหน้าเดียว มีการติดตั้งบิลด์ที่ทันสมัยโดยไม่มีการกำหนดค่า
เราจะใช้มันเพื่อบูตแอปพลิเคชันที่เราจะสร้าง จาก CLI ของคุณ ให้รันคำสั่งด้านล่าง:
npx create-react-app react-ts-app && cd react-ts-app
เมื่อการติดตั้งสำเร็จ ให้เริ่มเซิร์ฟเวอร์ React โดยเรียกใช้ npm start

การทำความเข้าใจอินเทอร์เฟซและประเภทใน 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
ถูกกำหนดประเภท string
แทนที่จะเป็นประเภท number
มาสร้างไฟล์ interface.ts
ในโฟลเดอร์ src
ของเรากัน คัดลอกและวางรหัสนี้ลงไป:
/** |-------------------------------------------------- | 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
ของเรามีคุณสมบัติ episodes
และ favorites
ตามลำดับ และอินเทอร์เฟซ Array<IEpisode>
IAการกระทำ
คุณสมบัติของอินเทอร์เฟซ IAction
คือ payload
และ type
คุณสมบัติ type
มีประเภทสตริง ในขณะที่ payload มีประเภทของ Array | any
Array | any
โปรดทราบว่า Array | any
หมาย Array | any
อาร์เรย์ของอินเทอร์เฟซตอนหรือประเภทใดก็ได้
ประเภท Dispatch
ถูกตั้งค่าเป็น React.Dispatch
และอินเทอร์เฟซ <IAction>
โปรดทราบว่า React.Dispatch
เป็นประเภทมาตรฐานสำหรับฟังก์ชันการสั่ง dispatch
ตามฐานรหัส @types/react
ในขณะที่ <IAction>
เป็นอาร์เรย์ของการดำเนินการของอินเทอร์เฟซ
นอกจากนี้ Visual Studio Code ยังมีตัวตรวจสอบ TypeScript ดังนั้น เพียงแค่เน้นหรือวางเมาส์เหนือโค้ด ก็ฉลาดพอที่จะแนะนำประเภทที่เหมาะสมได้
กล่าวอีกนัยหนึ่ง เพื่อให้เราใช้อินเทอร์เฟซของเราในแอปต่างๆ ได้ เราจำเป็นต้องส่งออกอินเทอร์เฟซนั้น จนถึงตอนนี้ เรามีร้านค้าและอินเทอร์เฟซของเราที่มีประเภทของวัตถุของเรา มาสร้างร้านของเรากันเถอะ โปรดทราบว่าอินเทอร์เฟซอื่นปฏิบัติตามข้อตกลงเดียวกันกับที่อธิบายไว้
ดึงตอน
การสร้างร้านค้า
ในการดึงข้อมูลตอนของเรา เราต้องการร้านค้าที่มีสถานะเริ่มต้นของข้อมูลและที่กำหนดฟังก์ชันตัวลดของเรา
เราจะใช้ประโยชน์จาก useReducer
hook เพื่อตั้งค่า สร้างไฟล์ store.tsx
ในโฟลเดอร์ src
ของคุณ คัดลอกและวางรหัสต่อไปนี้ลงไป
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} }
ต่อไปนี้เป็นขั้นตอนที่เราได้ดำเนินการเพื่อสร้างร้านค้า:

- ในการกำหนดร้านค้าของเรา เราจำเป็นต้องมี
useReducer
hook และcreateContext
API จาก React ซึ่งเป็นสาเหตุที่เรานำเข้ามา - เรานำเข้า
IState
และIAction
จาก ./types/./types/interfaces
- เราประกาศอ็อบเจ็กต์
initialState
ด้วยประเภทของIState
และคุณสมบัติของตอนและรายการโปรด ซึ่งตั้งค่าทั้งคู่เป็นอาร์เรย์ว่างตามลำดับ - ต่อไป เราสร้างตัวแปร
Store
ที่เก็บเมธอดcreateContext
และถูกส่งผ่านinitialState
ประเภทเมธอด createContext
คือ <IState | any>
<IState | any>
ซึ่งหมายความว่าอาจเป็นประเภท <IState>
หรือ any
. เราจะเห็น any
ที่ใช้บ่อยในบทความนี้
- ต่อไป เราประกาศฟังก์ชันรี
reducer
วเซอร์และส่งผ่านในstate
และaction
เป็นพารามิเตอร์ ฟังก์ชันreducer
มีคำสั่ง switch ที่ตรวจสอบค่าของaction.type
หากค่าเป็นFETCH_DATA
มันจะส่งคืนอ็อบเจ็กต์ที่มีสำเนาของสถานะของเรา(...state)
และสถานะตอนที่เก็บข้อมูลการดำเนินการของเรา - ในคำสั่ง switch เราคืนค่าสถานะ
default
โปรดทราบว่าพารามิเตอร์ state
และ action
ในฟังก์ชันตัวลดมีประเภท IState
และ IAction
ตามลำดับ นอกจากนี้ ฟังก์ชันตัว reducer
ยังมีประเภทของ IState
- สุดท้ายนี้ เราได้ประกาศฟังก์ชัน
StoreProvider
ซึ่งจะทำให้ส่วนประกอบทั้งหมดในแอปของเราเข้าถึงร้านค้าได้ - ฟังก์ชันนี้ใช้
children
เป็นพร็อพ และภายในฟังก์ชันStorePrivder
เราได้ประกาศขอuseReducer
- เราทำลาย
state
และdispatch
- เพื่อให้ร้านค้าของเราสามารถเข้าถึงส่วนประกอบทั้งหมดได้ เราได้ส่งผ่านค่าอ็อบเจ็กต์ที่มี
state
และdispatch
state
ที่มีตอนและสถานะรายการโปรดของเราจะสามารถเข้าถึงได้โดยส่วนประกอบอื่น ๆ ในขณะที่การ dispatch
เป็นฟังก์ชันที่เปลี่ยนสถานะ
- เราจะส่งออก
Store
และStoreProvider
เพื่อให้สามารถใช้ได้ในแอปพลิเคชันของเรา
สร้าง 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
เป็นพารามิเตอร์ - เนื่องจากฟังก์ชันของเราเป็นแบบอะซิงโครนัส เราจะใช้
async
และawait
- เราสร้างตัวแปร (
URL
) ที่เก็บปลายทาง API ของเรา - เรามีตัวแปรอื่นชื่อ
data
ที่เก็บการตอบสนองจาก API - จากนั้น เราเก็บการตอบสนอง JSON ใน
dataJSON
หลังจากที่เราได้รับการตอบสนองในรูปแบบ JSON โดยการเรียกdata.json()
- สุดท้าย เราส่งคืนฟังก์ชันการจัดส่งที่มีคุณสมบัติ
type
และสตริงของFETCH_DATA
นอกจากนี้ยังมีpayload()
_embedded.episodes
คืออาร์เรย์ของอ็อบเจกต์ตอนจากendpoint
ของเรา
โปรดทราบว่าฟังก์ชัน fetchDataAction
จะดึงข้อมูลปลายทางของเรา แปลงเป็นออบเจ็กต์ JSON
และส่งคืนฟังก์ชันการจัดส่ง ซึ่งจะอัปเดตสถานะที่ประกาศไว้ก่อนหน้านี้ใน Store
ประเภทการส่งที่ส่งออกถูกตั้งค่าเป็น React.Dispatch
โปรดทราบว่า React.Dispatch
เป็นประเภทมาตรฐานสำหรับฟังก์ชันการจัดส่งตามฐานโค้ด @types/react
ในขณะที่ <IAction>
เป็นอาร์เรย์ของอินเทอร์เฟซ 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
- เรานำเข้า
IEpisode
และIProps
จากinterfaces.tsx
- ต่อไป เราสร้างฟังก์ชัน
EpisodesList
ที่ใช้อุปกรณ์ประกอบฉาก อุปกรณ์ประกอบฉากจะมีประเภทของIProps
ในขณะที่ฟังก์ชั่นมีประเภทของArray<JSX.Element>
Visual Studio Code แนะนำว่าประเภทฟังก์ชันของเราเขียนเป็น JSX.Element[]

ในขณะที่ Array<JSX.Element>
เท่ากับ JSX.Element[]
นั้น Array<JSX.Element>
จะถูกเรียกว่าเอกลักษณ์ทั่วไป ดังนั้น บทความนี้จะนิยมใช้รูปแบบทั่วไป
- ภายในฟังก์ชัน เราทำลายโครงสร้าง
episodes
จากprops
ซึ่งมีIEpisode
เป็นประเภท
อ่านเกี่ยวกับข้อมูลประจำตัวทั่วไป ความรู้นี้จำเป็นเมื่อเราดำเนินการ
- เราส่งคืนอุปกรณ์ประกอบฉากของ
episodes
และจับคู่เพื่อส่งคืนแท็ก HTML สองสามรายการ - ส่วนแรกมี
key
ซึ่งก็คือepisode.id
และclassName
ของepisode-box
ซึ่งจะถูกสร้างขึ้นในภายหลัง เรารู้ว่าตอนของเรามีภาพ ดังนั้นแท็กรูปภาพ - รูปภาพมีโอเปอเรเตอร์ ternary ที่ตรวจสอบว่ามี
episode.image
หรือepisode.image.medium
มิฉะนั้น เราจะแสดงสตริงว่างหากไม่พบรูปภาพ นอกจากนี้เรายังรวมepisode.name
ไว้ใน div
ใน 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
- เรานำเข้า
useContext
,useEffect
,lazy
และSuspense
จาก React ส่วนประกอบแอพที่นำเข้านั้นเป็นรากฐานที่ส่วนประกอบอื่นๆ ทั้งหมดจะต้องได้รับมูลค่าของร้านค้า - นอกจากนี้เรายังนำเข้า
Store
,IEpisodeProps
และFetchDataAction
จากไฟล์ที่เกี่ยวข้อง - เรานำเข้าองค์ประกอบ
EpisodesList
โดยใช้คุณสมบัติReact.lazy
ที่มีอยู่ใน React 16.6
React lazy loading รองรับหลักการแยกโค้ด ดังนั้นส่วนประกอบ EpisodesList
ของเราจึงถูกโหลดแบบไดนามิก แทนที่จะโหลดพร้อมกัน จึงเป็นการปรับปรุงประสิทธิภาพของแอปของเรา
- เราทำลายโครงสร้าง
state
และdispatch
เป็นอุปกรณ์ประกอบฉากจากStore
- เครื่องหมาย (&&) ใน
useEffect
hook จะตรวจสอบว่าสถานะตอนของเราempty
หรือไม่ (หรือเท่ากับ 0) มิฉะนั้น เราจะคืนค่าฟังก์ชันfetchDataAction
- สุดท้าย เราส่งคืนองค์ประกอบ
App
ข้างในเราใช้ WrapperSuspense
และตั้งค่าทางfallback
เป็น div พร้อมข้อความloading
สิ่งนี้จะแสดงให้ผู้ใช้เห็นในขณะที่เรารอการตอบกลับจาก API - คอมโพเนนต์
EpisodesList
จะต่อเชื่อมเมื่อมีข้อมูล และข้อมูลที่จะมีepisodes
คือสิ่งที่เราจะกระจายไป
ตั้งค่า Index.txt
คอมโพเนนต์ 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
, HomePage
และ index.css
จากไฟล์ที่เกี่ยวข้อง We wrap the HomePage
component in our StoreProvider
. This makes it possible for the Homepage
component to access the store, as we saw in the previous section.
เรามาไกลมากแล้ว Let's check what the app looks like, without any CSS.

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
favorites
และ store
เป็นอุปกรณ์ประกอบฉาก และเราจะทำลาย state
ซึ่งเป็นการ dispatch
จากร้านค้า ในการเลือกตอนโปรดของเรา เราได้รวมเมธอด toggleFavAction
ไว้ในเหตุการณ์ onClick
และ dispatch
ผ่าน state
, การสั่งงาน และ episode
ประกอบเป็นอาร์กิวเมนต์ของฟังก์ชัน
สุดท้าย เราวนรอบสถานะ favorite
เพื่อตรวจสอบว่า fav.id
(ID ที่ชื่นชอบ) ตรงกับ episode.id
หรือไม่ หากเป็นเช่นนั้น เราจะสลับระหว่างข้อความ Unfav
และ Fav
ซึ่งจะช่วยให้ผู้ใช้ทราบว่าพวกเขาได้ชื่นชอบตอนนั้นหรือไม่
เรากำลังใกล้ถึงจุดสิ้นสุด แต่เรายังคงต้องการหน้าที่เชื่อมโยงตอนโปรดเมื่อผู้ใช้เลือกตอนต่างๆ ในหน้าแรก
ถ้าคุณมาไกลขนาดนี้ ให้ตบหลังตัวเองหน่อย
ส่วนประกอบ Favpage
ในโฟลเดอร์ 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> ) }
เพื่อสร้างตรรกะในการเลือกตอนโปรด เราได้เขียนโค้ดเล็กน้อย เรานำเข้า lazy
และ Suspense
จาก React นอกจากนี้เรายังนำเข้า Store
, IEpisodeProps
และ toggleFavAction
จากไฟล์ที่เกี่ยวข้อง
เรานำเข้าองค์ประกอบ EpisodesList
ของเราโดยใช้คุณสมบัติ React.lazy
สุดท้าย เราส่งคืนองค์ประกอบ App
ข้างในเราใช้ Wrapper Suspense
และตั้งค่าทางเลือกเป็น div พร้อมข้อความโหลด
การทำงานนี้คล้ายกับองค์ประกอบ Homepage
ส่วนประกอบนี้จะเข้าถึงร้านค้าเพื่อรับตอนที่ผู้ใช้ชื่นชอบ จากนั้น รายการของตอนจะถูกส่งไปยังคอมโพเนนต์ EpisodesList
มาเพิ่มอีกสองสามตัวอย่างในไฟล์ HomePage.tsx
รวม toggleFavAction
จาก ../ ../Actions
รวมถึงวิธี 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
เพื่อให้บรรลุสิ่งนี้ เราใช้ Reach Router ซึ่งเป็นไลบรารีที่คล้ายกับ React Router William Le อธิบายความแตกต่างระหว่าง Reach Router และ React Router
ใน CLI ของคุณ ให้รัน npm install @reach/router @types/reach__router
เรากำลังติดตั้งทั้งไลบรารี Reach Router และประเภท reach-router
เมื่อติดตั้งสำเร็จ ให้นำเข้า Link
จาก @reach/router
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
เรานำเข้า FavPage
และ HomePage
ตามลำดับ และรวมไว้ใน 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
:
import
{ IAction, IEpisode, IState, 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 }) } //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) }
เรานำเข้าอินเทอร์เฟซ IState
จาก ./types/interfaces
/interfaces เนื่องจากเราจะต้องส่งผ่านอินเทอร์เฟซดังกล่าวเป็นประเภทไปยังอุปกรณ์ประกอบฉาก state
ในฟังก์ชัน toggleFavAction
ตัวแปร episodeInFav
ถูกสร้างขึ้นเพื่อตรวจสอบว่ามีตอนอยู่ในสถานะ favorites
หรือไม่
เรากรองสถานะรายการโปรดเพื่อตรวจสอบว่า ID ที่ชื่นชอบไม่เท่ากับ ID ตอนหรือไม่ ดังนั้น dispatchObj
จึงถูกกำหนดประเภท REMOVE_FAV
ใหม่และส่วนของข้อมูล favWithoutEpisode
มาดูตัวอย่างผลลัพธ์ของแอพของเรากัน
บทสรุป
ในบทความนี้ เราได้เห็นวิธีตั้งค่า TypeScript ในโปรเจ็กต์ React และวิธีย้ายโปรเจ็กต์จาก vanilla React เป็น TypeScript
นอกจากนี้เรายังได้สร้างแอปด้วย TypeScript และ React เพื่อดูว่ามีการใช้ TypeScript ในโครงการ React อย่างไร ฉันเชื่อว่าคุณสามารถเรียนรู้บางสิ่งได้
โปรดแบ่งปันความคิดเห็นและประสบการณ์ของคุณกับ TypeScript ในส่วนความคิดเห็นด้านล่าง ฉันชอบที่จะเห็นสิ่งที่คุณคิด!
พื้นที่เก็บข้อมูลสนับสนุนสำหรับบทความนี้มีอยู่ใน GitHub
อ้างอิง
- “วิธีโยกย้ายแอป React ไปยัง TypeScript” Joe Previte
- “ทำไมและต้องใช้ TypeScript ในแอป React ของคุณอย่างไร” Mahesh Haldar