Typescript使用
什么是TypeScript?
TypeScript是微軟公司2009年發布的一個開源的JavaScript超集語言,它的設計者是C#語言之父安德斯·海爾斯伯格。
JavaScript超集:當前任何JavaScript都是合法的TypeScript代碼。
TypeScript主要為JavaScript提供了類型系統和ES6+語法支持。
與Flow相比,Flow是一個類型檢查工具,TypeScript是一種開發語言。
TypeSCript有自己的編譯器,可以將寫好的TypeScript代碼最終通過編譯器編譯成JavaScript代碼進行運行。
為什么要給javascript加上類型?
- 類型系統可以提高代碼質量和可維護性。
- 有利於代碼重構,在編譯時捕獲錯誤,提供類型安全,提高代碼健壯性。
- 類型系統是一個出色的文檔形式,方便大型項目的協同開發,降低團隊成員溝通成本。
安裝和配置TypeScript
TypeScript最終要運行起來,需要將TypeScript轉換成JavaScript代碼。轉換可以通過TypeScript的命令行工具來完成。
安裝:
npm install -g typescript
執行以上命令會在全局環境下安裝tsc命令。
編寫typescript代碼:
示例代碼:
新建一個hello.ts的文件
let num: number = 100
num = 1234
function sum(a: number, b: number): number {
return a + b;
}
const total: number = sum(10, 20)
編譯:
tsc hello.ts
通過上面這個命令可以將hello.ts文件編譯成js文件。tsc只負責編譯將ts編譯為js,而沒有直接運行ts的功能。要想直接運行ts,可以全局安裝 npm i -g ts-node
,終端輸入 ts-node ./hello.ts
即可直接查看運行結果。
初始化:
tsc --init
使用以上命令可以在當前目錄下生成一個tsconfig.json的文件,用來配置當前項目的相關內容。
tsconfig.json
{
"compilerOptions": {
/* 基本選項 */
"target": "es5", // 指定 ECMAScript 目標版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在編譯中的庫文件
"allowJs": true, // 允許編譯 javascript 文件
"checkJs": true, // 報告 javascript 文件中的錯誤
"jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相應的 '.d.ts' 文件
"sourceMap": true, // 生成相應的 '.map' 文件
"outFile": "./", // 將輸出文件合並為一個文件
"outDir": "./", // 指定輸出目錄
"rootDir": "./", // 用來控制輸出目錄結構 --outDir.
"removeComments": true, // 刪除編譯后的所有的注釋
"noEmit": true, // 不生成輸出文件
"importHelpers": true, // 從 tslib 導入輔助工具函數
"isolatedModules": true, // 將每個文件作為單獨的模塊 (與 'ts.transpileModule' 類似).
/* 嚴格的類型檢查選項 */
"strict": true, // 啟用所有嚴格類型檢查選項
"noImplicitAny": true, // 在表達式和聲明上有隱含的 any類型時報錯
"strictNullChecks": true, // 啟用嚴格的 null 檢查
"noImplicitThis": true, // 當 this 表達式值為 any 類型的時候,生成一個錯誤
"alwaysStrict": true, // 以嚴格模式檢查每個模塊,並在每個文件里加入 'use strict'
/* 額外的檢查 */
"noUnusedLocals": true, // 有未使用的變量時,拋出錯誤
"noUnusedParameters": true, // 有未使用的參數時,拋出錯誤
"noImplicitReturns": true, // 並不是所有函數里的代碼都有返回值時,拋出錯誤
"noFallthroughCasesInSwitch": true, // 報告 switch 語句的 fallthrough 錯誤。(即,不允許 switch 的 case 語句貫穿)
/* 模塊解析選項 */
"moduleResolution": "node", // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用於解析非相對模塊名稱的基目錄
"paths": {}, // 模塊名到基於 baseUrl 的路徑映射的列表
"rootDirs": [], // 根文件夾列表,其組合內容表示項目運行時的結構內容
"typeRoots": [], // 包含類型聲明的文件列表
"types": [], // 需要包含的類型聲明文件名列表
"allowSyntheticDefaultImports": true, // 允許從沒有設置默認導出的模塊中默認導入。
/* Source Map Options */
"sourceRoot": "./", // 指定調試器應該找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定調試器應該找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
"inlineSources": true, // 將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性
/* 其他選項 */
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true // 為裝飾器提供元數據的支持
}
}
配置項:
- "outDir": "./dist" : 生成文件的存放目錄
- "rootDir": "./src" :ts代碼的存放路徑
- "strict": true: 將TypeScript代碼轉換成嚴格模式的代碼
- "target": "es5": 將ts代碼轉換成js代碼的版本(默認是es5)
- "module": "commonjs":將ts代碼轉換后,使用的模塊化標准。(默認commonjs)
聲明空間
-
類型聲明空間:
用來存儲數據類型注解,僅用於對變量做類型注解,不能直接當作變量使用。
class Foo {} interface Car {} type Phone {} const a: Foo; const b: Car; const c: Phone;
-
變量聲明空間:
用來存儲具體數據,可以直接使用。
const a: number = 1111; const b: string = "hello word";
命名空間:
用於區分某些邊界問題創建獨立作用域,在js中可以通過匿名函數或空對象來實現。ts則提供了namespace 關鍵字用來表述某個作用域塊。
// 使用匿名函數創建獨立作用域
(function(a,b,c){
// some code
})(a,b,c)
// 使用空對象創建獨立作用域
{
function test() {
// some code
}
const abc: number = 111;
}
// 使用ts創建獨立作用域
namespace Test {
// some code
}
模塊
模塊的概念:當一個項目存在多個文件,多個文件相互關聯,且每個文件有自己的作用域時,每個文件就是一個模塊,在模塊內部定義的變量、函數、類都是私有的,對其他文件不可見。只能通過模塊化規范中的特定語法來暴露給其他文件使用,其他文件可以使用模塊化規范中的語法來導入這些模塊暴露的數據進行使用。
模塊化規范
目前模塊化標准較多只介紹兩種常用用到的。
-
CommonJS: Node.js所使用的模塊化系統就是基於CommonJS規范實現的,它的模塊加載過程是同步的,支持動態導入。可以在if等語句中執行加載。
// 111.js const name = '1' const age = '2' module.exports.name = name; // 導出一個變量 module.exports.age = age // 導出另一個變量 module.exports = age; // 默認導出一個變量 (一個模塊內默認導出與其他導出語句不能同時存在,且一個文件只能有一個默認導出) // 222.js const obj = require('./111.js') // { name: '1', age: '2'} const age = require('./111.js') // '2'
-
ESM: ES6 Module是JavaScript的模塊化規范,使用ES6模塊化會自動啟用嚴格模式,即使沒在文件頂部添加'use strict' 語句。ESM加載模是同步還是異步取決於項目中的使用,一般項目中使用的是靜態同步的導入,導入只能在當前模塊的最頂層作用域中。在webpack中也可以通過使用插件(
@babel/plugin-syntax-dynamic-import
)來支持異步動態(運行時)導入(分包懶加載就是通過這種方法實現的)。ESM異步導入目前處於TC39提案階段。// 111.ts const num1: number = 1111; const str1: string = 'aaa'; const obj1: object = {}; export num1; // 導出某一項 export num2; // 導出某一項 export default obj1; // 默認導出 // 222.ts import obj1, { num1, str1 } from './111.ts'; // 導入方式 import * as someValue from './111.ts'; // 導入全部模塊並賦值給一個變量 import './111.ts'; // 只執行導入
模塊路徑
-
相對路徑:根據當前文件的所在位置導入。(webpack在配置中有個extensions字段可以設置默認后綴名,導入時可以省略后綴不寫)例如
../../xxx/index
-
動態路徑:當導入語句不以
./
或../
開頭時,導入操作會動態查找當前目錄下的node_modules/xxx
模塊,如果不存在則逐級從項目文件夾向上查找node_modules文件夾,一種到根目錄和全局node_modules。 -
重寫類型動態查找:在項目中可以可以通過聲明全局模塊的方式來解決查找模塊路徑的問題。
// 111.ts declare module 'abc' { export const a = '1' export const b = '2' } // 222.ts import * as soneModule from 'abc';
定義模塊
與ts的全局變量類似,可以通過 declare module 來定義一個模塊,這個模塊可以像安裝的npm包一樣使用。對於絕大多數比較流行的庫,社區已經提供了@types 的類型聲明,如果沒有這個類型聲明,就可以通過這種方式來實現。
declare module 'jquery' { // 這里用來定義為模塊
export const query = xxxx
}
import * from 'jquery' // 這里可以不寫相對路徑,直接引入使用
數據類型
數值類型(Number)
const a: number = 123 // 數值
const b: number = NaN // 非數字
const c: number = Infinity // 正無窮大的數值
const d: number = 0xA12 // 十六進制
const e: number = 0b1010101 // 二進制
const f: number = 0o75 // 八進制
字符串類型(String)
const str1: string = "hello"
const str2: string = 'hello'
const str3: string = `hello`
布爾類型 (Boolean)
const flag1: boolean = true
const flag2: boolean = false
數組(Array)
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
元祖(Tuple)
元祖類型允許表示一個已知元素數量和類型的數組,各元素的類型不必相同。
const arr1: [number, string] = [100, 'hello']
空值(void)
某種程度上來說,void類型像是與any類型相反,它表示沒有任何類型,當一個函數沒有返回值時,通常會見到返回值類型是void。
聲明一個void類型沒什么大用。因為它只能賦值為undefined
function foo():void {
alert('hello word')
}
const result: void = undefined
undefined
undefined 類型只能賦值為undefined
null
null類型只能賦值為null
any
any類型可以賦值為任意類型
let val: any = 100
val = 'hello'
val = {}
val = []
Never
never類型表示的是那些用不存在的值的類型。例如, never類型是那些總是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型。變量也可能是naver類型,當他們被用部位真的類型保護所約束時。
never類型時任何類型的子類型,也可以賦值給任何類型;然而,沒有類型時never的子類型或可以賦值給nerver類型(除了never本身之外),即使any也不可以賦值給never。
never類型一般用在不可能返回內容的函數返回值類型
// 返回never的函數必須存在無法到達的終點
function error(message: string): never {
throw new Error(message)
}
// 推斷的返回值類型為never
function fail() {
return error('something failed')
}
// 返回never的函數必須存在無法到達的終點
function infiniteLoop(): never {
while (true) { }
}
對象(Object)
const obj1: { name: string, age: number } = { name: 'xiaoming', age: 20 }
枚舉(Enum)
enum類型是對JavaScript標准數據類型的一個補充。像其他語言一樣,使用枚舉類型可以為一組數值賦予友好的名字。
enum Color {
red,
green,
blue,
}
const green: Color = Color.green;
默認情況下從0開始為元素編號,數字枚舉帶有自增長,如果不指定具體值,則默認從0開始自增。也可以手動的制定成員的數值。例如,我們將上面的例子改成從1開始編號。
enum Color2 {
red = 1,
green,
blue
}
const red: Color2 = Color2.red
或者,全部都采用手動賦值
enum Color3 {
red = 1,
green = 2,
blue = 3
}
const blue: Color3 = Color3.blue
字符串枚舉
在typescript版本支持了字符串枚舉,每個成員都必須使用字符串字面量。
Enum Colors {
red = 'RED',
green = 'GREEN',
blue = 'BLUE'
}
類型斷言
有時你會遇到這樣的情況,你會比typescript更了解某個值的詳細信息,通常這回發生在你清楚地知道一個實例具有比他現有類型更確切的類型。
通過類型斷言這種方式可以告訴編譯器,類型斷言好比其他語言里的類型轉換,但是不進行特殊的數據檢查和解構。它沒有運行時的影響,只是在表意階段起作用,typescript會假設你,已經進行了必須的檢查。
類型斷言有兩種形式:
其一是尖括號語法:
const someValue: any = 'this is a string'
const strLength: number = (<string>someValue).length
另一個為as語法:在react項目中推薦這種寫法防止與jsx產生沖突
const someValue: any = 'this is a string'
const strLength: number = (someValue as string).length;
兩種形式的等價的,可以憑個人喜好使用。然而,在typescript里使用jsx時,只有as 語法斷言是被允許的,這也是為了防止和react中的jsx元素產生沖突。
聯合類型
當一個字面量有可能為多種類型時,聯合類型可以把多個類型結合起來用於約束某一個字面量,字面量只能為多種類型中的其中一種。多個類型之間用 | 分割。
const type1: string;
// 聯合類型
const type2: number | boolean;
const type3: string | object;
交叉類型組合類型
在需要把多個類型合並成一個類型進行處理時,交叉類型在這個時候非常有用。它是有多個類型組合而成的,具有各個類型的所有屬性。這與上面提到的聯合類型剛好相反,它可以將多個類型組成一個類型。這里有個問題時如果兩個個類型中有相同的屬性,這個相同屬性的類型相同則不會報錯,如果屬性相同類型不同,組合起來就會報錯。解決辦法是把相同屬性的類型改為聯合類型,但從理解上來看他們應該有不同的行為,不應該強制合並到一起,更好的做法是使用不同的名稱來區分。交叉類型對基本類型、接口、類、泛型等都適用。
class A {
width: number = 0;
height: number = 0;
}
class B {
name: string = '',
type: string = ''
}
function make(size: A, goods: B): A & B {
const Book = <A & B> {}
Book.width = 1
Book.height = 1
Book.name = 'abc'
Book.type = 'book'
return Book
}
類(class)
介紹
使用函數和基於原型的繼承來創建可重用的代碼。但對於熟悉使用面向對象方式的開發者來講就有些棘手。因為他們用的是機遇類型的繼承斌切對象是由類構建出來的。從ECMAScript2015,也就是ES6開始,JavaScript程序員將能夠使用基於類的面相對象的方式。使用typescript,我們允許開發着現在就使用這些特性。並且編譯后的JavaScript可以子所有主流瀏覽器和平台上運行,而不需要等到下個JavaScript版本。
一個簡單的例子:
class Greeter {
// 與ES6不同的是,TS中屬性必須聲明,需要指定類型
greeting: string
// 聲明好屬性之后,屬性必須賦值一個默認值后者在構造函數中進行初始化。
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'htllo' + this.greeting
}
}
let greeter = new Greeter('word')
類的繼承
class Say {
name: string
constructor(name: string) {
this.name = name
}
say() {
console.log(this.name + " say good morning")
}
}
class Person extends Say {
}
const john = new Person('john')
john.say()
class Say {
name: string
constructor(name: string) {
this.name = name
}
say() {
console.log(this.name + " say good morning")
}
}
class Person extends Say {
type: string
constructor(type: string, name: string) {
super(name)
this.type = type
}
// 子類中如果出現了和父類同名的方法,則會進行合並覆蓋
age() {
console.log(this.name + ' age is ' + this.type)
}
}
const john = new Person('jerry', '20')
john.say()
john.job()
訪問修飾符
訪問修飾符指的基石可以在類的成員前通過添加關鍵字來設置當前成員的訪問權限
- public: 公開的,默認,所有人都可以進行訪問。
- private: 私有的,只能在當前類中進行訪問。
- protected: 受保護的,只能在當前類或者子類中進行訪問。
enum CarColor {
red = 'red',
green = 'green',
blue = 'blue'
}
class Car {
// 如果不加訪問修飾符,則默認是公開的,所有人都可以訪問
color: string
constructor() {
this.color = CarColor.red
}
protected go() {
console.log('go to school')
}
}
const BYD = new Car()
BYD.color
class Audi extends Car {
// 添加了private,只能在當前的類中使用
private run() {
this.go() // protected 修飾后在子類可以正常訪問
console.log(this.color)
}
}
const audi = new Audi()
audi.color
// 報錯; run方法使用了private修飾外面訪問不到
// audi.run()
readonly修飾符
使用readonly管檢測可以將屬性設置為只讀的,只讀屬性必須在聲明時或者構造函數里被初始化。
class Person {
readonly name: string;
readonly numberOfStep: number = 8
constructor(theName: string) {
this.name = theName
}
}
const QaQ = new Person('fdsffsdfsf')
QaQ.name = 'fsdfsdfsdfsd' // 報錯!name是只讀的,不能賦值
參數屬性
在上面例子中,我們不得不定義一個受保護的成員name和一個構造函數參數theName在Person類里,並且立即將theName的值賦值給name。這種情況經常會遇到。參數屬性可以方便地讓我們在一個地方定義並初始化一個成員。下面例子是對之前Animal類的修改版,使用了參數屬性。
class Person2 {
constructor(private name: string) { }
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}`)
}
}
存取器
class People {
private _name: string = ''
// 當訪問name屬性的時候會自動調用這個方法,返回值為訪問此屬性獲取到的值
get name(): string {
return this._name
}
// 當修改name屬性的時候會自動調用這個方法,返回值為修改此屬性的值
set name(value: string) {
// 設置器中可以添加相關的校驗邏輯
if (value.length < 2 || value.length > 5) {
throw new Error('名稱不合法')
}
this._name = value
}
}
let p = new People()
p.name = 'hello word'
接口(interface)
TypeScript的核心原則之一是對值所具有的結構進行類型檢查,它有時候被稱作“鴨式辨型法”或“結構性子類型化”。在TypeScript里,接口的作用就是為這些類型命名和為你的代碼或第三方代碼定義契約。
一個簡單的示例代碼:
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label)
}
let myObj = { size: 10, label: "size 10 object" }
printLabel(myObj)
類型檢查會查看printLabel的調用,printLabel有一個參數,並要求這個對象有一個名為label類型為string的屬性。需要注意的是,我們傳入的對象參數實際上會包含很多屬性。但是編譯器只會減產那些必須的屬性是否存在,並且其類型是否匹配。然而有些時候TypeScript卻並不回這么寬松。例如以下示例
我們重寫上面的例子,這次使用接口來描述,必須包含一個label屬性且類型為string:
// 使用接口進行聲明
interface LabelledValue {
readonly label: string // 在接口中也可以設置只讀屬性
name?: string // 加上?代表可選屬性,可以傳也可以不傳
success(msg: string): void
[propName: string]: any // 額外的屬性檢查,如果多傳了額外的參數自動忽略掉
}
function printLabel(options: LabelledValue) {
}
printLabel({ label: 'hello word', success() { } })
函數類型接口
interface SumInterface {
(a: number, b: number): number
}
const sum: SumInterface = function (a: number, b: number): number {
return a + b
}
類類型的接口
interface PersonInterface {
name: string
age: number
say(): void
}
class Jack implements PersonInterface {
name: string = 'jack'
age: number = 18
say() { }
}
接口繼承接口
interface OneInterface {
x: number,
y: number,
}
interface TwoInterface extends OneInterface {
z: number
}
const Print: TwoInterface = {
x: 10,
y: 20,
z: 30,
}
多繼承接口(一次性繼承多個接口)
interface OneInterface {
x: number,
y: number,
}
interface TwoInterface extends OneInterface {
z: number
}
interface ThreeInterface extends OneInterface, TwoInterface {
date: Date
}
const Print2: ThreeInterface = {
x: 10,
y: 20,
z: 30,
date: new Date()
}
接口繼承類
class Boy {
name: string = ''
say(): void { }
}
interface Say extends Boy {
}
const Even: Boy = {
name: 'Even',
say() { }
}
Even.say()
泛型(Generies)
泛型的概念:
如果一個函數它的入參是any類型的,返回值也是any類型。如果輸入和輸出可以是任意類型,就會造成類型丟失,使得后續代碼無法繼續推導類型。而泛型就是解決這個問題的,它可以讓比較靈活的參數,保留數據類型,讓類型ts編譯器的推導變得更准確不會丟失某些信息。
簡單來講泛型是一種對未知類型的定義。相當於一個用來存儲類型注解的零時占位符,當一個函數傳入參數時這個零時占位符就保存下了這個傳入參數的類型,使它的返回結果也能夠得到更准確的推斷。
function echo(arg: any) { // 返回結果不確定類型
return arg
}
// 根據入參,返回某個具體的確定的類型
function echo<T>(arg: T): T {
return arg
}
// 泛型函數
function foo<T, P, N>(a: T, b: P, c: N): [N, P, T]{
return [c, b, a]
}
// 泛型類
class add <T> {
count: T;
add: (x: T, y: T) => T;
}
// 泛型約束
// 當入參不確定,但又需要保證入參必須具有某個屬性時,可以通過結合 interface + extends來對入參做約束,防止代碼在運行過程中拋出異常。
interface hasLength {
length: number;
}
function foo <T extends hasLength>(arg: T): T {
console.log(arg.length)
return arg
}
裝飾器(Decorator)
裝飾器雖然在ECMA中處於提案階段,但是通過babel等構建工具可以轉譯,在項目上早以普遍適用。typescript把它作為實驗性功能進行了引入。這種語法與APO技術比較相似,AOP技術可以切開一段代碼並將其分離到其他位置,從而提取重復性代碼,減少樣板性代碼。開啟裝飾器功能可以在tsconfig.json中設置 experimentDecorators: true
interface ITips { // 接口定義兩種提示信息
Log(text: string): void;
Warn(text: string): void;
}
class Print implements ITips { // 用於在控制台打印內容
Log(text: string): void {
console.log(text)
}
Warn(text: string): void {
console.warn(text)
}
}
const info1 = { type: 'log', text: 'this is text' };
const info2 = { type: 'warn', text: 'this is warn' };
function foo (someMthod: ITips) {
console.log(`current type: ${info1.type}`)
someMthod.Log(info1)
someMthod.Warn(info1)
}
foo(new Print())
以上面代碼為例,預期結果是根據不同類型執行不同的打印方法,把結果打印輸出到控制台,
Log方法只在type為log時調用,Warn方法只在type為warn時調用,而不是兩個方法都執行。要修改這段代碼就要在每個方法上面加一些邏輯 嵌套一層 if 語句判斷用以區分,如果類型較多都要判斷,那么修改量大,容易引入不易察覺的bug。這里用裝飾器就可以轉移樣板代碼,同時也解決了單一職責問題,把判斷的邏輯抽離開。
裝飾器接收3個參數:
- 參數一 要裝飾的對象
- 參數二 所要裝飾的屬性名
- 參數三 該屬性的描述對象,用來修改某些方法的行為
function fn(target, name, descriptor) {
// descriptor是一個對象 它的值如下:
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// }
}
通過裝飾器修改原方法:
function exec(type: string) {
return function(target: any, name: string, descriptor: any) {
}
}
搭建ts項目
基本的ts項目示例:
這里以react + ts為例創建一個簡單的ts-demo項目。
-
創建目錄並初始化:
mkdir ts-demo && cd ts-demo && npm init -y && touch webpack.config.js && mkdir src && touch ./src/index.tsx && npm i typescript webpack webpack-cli webpack-dev-server react react-dom babel-loader babel-preset-react-app html-webpack-plugin @types/react
-
webpack.config.js配置文件:
// webpack.config.js
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
process.env.NODE_ENV = 'development'
module.exports = {
entry: './src/index.tsx',
output: {
path: resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js']
},
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, './index.html')
})
],
module: {
rules: [
{
test: /\.(tsx?)|(jsx?)$/,
loader: 'babel-loader',
options: {
presets: [
['babel-preset-react-app', { flow: false, typescript: true }]
]
}
}
]
},
devServer: {
open: 'Google Chrome'
}
}
-
在項目根目錄創建一個空白的index.html文件。
-
/src/index.tsx文件:
// /src/index.tsx import React from 'react'; import { render } from 'react-dom'; function App() { return <h1>hello typescript</h1>; } document.write('<div id="root"></div>'); render(<App/>, document.querySelector('#root'));
-
運行:
npx webpack serve
完整的ts項目示例:
- 安裝:
sudo npm i -g xtie-cli
- 創建項目:
xt ts-project
- 運行:
cd ts-project && npm start