การตั้งค่า 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สร้างไฟล์.mapmap ซึ่งมีประโยชน์มากสำหรับการดีบักแอป -
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 -
resolveWebpack จะดูที่แอตทริบิวต์นี้เพื่อตัดสินใจว่าจะรวมกลุ่มหรือข้ามไฟล์ ดังนั้น ในโครงการของเรา webpack จะพิจารณาไฟล์ที่มีนามสกุล ..js,.jsx,.json,.tsและ ..tsxสำหรับการรวมกลุ่ม -
moduleเราสามารถเปิดใช้งาน webpack เพื่อโหลดไฟล์เฉพาะเมื่อแอพร้องขอโดยใช้ตัวโหลด ใช้วัตถุกฎที่ระบุว่า:- ไฟล์ใดๆ ที่ลงท้ายด้วยนามสกุล .
.tsxหรือ.tsควรใช้awesome-typescript-loaderเพื่อโหลด - ไฟล์ที่ลงท้ายด้วยนามสกุล .
.jsควรโหลดด้วยsource-map-loader; - ไฟล์ที่ลงท้ายด้วยนามสกุล
.cssควรโหลดด้วยcss-loader
- ไฟล์ใดๆ ที่ลงท้ายด้วยนามสกุล .
-
pluginsWebpack มีข้อ จำกัด ของตัวเองและมีปลั๊กอินเพื่อเอาชนะและเพิ่มขีดความสามารถ ตัวอย่างเช่น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สร้างไฟล์.mapmap ซึ่งมีประโยชน์มากสำหรับการดีบักแอปของเรา -
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} }
ต่อไปนี้เป็นขั้นตอนที่เราได้ดำเนินการเพื่อสร้างร้านค้า:

- ในการกำหนดร้านค้าของเรา เราจำเป็นต้องมี
useReducerhook และcreateContextAPI จาก 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 - เครื่องหมาย (&&) ใน
useEffecthook จะตรวจสอบว่าสถานะตอนของเรา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 } = storereturn 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.pageComponentReactDOM.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 typeexport 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
