การตั้งค่า TypeScript สำหรับโปรเจ็กต์ React สมัยใหม่โดยใช้ Webpack

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ บทความนี้จะแนะนำ Typescript ซึ่งเป็นตัวยกของ JavaScript ที่นำเสนอคุณลักษณะประเภทสแตติกสำหรับการระบุข้อผิดพลาดทั่วไปในฐานะรหัสของนักพัฒนา ซึ่งช่วยเพิ่มประสิทธิภาพการทำงาน จึงส่งผลให้แอปพลิเคชันระดับองค์กรแข็งแกร่ง คุณจะได้เรียนรู้วิธีตั้งค่า TypeScript อย่างมีประสิทธิภาพใน React Project ขณะที่เราสร้าง Money Heist Episode Picker App, สำรวจ TypeScript, React hooks เช่น useReducer, useContext และ Reach Router

ในยุคของการพัฒนาซอฟต์แวร์นี้ 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) ของคุณ

กำลังเปลี่ยนชื่อแอปเป็น 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 ในบทช่วยสอนนี้ เราจะพูดถึงแค่สองข้อเท่านั้น

วิธีที่ 1: สร้างแอป React + TypeScript

ประมาณสองปีที่แล้วทีม React ได้เปิดตัว Create React App 2.1 พร้อมรองรับ TypeScript ดังนั้น คุณอาจไม่ต้องดำเนินการใดๆ เพื่อนำ TypeScript มาสู่โปรเจ็กต์ของคุณ

ประกาศเกี่ยวกับ TypeScript ในแอป Create React (ตัวอย่างขนาดใหญ่)

ในการเริ่มโครงการ 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 ของเรา:

  1. เพิ่ม webpack.config.js เพื่อเพิ่มการกำหนดค่าที่เกี่ยวข้องกับ webpack
  2. เพิ่ม tsconfig.json สำหรับการกำหนดค่า TypeScript ทั้งหมดของเรา
  3. เพิ่มไดเร็กทอรีใหม่ src .
  4. สร้างไดเร็กทอรีใหม่ components ในโฟลเดอร์ src
  5. สุดท้าย เพิ่ม 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 ของคุณ หากทุกอย่างเป็นไปด้วยดี คุณควรเห็นสิ่งนี้:

เอาต์พุตการตั้งค่า React-Webpack (ตัวอย่างขนาดใหญ่)

หากคุณมีความสามารถพิเศษด้าน 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

เพื่อให้กระบวนการนี้สามารถจัดการได้มากขึ้น เราจะแบ่งขั้นตอนออกเป็นขั้นตอน ซึ่งจะทำให้เราสามารถย้ายข้อมูลเป็นส่วนๆ ได้ ต่อไปนี้คือขั้นตอนที่เราจะดำเนินการเพื่อย้ายโครงการของเรา:

  1. เพิ่ม TypeScript และประเภท
  2. เพิ่ม tsconfig.json
  3. เริ่มเล็ก.
  4. เปลี่ยนชื่อนามสกุลไฟล์เป็น . .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 ที่จะค่อยๆ นำไปใช้ ไปทีละไฟล์ตามที่คุณต้องการ ทำในสิ่งที่เหมาะสมกับคุณและทีมของคุณ อย่าพยายามจัดการทั้งหมดพร้อมกัน

เพื่อแปลงสิ่งนี้ให้ถูกต้อง เราต้องทำสองสิ่ง:

  1. เปลี่ยนนามสกุลไฟล์เป็น . .tsx
  2. เพิ่มคำอธิบายประกอบประเภท (ซึ่งจะต้องมีความรู้เกี่ยวกับ TypeScript)

4.เปลี่ยนชื่อนามสกุลไฟล์เป็น .tsx

ในฐานรหัสขนาดใหญ่ การเปลี่ยนชื่อไฟล์ทีละไฟล์อาจดูน่าเบื่อหน่าย

เปลี่ยนชื่อไฟล์หลายไฟล์บน macOS

การเปลี่ยนชื่อหลายไฟล์อาจทำให้เสียเวลา นี่คือวิธีที่คุณสามารถทำได้บน Mac คลิกขวา (หรือ Ctrl + คลิก หรือคลิกด้วยสองนิ้วพร้อมกันบนแทร็คแพดหากคุณใช้ MacBook) ในโฟลเดอร์ที่มีไฟล์ที่คุณต้องการเปลี่ยนชื่อ จากนั้นคลิก "เปิดเผยใน Finder" ใน Finder ให้เลือกไฟล์ทั้งหมดที่คุณต้องการเปลี่ยนชื่อ คลิกขวาที่ไฟล์ที่เลือก แล้วเลือก “เปลี่ยนชื่อรายการ X…” จากนั้นคุณจะเห็นสิ่งนี้:

เปลี่ยนชื่อไฟล์บน Mac (ตัวอย่างขนาดใหญ่)

แทรกสตริงที่คุณต้องการค้นหา และสตริงที่คุณต้องการแทนที่สตริงที่พบ แล้วกด "เปลี่ยนชื่อ" เสร็จแล้ว.

เปลี่ยนชื่อไฟล์หลายไฟล์ใน 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[]

Visual Studio Code แนะนำประเภท (ตัวอย่างขนาดใหญ่)

ในขณะที่ 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 ข้างในเราใช้ Wrapper Suspense และตั้งค่าทาง 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.

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 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

อ้างอิง

  1. “วิธีโยกย้ายแอป React ไปยัง TypeScript” Joe Previte
  2. “ทำไมและต้องใช้ TypeScript ในแอป React ของคุณอย่างไร” Mahesh Haldar