前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript
後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~html
文章列表:juejin.im/user/5a84f8…前端
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/vue
在本文我將和你們一塊兒玩轉Typescript,目前angular、deno已經開始使用typescript,而且咱們熟知的vue,在3.0也即將會使用typescript,能夠說,前端領域,typescript會逐漸變爲必備的技能,那麼,爲何typescript變得愈來愈火呢?html5
網上有各類typescript和javascript的對比,那麼在個人角度的理解,javascript是解釋型(動態)語言,能夠說是從上到下執行,在咱們開發過程當中,好比有語法錯誤等等,須要執行到這一行代碼才能知道,而typescript則像寫易語言那樣生成exe時,須要靜態編譯,而靜態編譯這個過程,會把代碼都檢查一遍,看是否經過檢測,最終才生成exe,typescript最終是也是編譯成javascript原生代碼的,只是在這個生成過程當中,會進行各類檢測,來檢查代碼是否符合語法啊規則啊,符合的話最終再編譯成javascript,規範了咱們代碼的編寫,同時也提升了代碼的複用以及組件化,在runtime階段爲咱們提早找到錯誤。java
typescript支持es5/es6的語法,而且擴展了javascript語法,更像java、c#、swift這種語言了。node
在前端nodejs很火,可是爲何在後端卻不火,很大程度也是由於nodejs也是解釋型(動態)語言,優點就是解釋型語言比較靈活,可是缺點也很明顯,用node開發後臺程序,開發一直爽,重構火葬場=。=!一旦重構了,就會出現不少問題,像Java、c#這類語言,很是嚴謹,類型檢查等很是嚴謹,而javascript呢,通常是靠咱們用肉眼去排查,很麻煩,typescript就是解決這一類問題的。react
總而言之,typescript是將來的趨勢,也是谷歌推薦的框架,我也是剛學typescript,不少都是站在前輩的肩膀總結的,廢話很少說,咱們開始進入正題吧!webpack
首先咱們全局安裝ios
npm i typescript -g
全局安裝完成後,咱們新建一個hello.ts
的ts文件
// hello.ts內容
let a = "邵威儒"
複製代碼
接下來咱們在命令行輸入tsc hello.ts
來編譯這個ts文件,而後會在同級目錄生成一個編譯好了的hello.js
文件
// hello.js內容
var = "邵威儒"
複製代碼
那麼咱們每次都要輸tsc hello.ts
命令來編譯,這樣很麻煩,可否讓它自動編譯?答案是能夠的,我平時使用vscode來開發,須要配置一下vscode就能夠。
首先咱們在命令行執行tsc --init
來生成配置文件,而後咱們在目錄下看到生成了一個tsconfig.json
文件
這個json文件裏有不少選項
target
是選擇編譯到什麼語法module
則是模塊類型outDir
則是輸出目錄,能夠指定這個參數到指定目錄接下來咱們須要開啓監控了,在vscode任務欄中
此時就會開啓監控了,會監聽ts的變化,而後自動去編譯。
java、c#是強類型語言,而js是弱類型語言,強弱類語言有什麼區別呢?typescript最大的優勢就是類型檢查,能夠幫你檢查你定義的類型和賦值的類型。
// 在js中,定義isFlag爲true,爲布爾類型boolean
let isFlag = true;
// 可是咱們也能夠從新給它賦值爲字符串
isFlag = "hello swr";
// 在ts中,定義isFlag爲true,爲布爾類型boolean
// 在變量名後加冒號和類型,如 :boolean
let isFlag:boolean = true
// 從新賦值到字符串類型會報錯
isFlag = "hello swr"
// 在java中,通常是這樣定義,要寫變量名也要寫類型名
// int a = 10;
// string name = "邵威儒"
複製代碼
let age:number = 28;
age = 29;
複製代碼
let name:string = "邵威儒"
name = "iamswr"
複製代碼
以上boolean、number、string
類型有個共性,就是能夠經過typeof
來獲取到是什麼類型,是基本數據類型。
那麼複雜的數據類型是怎麼處理的呢?
// 在js中
let pets = ["旺財","小黑"];
// 在ts中
// 須要注意的是,這個是一個字符串類型的數組
// 只能往裏面寫字符串,寫別的類型會報錯
let pets:string[] = ["旺財","小黑"];
// 另一種ts寫法
let pets:Array<string> = ["旺財","小黑"];
// 那麼若是想在數組裏放對象呢?
let pets:Array<object> = [{name:"旺財"},{name:"小黑"}];
// 那麼怎樣在一個數組中,隨意放string、number、boolean類型呢?
// 這裏的 | 至關於 或 的意思
let arr:Array<string|number|boolean> = ["hello swr",28];
// 想在數組中聽任意類型
let arr:Array<any> = ["hello swr",28,true]
複製代碼
什麼是元組類型?其實元組是數組的一種。
let person:[string,number] = ['邵威儒',28]
複製代碼
有點相似解構賦值,可是又不徹底是解構賦值,好比元組類型必須一一對應上,多了少了或者類型不對都會報錯。
元組類型是一個不可變的數組,長度、類型是不可變的。
枚舉在java中是從6.0才引入的一種類型,在java和ts中的關鍵字都是enum
。
什麼是枚舉?枚舉有點相似一一列舉,一個一個數出來,在易語言中,咱們會常常枚舉窗口,來找到本身想要的,通常用於值是某幾個固定的值,好比生肖(有12種)、星座(有12種)、性別(男女)等,這些值是固定的,能夠一個一個數出來。
爲何咱們要用枚舉呢?咱們能夠定義一些值,定義完了後能夠直接拿來用了,用的時候也不會賦錯值。
好比咱們普通賦值
// 咱們給性別賦值一個boy,可是咱們有時候手誤,可能輸成boy一、boy2了
// 這樣就會致使咱們賦值錯誤了
let sex = "boy"
複製代碼
既然這樣容易致使手誤賦錯值,那麼咱們能夠定義一個枚舉
// 定義一個枚舉類型的值
enum sex {
BOY,
GIRL
}
console.log(sex)
console.log(`邵威儒是${sex.BOY}`)
複製代碼
咱們看看轉爲es5語法是怎樣的
// 轉爲es5語法
"use strict";
var sex;
(function (sex) {
sex[sex["BOY"] = 0] = "BOY";
sex[sex["GIRL"] = 1] = "GIRL";
})(sex || (sex = {}));
console.log(sex); // 打印輸出{ '0': 'BOY', '1': 'GIRL', BOY: 0, GIRL: 1 }
console.log("\u90B5\u5A01\u5112\u662F" + sex.BOY); // 打印輸出 邵威儒是0
複製代碼
是否是感受有點像給對象添加各類屬性,而後這個屬性又有點像常量,而後經過對象去取這個屬性?
上面這樣寫,不是很友好,那麼咱們還能夠給BOY
GIRL
賦值
enum sex{
BOY="男",
GIRL="女"
}
複製代碼
// 轉化爲es5語法
// 咱們順便看看實現的原理
"use strict";
var sex;
// 首先這裏是一個自執行函數
// 而且把sex定義爲對象,傳參進給自執行函數
// 而後給sex對象添加屬性而且賦值
(function (sex) {
sex["BOY"] = "\u7537";
sex["GIRL"] = "\u5973";
})(sex || (sex = {}));
console.log(sex); // 打印輸出 { BOY: '男', GIRL: '女' }
console.log("\u90B5\u5A01\u5112\u662F" + sex.BOY); // 打印輸出 邵威儒是男
複製代碼
好比咱們實際項目中,特別是商城類,訂單會存在不少狀態流轉,那麼很是適合用枚舉
enum orderStatus {
WAIT_FOR_PAY = "待支付",
UNDELIVERED = "完成支付,待發貨",
DELIVERED = "已發貨",
COMPLETED = "已確認收貨"
}
複製代碼
到這裏,咱們會有一個疑慮,爲何咱們不這樣寫呢?
let orderStatus2 = {
WAIT_FOR_PAY : "待支付",
...
}
複製代碼
若是咱們直接寫對象的鍵值對方式,是能夠在外部修改這個值的,而咱們經過enum
則不能修改定義好的值了,更加嚴謹。
any
有好處也有壞處,特別是前端,不少時候寫類型的時候,幾乎分不清楚類型,任意去寫,寫起來很爽,可是對於後續的重構、迭代等是很是不友好的,會暴露出不少問題,某種程度來講,any類型就是放棄了類型檢查了。。。
好比咱們有這樣一個場景,就是須要獲取某一個dom節點
let btn = document.getElementById('btn');
btn.style.color = "blue";
複製代碼
此時咱們發如今ts中會報錯
由於咱們取這個dom節點,有可能取到,也有可能沒取到,當沒取到的時候,至關因而null,是沒有style這個屬性的。
那麼咱們能夠給它添加一個類型爲any
// 添加一個any類型,此時就不會報錯了,可是也至關於放棄了類型檢查了
let btn:any = document.getElementById('btn');
btn.style.color = "blue";
// 固然也有粗暴一些的方式,利用 ! 強制斷言
let btn = document.getElementById("btn");
btn!.style!.color = "blue";
// 能夠賦值任何類型的值
// 跟之前咱們var let聲明的如出一轍的
let person:any = "邵威儒"
person = 28
複製代碼
這個也沒什麼好說的,不過能夠看下下面的例子
// (string | number | null | undefined) 至關於這幾種類型
// 是 string 或 number 或 null 或 undefined
let str:(string | number | null | undefined)
str = "hello swr"
str = 28
str = null
str = undefined
複製代碼
void表示沒有任何類型,通常是定義函數沒有返回值。
// ts寫法
function say(name:string):void {
console.log("hello",name)
}
say("swr")
複製代碼
// 轉爲es5
"use strict";
function say(name) {
console.log("hello", name);
}
say("swr");
複製代碼
怎麼理解叫沒有返回值呢?此時咱們給函數return一個值
function say(name:string):void {
console.log("hello",name)
// return "ok" 會報錯
return "ok"
// return undefined 不會報錯
// return 不會報錯
}
say("swr")
複製代碼
那麼此時咱們但願這個函數返回一個字符串類型怎麼辦?
function say(name:string):string {
console.log("hello",name)
return "ok"
}
say("swr")
複製代碼
這個用得不多,通常是用於拋出異常。
let xx:never;
function error(message: string): never {
throw new Error(message);
}
error("error")
複製代碼
any
比較好理解,就是任何值均可以
let str:any = "hello swr"
str = 28
str = true
複製代碼
void
不能有任何值(返回值)
function say():void {
}
複製代碼
never
則很差理解,什麼叫永遠不會有返回值?
// 除了上面舉例的拋出異常之外,咱們看一下這個例子
// 這個loop函數,一旦開始執行,就永遠不會結束
// 能夠看出在while中,是死循環,永遠都不會有返回值,包括undefined
function loop():never {
while(true){
console.log("陷入死循環啦")
}
}
loop()
// 包括好比JSON.parse也是使用這種 never | any
function parse(str:string):(never | any){
return JSON.parse(str)
}
// 首先在正常狀況下,咱們傳一個JSON格式的字符串,是能夠正常獲得一個JSON對象的
let json = parse('{"name":"邵威儒"}')
// 可是有時候,傳進去的不必定是JSON格式的字符串,那麼就會拋出異常
// 此時就須要never了
let json = parse("iamswr")
複製代碼
也就是說,當一個函數執行的時候,被拋出異常打斷了,致使沒有返回值或者該函數是一個死循環,永遠沒有返回值,這樣叫作永遠不會有返回值。
實際開發中,是never和聯合類型來一塊兒用,好比
function say():(never | string) {
return "ok"
}
複製代碼
函數是這樣定義的
function say(name:string):void {
console.log("hello",name)
}
say("邵威儒")
複製代碼
形參和實參要徹底同樣,如想不同,則須要配置可選參數,可選參數放在後面
// 形參和實參一一對應,徹底同樣
function say(name:string,age:number):void {
console.log("hello",name,age)
}
say("邵威儒",28)
複製代碼
// 可選參數,用 ? 處理,只能放在後面
function say(name:string,age?:number):void {
console.log("hello",name,age)
}
say("邵威儒")
複製代碼
那麼如何設置默認參數呢?
// 在js中咱們是這樣寫的
function ajax(url,method="get"){
console.log(url,method)
}
// 在ts中咱們是這樣寫的
function ajax(url:string,method:string = "GET") {
console.log(url,method)
}
複製代碼
那麼如何設置剩餘參數呢?能夠利用擴展運算符
function sum(...args:Array<number>):number {
return eval(args.join("+"))
}
let total:number = sum(1,2,3,4,5)
console.log(total)
複製代碼
那麼如何實現函數重載呢?函數重載是java中很是有名的,在java中函數的重載,是指兩個或者兩個以上的同名函數,參數的個數和類型不同
// 好比說咱們如今有2個同名函數
function say(name:string){
}
function say(name:string,age:number){
}
// 那麼我想達到一個效果
// 當我傳參數name時,執行name:string這個函數
// 當我傳參數name和age時,執行name:string,age:number這個函數
// 此時該怎麼辦?
複製代碼
接下來看一下typescript中的函數重載
// 首先聲明兩個函數名同樣的函數
function say(val: string): void; // 函數的聲明
function say(val: number): void; // 函數的聲明
// 函數的實現,注意是在這裏是有函數體的
// 其實下面的say()不管怎麼執行,實際上就是執行下面的函數
function say(val: any):void {
console.log(val)
}
say("hello swr")
say(28)
複製代碼
在typescript中主要體現是同一個同名函數提供多個函數類型定義,函數實際上就只有一個,就是擁有函數體那個,若是想根據傳入值類型的不同執行不一樣邏輯,則須要在這個函數裏面進行一個類型判斷。
那麼這個函數重載有什麼做用呢?其實在ts中,函數重載只是用來限制參數的個數和類型,用來檢查類型的,並且重載不能拆開幾個函數,這一點和java的處理是不同的,須要注意。
// ts寫法
// 其實跟es6很是像,沒太大的區別
class Person{
// 這裏聲明的變量,是實例上的屬性
name:string
age:number
constructor(name:string,age:number){
// this.name和this.age必須在前面先聲明好類型
// name:string age:number
this.name = name
this.age = age
}
// 原型方法
say():string{
return "hello swr"
}
}
let p = new Person("邵威儒",28)
複製代碼
// 那麼轉爲es5呢?
"use strict";
var Person = /** @class */ (function () {
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
return "hello swr";
};
return Person;
}());
var p = new Person("邵威儒", 28);
複製代碼
// 類的繼承和es6也是差很少
class Parent{
// 這裏聲明的變量,是實例上的屬性
name:string
age:number
constructor(name:string,age:number){
// this.name和this.age必須在前面先聲明好類型
// name:string age:number
this.name = name
this.age = age
}
// 原型方法
say():string{
return "hello swr"
}
}
class Child extends Parent{
childName:string
constructor(name:string,age:number,childName:string){
super(name,age)
this.childName = childName
}
childSay():string{
return this.childName
}
}
let child = new Child("邵威儒",28,"bb")
console.log(child)
複製代碼
public
公開的,能夠供本身、子類以及其它類訪問protected
受保護的,能夠供本身、子類訪問,可是其餘就訪問不了private
私有的,只有本身訪問,而子類、其餘都訪問不了class Parent{
public name:string
protected age:number
private money:number
/**
* 也能夠簡寫爲
* constructor(public name:string,protected age:number,private money:number)
*/
constructor(name:string,age:number,money:number){
this.name = name
this.age = age
this.money = money
}
getName():string{
return this.name
}
getAge():number{
return this.age
}
getMoney():number{
return this.money
}
}
let p = new Parent("邵威儒",28,10)
console.log(p.name)
console.log(p.age) // 報錯
console.log(p.money) // 報錯
複製代碼
class Person{
// 這是類的靜態屬性
static name = "邵威儒"
// 這是類的靜態方法,須要經過這個類去調用
static say(){
console.log("hello swr")
}
}
let p = new Person()
Person.say() // hello swr
p.say() // 報錯
複製代碼
抽象類和方法,有點相似抽取共性出來,可是又不是具體化,好比說,世界上的動物都須要吃東西,那麼會把吃東西這個行爲,抽象出來。
若是子類繼承的是一個抽象類,子類必須實現父類裏的抽象方法,否則的話不能實例化,會報錯。
// 關鍵字 abstract 抽象的意思
// 首先定義個抽象類Animal
// Animal類有一個抽象方法eat
abstract class Animal{
// 其實是使用了public修飾符
// 若是添加private修飾符則會報錯
abstract eat():void;
}
// 須要注意的是,這個Animal類是不能實例化的
let animal = new Animal() // 報錯
// 抽象類的抽象方法,意思就是,須要在繼承這個抽象類的子類中
// 實現這個抽象方法,否則會報錯
// 報錯,由於在子類中沒有實現eat抽象方法
class Person extends Animal{
eat1(){
console.log("吃米飯")
}
}
// Dog類繼承Animal類後而且實現了抽象方法eat,因此不會報錯
class Dog extends Animal{
eat(){
console.log("吃骨頭")
}
}
複製代碼
這裏的接口,主要是一種規範,規範某些類必須遵照規範,和抽象類有點相似,可是不侷限於類,還有屬性、函數等。
// 假設我須要獲取用戶信息
// 咱們經過這樣的方式,規範必須傳name和age的值
function getUserInfo(user:{name:string,age:number}){
console.log(`${user.name} ${user.age}`)
}
getUserInfo({name:"邵威儒",age:28})
複製代碼
這樣看,仍是挺完美的,那麼問題就出現了,若是我另外還有一個方法,也是須要這個規範呢?
function getUserInfo(user:{name:string,age:number}){
console.log(`${user.name} ${user.age}`)
}
function getInfo(user:{name:string,age:number}){
console.log(`${user.name} ${user.age}`)
}
getUserInfo({name:"邵威儒",age:28})
getInfo({name:"iamswr",age:28})
複製代碼
能夠看出,函數getUserInfo
和getInfo
都遵循同一個規範,那麼咱們有辦法對這個規範複用嗎?
// 首先把須要複用的規範,寫到接口中 關鍵字 interface
interface infoInterface{
name:string,
age:number
}
// 而後把這個接口,替換到咱們須要複用的地方
function getUserInfo(user:infoInterface){
console.log(`${user.name} ${user.age}`)
}
function getInfo(user:infoInterface){
console.log(`${user.name} ${user.age}`)
}
getUserInfo({name:"邵威儒",age:28})
getInfo({name:"iamswr",age:28})
複製代碼
那麼有些參數可傳可不傳,該怎麼處理呢?
interface infoInterface{
name:string,
age:number,
city?:string // 該參數爲可選參數
}
function getUserInfo(user:infoInterface){
console.log(`${user.name} ${user.age} ${user.city}`)
}
function getInfo(user:infoInterface){
console.log(`${user.name} ${user.age}`)
}
getUserInfo({name:"邵威儒",age:28,city:"深圳"})
getInfo({name:"iamswr",age:28})
複製代碼
// 對一個函數的參數和返回值進行規範
interface mytotal {
// 左側是函數的參數,右側是函數的返回類型
(a:number,b:number) : number
}
let total:mytotal = function (a:number,b:number):number {
return a + b
}
console.log(total(10,20))
複製代碼
interface userInterface {
// index爲數組的索引,類型是number
// 右邊是數組裏爲字符串的數組成員
[index: number]: string
}
let arr: userInterface = ['邵威儒', 'iamswr'];
console.log(arr);
複製代碼
這個比較重要,由於寫react的時候會常用到類
// 首先實現一個接口
interface Animal{
// 這個類必須有name
name:string,
// 這個類必須有eat方法
// 規定eat方法的參數類型以及返回值類型
eat(any:string):void
}
// 關鍵字 implements 實現
// 由於接口是抽象的,須要經過子類去實現它
class Person implements Animal{
name:string
constructor(name:string){
this.name = name
}
eat(any:string):void{
console.log(`吃${any}`)
}
}
複製代碼
那麼若是想遵循多個接口呢?
interface Animal{
name:string,
eat(any:string):void
}
// 新增一個接口
interface Animal2{
sleep():void
}
// 能夠在implements後面經過逗號添加,和java是同樣的
// 一個類只能繼承一個父類,可是卻能遵循多個接口
class Person implements Animal,Animal2{
name:string
constructor(name:string){
this.name = name
}
eat(any:string):void{
console.log(`吃${any}`)
}
sleep(){
console.log('睡覺')
}
}
複製代碼
interface Animal{
name:string,
eat(any:string):void
}
// 像類同樣,經過extends繼承
interface Animal2 extends Animal{
sleep():void
}
// 由於Animal2類繼承了Animal
// 因此這裏遵循Animal2就至關於把Animal也繼承了
class Person implements Animal2{
name:string
constructor(name:string){
this.name = name
}
eat(any:string):void{
console.log(`吃${any}`)
}
sleep(){
console.log('睡覺')
}
}
複製代碼
泛型能夠支持不特定的數據類型,什麼叫不特定呢?好比咱們有一個方法,裏面接收參數,可是參數類型咱們是不知道,可是這個類型在方法裏面不少地方會用到,參數和返回值要保持一致性
// 假設咱們有一個需求,咱們不知道函數接收什麼類型的參數,也不知道返回值的類型
// 而咱們又須要傳進去的參數類型和返回值的類型保持一致,那麼咱們就須要用到泛型
// <T>的意思是泛型,即generic type
// 能夠看出value的類型也爲T,返回值的類型也爲T
function deal<T>(value:T):T{
return value
}
// 下面的<string>、<number>實際上用的時候再傳給上面的<T>
console.log(deal<string>("邵威儒"))
console.log(deal<number>(28))
複製代碼
class MyMath<T>{
// 定義一個私有屬性
private arr:T[] = []
// 規定傳參類型
add(value:T){
this.arr.push(value)
}
// 規定返回值的類型
max():T{
return Math.max.apply(null,this.arr)
}
}
// 這裏規定了類型爲number
// 至關於把T都替換成number
let mymath = new MyMath<number>()
mymath.add(1)
mymath.add(2)
mymath.add(3)
console.log(mymath.max())
// 假設咱們傳個字符串呢?
// 則會報錯:類型「"邵威儒"」的參數不能賦給類型「number」的參數。
mymath.add("邵威儒")
複製代碼
那麼咱們會思考,有了接口爲何還須要抽象類?
接口裏面只能放定義,抽象類裏面能夠放普通類、普通類的方法、定義抽象的東西。
好比說,咱們父類有10個方法,其中9個是實現過的方法,有1個是抽象的方法,那麼子類繼承過來,只須要實現這一個抽象的方法就能夠了,可是接口的話,則是全是抽象的,子類都要實現這些方法,簡而言之,接口裏面不能夠放實現,而抽象類能夠放實現。
這部分代碼我傳到了github地址 github.com/iamswr/ts_r… ,你們能夠結合來看
咱們用ts來搭建一下ts版的react版全家桶腳手架,接下來這部分須要webpack和react的相關基礎,我儘可能把註釋寫全,最好結合git代碼一塊兒看或者跟着敲一遍,這樣更好理解~
首先,咱們生成一個目錄ts_react_demo
,輸入npm init -y
初始化項目
而後在項目裏咱們須要一個.gitignore
來忽略指定目錄不傳到git上
進入.gitignore
輸入咱們須要忽略的目錄,通常是node_modules
// .gitignore
node_modules
複製代碼
接下來咱們準備下載相應的依賴包,這裏須要瞭解一個概念,就是類型定義文件
------------------------插入開始-------------------------
由於目前主流的第三方庫都是以javascript編寫的,若是用typescript開發,會致使在編譯的時候會出現不少找不到類型的提示,那麼若是讓這些庫也能在ts中使用呢?
咱們在ios開發的時候,會遇到swift、co混合開發,爲了解決兩種語法混合開發,是經過一個.h
格式的橋接頭來把二者聯繫起來,在js和ts,也存在這樣的概念。
類型定義文件(*.d.ts)
就是可以讓編輯器或者插件來檢測到第三方庫中js的靜態類型,這個文件是以.d.ts
結尾。
好比說react
的類型定義文件:github.com/DefinitelyT…
在typescript2.0中,是使用@type來進行類型定義,當咱們使用@type進行類型定義,typescript會默認查看./node_modules/@types
文件夾,能夠經過這樣來安裝這個庫的定義庫npm install @types/react --save
------------------------插入結束-------------------------
接下來,咱們須要下載相關依賴包,涉及到如下幾個包
------------------------安裝依賴包開始-------------------------
這部分代碼已傳到 github.com/iamswr/ts_r… 分支:webpack_done
- react // react的核心文件
- @types/react // 聲明文件
- react-dom // react dom的操做包
- @types/react-dom
- react-router-dom // react路由包
- @types/react-router-dom
- react-redux
- @types/react-redux
- redux-thunk // 中間件
- @types/redux-logger
- redux-logger // 中間件
- connected-react-router
複製代碼
執行安裝依賴包npm i react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom react-redux @types/react-redux redux-thunk redux-logger @types/redux-logger connected-react-router -S
- webpack // webpack的核心包
- webpack-cli // webapck的工具包
- webpack-dev-server // webpack的開發服務
- html-webpack-plugin // webpack的插件,能夠生成index.html文件
複製代碼
執行安裝依賴包npm i webpack webpack-cli webpack-dev-server html-webpack-plugin -D
,這裏的-D
至關於--save-dev
的縮寫,下載開發環境的依賴包
- typescript // ts的核心包
- ts-loader // 把ts編譯成指定語法好比es5 es6等的工具,有了它,基本不須要babel了,由於它會把咱們的代碼編譯成es5
- source-map-loader // 用於開發環境中調試ts代碼
複製代碼
執行安裝依賴包npm i typescript ts-loader source-map-loader -D
從上面能夠看出,基本都是模塊和聲明文件都是一對對出現的,有一些不是一對對出現,就是由於都集成到一塊兒去了。
聲明文件能夠在node_modules/@types/xx/xx
中找到。
------------------------安裝依賴包結束-------------------------
---------------------Typescript config配置開始----------------------
首先咱們要生成一個tsconfig.json
來告訴ts-loader
怎樣去編譯這個ts代碼
tsc --init
複製代碼
會在項目中生成了一個tsconfig.json
文件,接下來進入這個文件,來修改相關配置
// tsconfig.json
{
// 編譯選項
"compilerOptions": {
"target": "es5", // 編譯成es5語法
"module": "commonjs", // 模塊的類型
"outDir": "./dist", // 編譯後的文件目錄
"sourceMap": true, // 生成sourceMap方便咱們在開發過程當中調試
"noImplicitAny": true, // 每一個變量都要標明類型
"jsx": "react", // jsx的版本,使用這個就不須要額外使用babel了,會編譯成React.createElement
},
// 爲了加快整個編譯過程,咱們指定相應的路徑
"include": [
"./src/**/*"
]
}
複製代碼
---------------------Typescript config配置結束----------------------
---------------------webpack配置開始----------------------
./src/
下建立一個index.html
文件,而且添加<div id='app'></div>
標籤// ./src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id='app'></div>
</body>
</html>
複製代碼
./
下建立一個webpack配置文件webpack.config.js
// ./webpack.config.js
// 引入webpack
const webpack = require("webpack");
// 引入webpack插件 生成index.html文件
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path")
// 把模塊導出
module.exports = {
// 之前是jsx,由於咱們用typescript寫,因此這裏後綴是tsx
entry:"./src/index.tsx",
// 指定模式爲開發模式
mode:"development",
// 輸出配置
output:{
// 輸出目錄爲當前目錄下的dist目錄
path:path.resolve(__dirname,'dist'),
// 輸出文件名
filename:"index.js"
},
// 爲了方便調試,還要配置一下調試工具
devtool:"source-map",
// 解析路徑,查找模塊的時候使用
resolve:{
// 通常寫模塊不會寫後綴,在這裏配置好相應的後綴,那麼當咱們不寫後綴時,會按照這個後綴優先查找
extensions:[".ts",'.tsx','.js','.json']
},
// 解析處理模塊的轉化
module:{
// 遵循的規則
rules:[
{
// 若是這個模塊是.ts或者.tsx,則會使用ts-loader把代碼轉成es5
test:/\.tsx?$/,
loader:"ts-loader"
},
{
// 使用sourcemap調試
// enforce:pre表示這個loader要在別的loader執行前執行
enforce:"pre",
test:/\.js$/,
loader:"source-map-loader"
}
]
},
// 插件的配置
plugins:[
// 這個插件是生成index.html
new HtmlWebpackPlugin({
// 以哪一個文件爲模板,模板路徑
template:"./src/index.html",
// 編譯後的文件名
filename:"index.html"
}),
new webpack.HotModuleReplacementPlugin()
],
// 開發環境服務配置
devServer:{
// 啓動熱更新,當模塊、組件有變化,不會刷新整個頁面,而是局部刷新
// 須要和插件webpack.HotModuleReplacementPlugin配合使用
hot:true,
// 靜態資源目錄
contentBase:path.resolve(__dirname,'dist')
}
}
複製代碼
webpack.config.js
呢?這就須要咱們在package.json
配置一下腳本package.json
裏的script
,添加build
和dev
的配置{
"name": "ts_react_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"dev":"webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/react": "^16.7.13",
"@types/react-dom": "^16.0.11",
"@types/react-redux": "^6.0.10",
"@types/react-router-dom": "^4.3.1",
"connected-react-router": "^5.0.1",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-redux": "^6.0.0",
"react-router-dom": "^4.3.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"source-map-loader": "^0.2.4",
"ts-loader": "^5.3.1",
"typescript": "^3.2.1",
"webpack": "^4.27.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
複製代碼
index.tsx
,那麼咱們在./src/
下建立一個index.tsx
,而且在裏面寫入一段代碼,看看webpack是否可以正常編譯webpack.config.js
中entry
設置的入口文件是index.tsx
,而且在module
中的rules
會識別到.tsx
格式的文件,而後執行相應的ts-loader
// ./src/index.tsx
console.log("hello swr")
複製代碼
npm run build
一下,看看能不能正常編譯npm run build
複製代碼
嗯,很好,編譯成功了,咱們能夠看看./dist/
下生成了index.html
index.js
index.js.map
三個文件
那麼咱們在開發過程當中,不會每次都npm run build
來看修改的結果,那麼咱們平時開發過程當中可使用npm run dev
npm run dev
複製代碼
這樣就啓動成功了一個http://localhost:8080/
的服務了。
接下來咱們修改webpack.config.js
的配置,新增一個devServer
配置對象,代碼更新在上面webpack.config.js
中,配置開發環境的服務以及熱更新。
接下來咱們看看熱更新是否配置正常,在./src/index.tsx
中新增一個console.log('hello 邵威儒')
,咱們發現瀏覽器的控制檯會自動打印出這一個輸出,說明配置正常了。
---------------------webpack配置結束----------------------
---------------------計數器組件開始----------------------
這部分代碼已傳到 github.com/iamswr/ts_r… 分支:CounterComponent_1
接下來咱們開始寫react,咱們按照官方文檔( redux.js.org/ ),寫一個計數器來演示。
首先咱們在./src/
下建立一個文件夾components
,而後在./src/components/
下建立文件Counter.tsx
// ./src/components/Counter.tsx
// import React from "react"; // 以前的寫法
// 在ts中引入的寫法
import * as React from "react";
export default class CounterComponent extends React.Component{
// 狀態state
state = {
number:0
}
render(){
return(
<div>
<p>{this.state.number}</p>
<button onClick={()=>this.setState({number:this.state.number + 1})}>+</button>
</div>
)
}
}
複製代碼
咱們發現,其實除了引入import * as React from "react"
之外,其他的和以前的寫法沒什麼不一樣。
接下來咱們到./src/index.tsx
中把這個組件導進來
// ./src/index.tsx
import * as React from "react";
import * as ReactDom from "react-dom";
import CounterComponent from "./components/Counter";
// 把咱們的CounterComponent組件渲染到id爲app的標籤內
ReactDom.render(<CounterComponent />,document.getElementById("app"))
複製代碼
這樣咱們就把這個組件引進來了,接下來咱們看下是否可以成功跑起來
到目前爲止,感受用ts寫react仍是跟之前差很少,沒什麼區別,要記住,ts最大的特色就是類型檢查,能夠檢驗屬性的狀態類型。
假設咱們須要在./src/index.tsx
中給<CounterComponent />
傳一個屬性name
,而CounterComponent
組件須要對這個傳入的name
進行類型校驗,好比說只容許傳字符串。
./src/index.tsx
中修改一下
ReactDom.render(<CounterComponent name="邵威儒" />,document.getElementById("app"))
複製代碼
而後須要在./src/components/Counter.tsx
中寫一個接口來對這個name
進行類型校驗
// ./src/components/Counter.tsx
// import React from "react"; // 以前的寫法
// 在ts中引入的寫法
import * as React from "react";
// 寫一個接口對name進行類型校驗
// 若是咱們不寫校驗的話,在外部傳name進來會報錯的
interface IProps{
name:string,
}
// 咱們還能夠用接口約束state的狀態
interface IState{
number: number
}
// 把接口約束的規則寫在這裏
// 若是傳入的name不符合類型會報錯
// 若是state的number屬性不符合類型也會報錯
export default class CounterComponent extends React.Component<IProps, IState>{
// 狀態state
state = {
number:0
}
render(){
return(
<div>
<p>{this.state.number}</p>
<p>{this.props}</p>
<button onClick={()=>this.setState({number:this.state.number + 1})}>+</button>
</div>
)
}
}
複製代碼
接下來看看如何在redux中使用ts呢?
---------------------計數器組件結束----------------------
---------------------Redux開始----------------------
這部分代碼已傳到 github.com/iamswr/ts_r…
分支:redux_thunk
上面state中的number就不放在組件裏了,咱們放到redux中,接下來咱們使用redux。
首先在./src/
建立store
目錄,而後在./src/store/
建立一個文件index.tsx
// .src/store/index.tsx
import { createStore } from "redux";
// 引入reducers
import reducers from "./reducers";
// 接着建立倉庫
let store = createStore(reducers);
// 導出store倉庫
export default store;
複製代碼
而後咱們須要建立一個reducers
,在./src/store/
建立一個目錄reducers
,該目錄下再建立一個文件index.tsx
。
可是咱們還須要對reducers
中的函數參數進行類型校驗,並且這個類型校驗不少地方須要複用,那麼咱們須要把這個類型校驗單獨抽離出一個文件。
那麼咱們須要在./src/
下建立一個types
目錄,該目錄下建立一個文件index.tsx
// ./src/types/index.tsx
// 導出一個接口
export interface Store{
// 咱們須要約束的屬性和類型
number:number
}
複製代碼
回到./src/store/reducers/index.tsx
// ./src/store/reducers/index.tsx
// 導入類型校驗的接口
// 用來約束state的
import { Store } from "../../types/index"
// 咱們須要給number賦予默認值
let initState:Store = { number:0 }
// 把接口寫在state:Store
export default function (state:Store=initState,action) {
// 拿到老的狀態state和新的狀態action
// action是一個動做行爲,而這個動做行爲,在計數器中是具有 加 或 減 兩個功能
}
複製代碼
上面這段代碼暫時先這樣,由於須要用到action
,咱們如今去配置一下action
相關的,首先咱們在./src/store
下建立一個actions
目錄,而且在該目錄下建立文件counter.tsx
。
由於配置./src/store/actions/counter.tsx
會用到動做類型,而這個動做類型是屬於常量,爲了更加規範咱們的代碼,咱們在./src/store/
下建立一個action-types.tsx
,裏面寫相應常量
// ./src/store/action-types.tsx
export const ADD = "ADD";
export const SUBTRACT = "SUBTRACT";
複製代碼
回到./src/store/actions/counter.tsx
// ./src/store/actions/counter.tsx
import * as types from "../action-types";
export default {
add(){
// 須要返回一個action對象
// type爲動做的類型
return { type: types.ADD}
},
subtract(){
// 須要返回一個action對象
// type爲動做的類型
return { type: types.SUBTRACT}
}
}
複製代碼
咱們能夠想一下,上面return { type:types.ADD }
其實是返回一個action對象
,未來使用的時候,是會傳到./src/store/reducers/index.tsx
的action
中,那麼咱們怎麼定義這個action
的結構呢?
// ./src/store/actions/counter.tsx
import * as types from "../action-types";
// 定義兩個接口,分別約束add和subtract的type類型
export interface Add{
type:typeof types.ADD
}
export interface Subtract{
type:typeof types.SUBTRACT
}
// 再導出一個type
// type是用來給類型起別名的
// 這個actions裏是一個對象,會有不少函數,每一個函數都會返回一個action
// 而 ./store/reducers/index.tsx中的action會是下面某一個函數的返回值
export type Action = Add | Subtract
// 把上面定義好的接口做用於下面
// 約束返回值的類型
export default {
add():Add{
// 須要返回一個action對象
// type爲動做的類型
return { type: types.ADD}
},
subtract():Subtract{
// 須要返回一個action對象
// type爲動做的類型
return { type: types.SUBTRACT}
}
}
複製代碼
接着咱們回到./store/reducers/index.tsx
通過上面一系列的配置,咱們能夠給action
使用相應的接口約束了而且根據不一樣的action
動做行爲來進行不一樣的處理
// ./store/reducers/index.tsx
// 導入類型校驗的接口
// 用來約束state的
import { Store } from "../../types/index"
// 導入約束action的接口
import { Action } from "../actions/counter"
// 引入action動做行爲的常量
import * as types from "../action-types"
// 咱們須要給number賦予默認值
let initState:Store = { number:0 }
// 把接口寫在state:Store
export default function (state:Store=initState,action:Action) {
// 拿到老的狀態state和新的狀態action
// action是一個動做行爲,而這個動做行爲,在計數器中是具有 加 或 減 兩個功能
// 判斷action的行爲類型
switch (action.type) {
case types.ADD:
// 當action動做行爲是ADD的時候,給number加1
return { number:state.number + 1 }
break;
case types.SUBTRACT:
// 當action動做行爲是SUBTRACT的時候,給number減1
return { number:state.number - 1 }
break;
default:
// 當沒有匹配到則返回本來的state
return state
break;
}
}
複製代碼
接下來,咱們怎麼樣把組件和倉庫創建起關係呢?
首先進入./src/index.tsx
// ./src/index.tsx
import * as React from "react";
import * as ReactDom from "react-dom";
// 引入redux這個庫的Provider組件
import { Provider } from "react-redux";
// 引入倉庫
import store from './store'
import CounterComponent from "./components/Counter";
// 用Provider包裹CounterComponent組件
// 而且把store傳給Provider
// 這樣Provider能夠向它的子組件提供store
ReactDom.render((
<Provider store={store}>
<CounterComponent />
</Provider>
),document.getElementById("app"))
複製代碼
咱們到組件內部創建鏈接,./src/components/Counter.tsx
// ./src/components/Counter.tsx
import * as React from "react";
// 引入connect,讓組件和倉庫創建鏈接
import { connect } from "react-redux";
// 引入actions,用於傳給connect
import actions from "../store/actions/counter";
// 引入接口約束
import { Store } from "../types";
// 接口約束
interface IProps{
number:number,
// add是一個函數
add:any,
// subtract是一個函數
subtract:any
}
class CounterComponent extends React.Component<IProps>{
render(){
// 利用解構賦值取出
// 這裏好比和IProps保持一致,不對應則會報錯,由於接口約束了必須這樣
let { number,add,subtract } = this.props
return(
<div>
<p>{number}</p><br/>
<button onClick={add}>+</button><br />
<button onClick={subtract}>-</button>
</div>
)
}
}
// 這個connect須要執行兩次,第二次須要咱們把這個組件CounterComponent傳進去
// connect第一次執行,須要兩個參數,
// 須要傳給connect的函數
let mapStateToProps = function (state:Store) {
return state
}
export default connect(
mapStateToProps,
actions
)(CounterComponent);
複製代碼
接下來咱們看下是否配置成功
成功了,能夠經過加減按鈕對number
進行控制
其實搞來搞去,跟原來的寫法差很少,主要就是ts會進行類型檢查。
若是對number
進行異步修改,該怎麼處理?這就須要咱們用到redux-thunk
接着咱們回到./src/store/index.tsx
// ./src/store/index.tsx
// 須要使用到thunk,因此引入中間件applyMiddleware
import { createStore, applyMiddleware } from "redux";
// 引入reducers
import reducers from "./reducers";
// 引入redux-thunk,處理異步
// 如今主流處理異步的是saga和thunk
import thunk from "redux-thunk";
// 引入日誌
import logger from "redux-logger";
// 接着建立倉庫和中間件
let store = createStore(reducers, applyMiddleware(thunk,logger));
// 導出store倉庫
export default store;
複製代碼
接着咱們回來./src/store/actions
,新增一個異步的動做行爲
// ./src/store/actions
import * as types from "../action-types";
// 定義兩個接口,分別約束add和subtract的type類型
export interface Add{
type:typeof types.ADD
}
export interface Subtract{
type:typeof types.SUBTRACT
}
// 再導出一個type
// type是用來給類型起別名的
// 這個actions裏是一個對象,會有不少函數,每一個函數都會返回一個action
// 而 ./store/reducers/index.tsx中的action會是下面某一個函數的返回值
export type Action = Add | Subtract
// 把上面定義好的接口做用於下面
// 約束返回值的類型
export default {
add():Add{
// 須要返回一個action對象
// type爲動做的類型
return { type: types.ADD}
},
subtract():Subtract{
// 須要返回一個action對象
// type爲動做的類型
return { type: types.SUBTRACT}
},
// 一秒後才執行這個行爲
addAsync():any{
return function (dispatch:any,getState:any) {
setTimeout(function(){
// 當1秒事後,會執行dispatch,派發出去,而後改變倉庫的狀態
dispatch({type:types.ADD})
}, 1000);
}
}
}
複製代碼
到./src/components/Counter.tsx
組件內,使用這個異步
// ./src/components/Counter.tsx
import * as React from "react";
// 引入connect,讓組件和倉庫創建鏈接
import { connect } from "react-redux";
// 引入actions,用於傳給connect
import actions from "../store/actions/counter";
// 引入接口約束
import { Store } from "../types";
// 接口約束
interface IProps{
number:number,
// add是一個函數
add:any,
// subtract是一個函數
subtract:any,
addAsync:any
}
class CounterComponent extends React.Component<IProps>{
render(){
// 利用解構賦值取出
// 這裏好比和IProps保持一致,不對應則會報錯,由於接口約束了必須這樣
let { number, add, subtract, addAsync } = this.props
return(
<div>
<p>{number}</p><br/>
<button onClick={add}>+</button><br/>
<button onClick={subtract}>-</button><br/>
<button onClick={addAsync}>異步加1</button>
</div>
)
}
}
// 這個connect須要執行兩次,第二次須要咱們把這個組件CounterComponent傳進去
// connect第一次執行,須要兩個參數,
// 須要傳給connect的函數
let mapStateToProps = function (state:Store) {
return state
}
export default connect(
mapStateToProps,
actions
)(CounterComponent);
複製代碼
接下來到瀏覽器看看可否成功
完美~ 可以正常執行
---------------------Redux結束----------------------
---------------------合併reducers開始----------------------
這部分代碼已傳到 github.com/iamswr/ts_r…
分支:reducers_combineReducers
假如咱們的項目裏面,有兩個計數器,並且它倆是徹底沒有關係的,狀態也是徹底獨立的,這個時候就須要用到合併reducers了。
下面這些步驟,其實有時間的話能夠本身實現一次,由於在實現的過程當中,你會發現,由於有了ts的類型校驗,咱們在修改的過程當中,會給咱們很是明確的報錯,而不像之前,寫一段,運行一下,再看看哪裏報錯,而ts是直接在編輯器中就提示報錯了,讓咱們能夠很是舒服地去根據報錯和提示,去相應的地方修改。
首先咱們把涉及到計數器組件的代碼拷貝兩份,由於改動太多了,能夠在git上看,改動後的目錄如圖
首先咱們新增action
的動做行爲類型,在./src/store/action-types.tsx
export const ADD = "ADD";
export const SUBTRACT = "SUBTRACT";
// 新增做爲Counter2.tsx中的actions動做行爲類型
export const ADD2 = "ADD2";
export const SUBTRACT2 = "SUBTRACT2";
複製代碼
而後修改接口文件,./src/types/index.tsx
// ./src/types/index.tsx
// 把Counter/Counter2組件彙總到一塊兒
export interface Store {
counter: Counter,
counter2: Counter2
}
// 分別對應Counter組件
export interface Counter {
number: number
}
// 分別對應Counter2組件
export interface Counter2 {
number: number
}
複製代碼
而後把./src/store/actions/counter.tsx
文件拷貝在當前目錄而且修更名稱爲counter2.tsx
// ./src/store/actions/counter2.tsx
import * as types from "../action-types";
export interface Add{
type:typeof types.ADD2
}
export interface Subtract{
type:typeof types.SUBTRACT2
}
export type Action = Add | Subtract
export default {
add():Add{
return { type: types.ADD2}
},
subtract():Subtract{
return { type: types.SUBTRACT2}
},
addAsync():any{
return function (dispatch:any,getState:any) {
setTimeout(function(){
dispatch({type:types.ADD2})
}, 1000);
}
}
}
複製代碼
而後把./src/store/reduces/index.tsx
拷貝而且更名爲counter.tsx
和counter2.tsx
counter.tsx
import { Counter } from "../../types"
import { Action } from "../actions/counter"
import * as types from "../action-types"
let initState: Counter = { number:0 }
export default function (state: Counter=initState,action:Action) {
switch (action.type) {
case types.ADD:
return { number:state.number + 1 }
break;
case types.SUBTRACT:
return { number:state.number - 1 }
break;
default:
return state
break;
}
}
複製代碼
counter2.tsx
import { Counter2 } from "../../types"
import { Action } from "../actions/counter2"
import * as types from "../action-types"
let initState:Counter2 = { number:0 }
export default function (state:Counter2=initState,action:Action) {
switch (action.type) {
case types.ADD2:
return { number:state.number + 1 }
break;
case types.SUBTRACT2:
return { number:state.number - 1 }
break;
default:
return state
break;
}
}
複製代碼
index.tsc
combineReducers
方法,進行合併的,由於咱們一個項目當中確定是存在很是多個reducer,因此統一在這裏處理。// 引入合併方法
import { combineReducers } from "redux";
// 引入須要合併的reducer
import counter from "./counter";
// 引入須要合併的reducer
import counter2 from "./counter2";
// 合併
let reducers = combineReducers({
counter,
counter2,
});
export default reducers;
複製代碼
最後修改組件,進入./src/components/
,其中
// ./src/components/Counter.tsx
import * as React from "react";
import { connect } from "react-redux";
import actions from "../store/actions/counter";
import { Store, Counter } from "../types";
interface IProps{
number:number,
add:any,
subtract:any,
addAsync:any
}
class CounterComponent extends React.Component<IProps>{
render(){
let { number, add, subtract, addAsync } = this.props
return(
<div>
<p>{number}</p><br/>
<button onClick={add}>+</button><br/>
<button onClick={subtract}>-</button><br/>
<button onClick={addAsync}>異步加1</button>
</div>
)
}
}
let mapStateToProps = function (state: Store): Counter {
return state.counter;
}
export default connect(
mapStateToProps,
actions
)(CounterComponent);
複製代碼
// ./src/components/Counter2.tsx
import * as React from "react";
// 引入connect,讓組件和倉庫創建鏈接
import { connect } from "react-redux";
// 引入actions,用於傳給connect
import actions from "../store/actions/counter2";
// 引入接口約束
import { Store, Counter2 } from "../types";
// 接口約束
interface IProps{
number:number,
// add是一個函數
add:any,
// subtract是一個函數
subtract:any,
addAsync:any
}
class CounterComponent1 extends React.Component<IProps>{
render(){
// 利用解構賦值取出
// 這裏好比和IProps保持一致,不對應則會報錯,由於接口約束了必須這樣
let { number, add, subtract, addAsync } = this.props
return(
<div>
<p>{number}</p><br/>
<button onClick={add}>+</button><br/>
<button onClick={subtract}>-</button><br/>
<button onClick={addAsync}>異步加1</button>
</div>
)
}
}
// 這個connect須要執行兩次,第二次須要咱們把這個組件CounterComponent傳進去
// connect第一次執行,須要兩個參數,
// 須要傳給connect的函數
let mapStateToProps = function (state: Store): Counter2 {
return state.counter2;
}
export default connect(
mapStateToProps,
actions
)(CounterComponent1);
複製代碼
到目前爲止,咱們完成了reducers的合併了,那麼咱們看看效果如何,首先咱們給./src/index.tsc
添加Counter2
組件,這樣的目的是與Counter
組件徹底獨立,互不影響,可是又可以最終合併到readucers
// ./src/index.tsx
import * as React from "react";
import * as ReactDom from "react-dom";
import { Provider } from "react-redux";
import store from './store'
import CounterComponent from "./components/Counter";
import CounterComponent2 from "./components/Counter2";
ReactDom.render((
<Provider store={store}>
<CounterComponent />
<br/>
<CounterComponent2 />
</Provider>
),document.getElementById("app"))
複製代碼
而後到瀏覽器看看效果~
完美,這樣咱們就處理完reducers的合併了,在這個過程當中,經過ts的類型檢測,我再也不像之前那樣,寫一段代碼,運行看看是否報錯,再定位錯誤,而是根據ts在編輯器的報錯信息,直接定位,修改,把錯誤扼殺在搖籃。
---------------------合併reducers結束----------------------
---------------------路由開始----------------------
這部分代碼已傳到 github.com/iamswr/ts_r…
分支:HashRouter
首先進入./src/index.tsx
導入咱們的路由所須要的依賴包
// ./src/index.tsx
import * as React from "react";
import * as ReactDom from "react-dom";
import { Provider } from "react-redux";
import store from './store'
// 引入路由
// 路由的容器:HashRouter as Router
// 路由的規格:Route
// Link組件
import { HashRouter as Router,Route,Link } from "react-router-dom"
import CounterComponent from "./components/Counter";
import CounterComponent2 from "./components/Counter2";
import Counter from "./components/Counter";
function Home() {
return <div>home</div>
}
ReactDom.render((
<Provider store={store}>
{/* 路由組件 */}
<Router>
{/* 放兩個路由規則須要在外層套個React.Fragment */}
<React.Fragment>
{/* 增長導航 */}
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/counter">Counter</Link></li>
<li><Link to="/counter2">Counter2</Link></li>
</ul>
{/* 當路徑爲 / 時是home組件 */}
{/* 爲了不home組件一直渲染,咱們能夠添加屬性exact */}
<Route exact path="/" component={Home}/>
<Route path="/counter" component={CounterComponent}/>
<Route path="/counter2" component={CounterComponent2} />
</React.Fragment>
</Router>
</Provider>
),document.getElementById("app"))
複製代碼
接下來看看路由是否配置成功
完美,成功了,也能夠看出Counter
Counter2
組件是互相獨立的。
可是咱們發現了一個問題,http://localhost:8080/#/counter
中有個#
的符號,很是不美觀,那麼咱們如何變成http://localhost:8080/counter
這樣呢?
這部分代碼已傳到 github.com/iamswr/ts_r…
分支:connected-react-router
咱們仍是進入./src/index.tsx
,
把import { HashRouter as Router,Route,Link } from "react-router-dom"
中的HashRouter
更改成BrowserRouter
再從瀏覽器訪問http://localhost:8080/
再跳轉到http://localhost:8080/counter
發現仍是很完美
可是有個很大的問題,就是咱們直接訪問http://localhost:8080/counter
會找不到路由
這是怎麼回事?由於咱們的是單頁面應用,無論路由怎麼變動,實際上都是訪問index.html
這個文件,因此當咱們訪問根路徑的時候,可以正常訪問,由於index.html
文件就放在這個目錄下,可是當咱們經過非根路徑的路由訪問,則出錯了,是由於咱們在相應的路徑沒有這個文件,因此出錯了。
從這一點也能夠衍生出一個實戰經驗,咱們平時項目部署上線的時候,會出現這個問題,通常咱們都是用ngxin
來把訪問的路徑都是指向index.html
文件,這樣就可以正常訪問了。
那麼針對目前咱們這個狀況,咱們能夠經過修改webpack
配置,讓路由無論怎麼訪問,都是指向咱們制定的index.html
文件。
進入./webpack.config.js
,在devServer
的配置對象下新增一些配置
// ./webpack.config.js
...
// 開發環境服務配置
devServer:{
// 啓動熱更新,當模塊、組件有變化,不會刷新整個頁面,而是局部刷新
// 須要和插件webpack.HotModuleReplacementPlugin配合使用
hot:true,
// 靜態資源目錄
contentBase:path.resolve(__dirname,'dist'),
// 無論訪問什麼路徑,都重定向到index.html
historyApiFallback:{
index:"./index.html"
}
}
...
複製代碼
修改webpack
配置須要重啓服務,而後重啓服務,看看瀏覽器可否正常訪問http://localhost:8080/counter
完美,無論訪問什麼路徑,都可以正常重定向到index.html
了
接下來,完美這個路由的路徑,如何同步到倉庫當中呢?
之前是用一個叫react-router-redux
的庫,把路由和redux
結合到一塊兒的,react-router-redux
挺好用的,可是這個庫再也不維護了,被廢棄了,因此如今推薦使用connected-react-router
這個庫,能夠把路由狀態映射到倉庫當中。
首先咱們在./src
下建立文件history.tsx
,
// ./src/history.tsx
// 引入一個基於html5 api的history的createBrowserHistory
import { createBrowserHistory } from "history";
// 建立一個history
let history = createBrowserHistory();
// 導出
export default history;
複製代碼
假設我有一個需求,就是我不經過Link
跳轉頁面,而是經過編程式導航,觸發一個動做,而後這個動做會派發出去,並且把路由信息放到redux中,供我之後查看。
咱們進入./src/store/reducers/index.tsx
// ./src/store/reducers/index.tsx
import { combineReducers } from "redux";
import counter from "./counter";
import counter2 from "./counter2";
// 引入connectRouter
import { connectRouter } from "connected-react-router";
import history from "../../history";
let reducers = combineReducers({
counter,
counter2,
// 把history傳到connectRouter函數中
router: connectRouter(history)
});
export default reducers;
複製代碼
咱們進入./src/store/index.tsx
來添加中間件
// ./src/store/index.tsx
複製代碼
咱們進入./src/store/actions/counter.tsx
加個goto
方法用來跳轉。
// ./src/store/actions/counter.tsx
複製代碼
咱們進入./src/components/Counter.tsx
中加個按鈕,當我點擊按鈕的時候,會向倉庫派發action,倉庫的action裏有中間件,會把咱們這個請求攔截到,而後跳轉。
// ./src/components/Counter.tsx
import * as React from "react";
import { connect } from "react-redux";
import actions from "../store/actions/counter";
import { Store, Counter } from "../types";
interface IProps{
number:number,
add:any,
subtract:any,
addAsync:any,
goto:any
}
class CounterComponent extends React.Component<IProps>{
render(){
let { number, add, subtract, addAsync,goto } = this.props
return(
<div>
<p>{number}</p><br/>
<button onClick={add}>+</button><br/>
<button onClick={subtract}>-</button><br/>
<button onClick={addAsync}>異步加1</button>
{/* 增長一個按鈕,而且點擊的時候執行goto方法實現跳轉 */}
<button onClick={()=>goto('/counter2')}>跳轉到/counter2</button>
</div>
)
}
}
let mapStateToProps = function (state: Store): Counter {
return state.counter;
}
export default connect(
mapStateToProps,
actions
)(CounterComponent);
複製代碼
---------------------路由結束----------------------
到此爲止,用typesript把react全家桶簡單過了一遍,之因此寫typesript版react全家桶,是爲了讓你們知道這個typesript在實際項目中,是怎麼使用的,可是涉及到各個文件跳來跳去,有時候很簡單的幾句話能夠帶過,可是爲了你們明白,寫得也囉裏囉嗦的,剛開始使用typesript,感受效率也沒怎麼提升,可是在慢慢使用當中,會發現,確實不少錯誤,可以提早幫咱們發現,這對之後項目的維護、重構顯得很是重要,不然未來項目大了,哪裏出現錯誤了,估計也須要排查很是久的時間,typesript未來或許會成爲趨勢,做爲前端,總要不斷學習的嘛