Typescript,Vue you enjoy it ?

前言


  據說typescript最近愈來愈流行,藉着開啓新項目,用vue + typescript嘗試了下。夏日炎炎,趁着乘涼的略微時間,進行一下項目總結,望大佬們多多指點!javascript

項目配置


初始化

vue-cli3腳手架走一波,手動選擇使用typescriptblablabla...具體就不細說啦. 首先附上一份官方目錄配置:css

├── public                          // 靜態頁面
├── src                             // 主目錄
    ├── assets                      // 靜態資源
    ├── components                  // 組件
    ├── views                       // 頁面
    ├── App.vue                     // 頁面主入口
    ├── main.ts                     // 腳本主入口
    ├── registerServiceWorker.ts    // PWA 配置
    ├── router.ts                   // 路由
    ├── shims-tsx.d.ts              // 相關 tsx 模塊注入
    ├── shims-vue.d.ts              // Vue 模塊注入
    └── store.ts                    // vuex 配置
├── tests                           // 測試用例
├── .postcssrc.js                   // postcss 配置
├── package.json                    // 依賴
├── tsconfig.json                   // ts 配置
└── tslint.json                     // tslint 配置
複製代碼

而後附上我的的目錄html

├── public                          // 靜態頁面
├── src                             // 主目錄
    ├── api                         // 後端接口
    ├── assets                      // 靜態資源
    ├── components                  // 組件
    ├── filters                     // 過濾
    ├── router                      // 路由配置
    ├── store                       // vuex 配置
    ├── utils                       // 工具方法
    ├── mixins                      // 混合
    ├── views                       // 頁面
    ├── App.vue                     // 頁面主入口
    ├── main.ts                     // 腳本主入口
    ├── registerServiceWorker.ts    // PWA 配置
    ├── shims-tsx.d.ts              // 相關 tsx 模塊注入
    ├── shims-vue.d.ts              // Vue 模塊注入
├── tests                           // 測試用例
├── .editorconfig                   // 編輯相關配置
├── .npmrc                          // npm 源配置
├── .postcssrc.js                   // postcss 配置
├── babel.config.js                 // preset 記錄
├── cypress.json                    // e2e plugins
├── package.json                    // 依賴
├── README.md                       // 項目 readme
├── tsconfig.json                   // ts 配置
├── tslint.json                     // tslint 配置
└── vue.config.js                   // webpack 配置
複製代碼

ts相關配置

tsconfig.json 配置


首先貼一份網上找的配置,傳送門:官方完整配置vue

{
  // 編譯選項
  "compilerOptions": {
    // 輸出目錄
    "outDir": "./output",
    // 是否包含能夠用於 debug 的 sourceMap
    "sourceMap": true,
    // 以嚴格模式解析
    "strict": true,
    // 採用的模塊系統
    "module": "esnext",
    // 如何處理模塊
    "moduleResolution": "node",
    // 編譯輸出目標 ES 版本
    "target": "es5",
    // 容許從沒有設置默認導出的模塊中默認導入
    "allowSyntheticDefaultImports": true,
    // 將每一個文件做爲單獨的模塊
    "isolatedModules": false,
    // 啓用裝飾器
    "experimentalDecorators": true,
    // 啓用設計類型元數據(用於反射)
    "emitDecoratorMetadata": true,
    // 在表達式和聲明上有隱含的any類型時報錯
    "noImplicitAny": false,
    // 不是函數的全部返回路徑都有返回值時報錯。
    "noImplicitReturns": true,
    // 從 tslib 導入外部幫助庫: 好比__extends,__rest等
    "importHelpers": true,
    // 編譯過程當中打印文件名
    "listFiles": true,
    // 移除註釋
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    // 在 .tsx文件裏支持JSX: "React""Preserve"。查看 JSX
    "jsx": "preserve",
    // 容許編譯javascript文件
    "allowJs": true,
    // 解析非相對模塊名的基準目錄
    "baseUrl": "./",
    // 指定特殊模塊的路徑
    "paths": {
      "jquery": [
        "node_modules/jquery/dist/jquery"
      ]
    },
    // 編譯過程當中須要引入的庫文件的列表
    "lib": [
      "dom",
      "es2015",
      "es2015.promise"
    ]
  }
}
複製代碼

   而後再貼一份本身的配置,能夠根據我的定製, 其中,若是指定了exclude選項,編譯器會包含當前目錄及子目錄下的全部TypeScript文件(*.ts 或 *.tsx),不包括這些指定要排除的文件java

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": false,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "strictFunctionTypes": false,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "mocha",
      "chai"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "files": [
    "shims-vue.d.ts"
  ],
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}
複製代碼

tslint.json 配置

{
  "defaultSeverity": "error",
  "jsRules": {},
  "rules": {
    // 容許使用 console
    "no-console": false,
    // 單引號
    "quotemark": false,
    // 設置修飾符順序
    "member-ordering": [
      true,
      {
      "order": [
          "public-static-field",
          "public-static-method",
          "protected-static-field",
          "protected-static-method",
          "private-static-field",
          "private-static-method",
          "public-instance-field",
          "protected-instance-field",
          "private-instance-field",
          "public-constructor",
          "protected-constructor",
          "private-constructor",
          "public-instance-method",
          "protected-instance-method",
          "private-instance-method"
        ]
      }
    ],

    // 容許帶一個參數的箭頭函數省去括號
    "arrow-parens": [
      true,
      "ban-single-arg-parens"
    ],

    // 對齊方式
    "align": [
      true,
      "parameters",
      "statements",
      "members",
      "elements"
    ]
  },
  "rulesDirectory": []
}
複製代碼

jsx支持

在vue-cli3 默承認以選擇設置支持jsx了,具體相關的配置在tsconfig.jsonnode

{
    "compilerOptions": {
        "jsx": "preserve"
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
    ]
}
複製代碼

typescript基礎使用

1)數據類型


typescript中數據類型有Boolean,Number,String,Array,Tuple,Enum,Any,Void,Null,Undefined,Never,以下圖: jquery

定義Boolean

let isDone: boolean = falsewebpack

定義String

let color: string = "blue"git

定義Number

let decimal: number = 6;es6

定義Array

有兩種方式:

//第一種:在元素類型後面用中括號([])來表示這種類型元素的數組
 let list: number[] = [1, 2, 3];
 
 //第二種:使用泛型建立類型數組,Array<elemType/ *數組元素的類型* />
 let list: Array<number> = [1, 2, 3];
複製代碼

定義Any

any 類型,則容許被賦值爲任意類型
let notSure: any = 4;

定義Enum(枚舉)

TypeScript增長了一個很實用的Enum類型。好比C#,枚舉給了咱們更友好的名稱(數字類型)來辨別數值集合

enum Color {Red = 1, Green = 2, Blue = 4}
    let c: Color = Color.Green;
複製代碼

定義void

能夠用 void 表示沒有任何返回值的函數, 也能夠聲明一個 void 類型的變量,不過只賦值爲 undefinednull

function warnUser(): void {
    console.log("This is my warning message");
}
複製代碼

定義Null and Undefined

let u: undefined = undefined;
    let n: null = null;
複製代碼

undefined 類型的變量只能被賦值爲undefinednull 類型的變量只能被賦值爲 null

void 的區別是,undefinednull 是全部類型的子類型, undefinednull 類型的變量,能夠賦值給 number 類型的變量,而void則不行

2)函數

函數定義(define)

typescript中函數定義有函數聲明法、直接量式等,和es6差很少,不過就多了參數類型的定義

函數聲明法

function add(x: number, y: number): number {
    return x + y;
}
複製代碼

add函數定義了參數x和y都是number類型,返回值也是number類型,若是傳入參數或返回值不是number類型,就會報錯

直接量式

以下:

var getInfo=function(name:string,age:number):string{
        return `${name} --- ${age}`;
    }
複製代碼

沒有返回值時

使用void 標識

function run():void{

        console.log('run')
    }
複製代碼

可選參數(Optional)

定義可選參數在傳參數時添加就行,傳參時能夠傳也能夠不傳

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob"); 
複製代碼

默認參數(Default)

ts默認參數與es6默認參數定義同樣

function getInfo(name:string,age:number=20):string{
        if(age){

            return `${name} --- ${age}`;
        }else{

            return `${name} ---年齡保密`;
        }
    }
複製代碼

剩餘參數(Rest)

就是使用拓展字符...,接收其他的參數

function sum(a:number,b:number,...result:number[]):number{
    var sum=a+b;
    for(var i=0;i<result.length;i++){
        sum+=result[i];  
    }
    return sum;
}
    alert(sum(1,2,3,4,5,6)) ;
複製代碼

函數重載

es5中重複定義函數會形成替換不一樣,typecript中的重載是爲同一個函數提供多個函數類型定義來達處處理不一樣函數參數的狀況,實現不一樣的邏輯功能

function getInfo(name:string):string;
    function getInfo(name:string,age:number):string;
    function getInfo(name:any,age?:any):any{
        if(age){

            return '我叫:'+name+'個人年齡是'+age;
        }else{

            return '我叫:'+name;
        }
    }
    
    alert(getInfo('zhangsan')) //我叫:zhangsan
複製代碼

3)類(class)

class定義

class定義時對屬性和方法進行類型約束

class Person{
        name:string; //屬性  前面省略了public關鍵詞
        constructor(n:string){  
            //構造器
            this.name=n;
        }
        run():void{
            alert(this.name);
        }
    }
    var p=new Person('張三');
    p.run()
複製代碼

class繼承

繼承和es6的寫法同樣,用extendssuper繼承父類

class Person{
        name:string;
        constructor(name:string){
             this.name=name;
         }
         run():string{
             return `${this.name}在運動`
         }
    }
    class Web extends Person{
        constructor(name:string){
            super(name);  /*初始化父類的構造函數*/
        }
    }
    var w=new Web('李四');
    alert(w.run());
複製代碼

class修飾符

typescript有三種修飾符,publicprotectedprivate,屬性若是不加修飾符 默認就是 公有 (public);以下:

public屬性當前類、外部、子類均可以訪問

class Person{

    public name:string;  /*公有屬性*/

    constructor(name:string){
        this.name=name;
    }

    run():string{

        return `${this.name}在運動`
    }
}

var  p=new Person('哈哈哈');

alert(p.name);
複製代碼

protected:保護類型,在類裏面、子類裏面能夠訪問 ,在類外部無法訪問

class Person{
    protected name:string;  /*保護類型*/
    constructor(name:string){
        this.name=name;
    }
    run():string{
        return `${this.name}在運動`
    }
}
var  p=new Person('哈哈哈');
alert(p.name);
複製代碼

類外部訪問會報錯


private :私有屬性,在類裏面能夠訪問,子類、類外部都無法訪問

class Person{

    private name:string;  /*私有*/

    constructor(name:string){
        this.name=name;
    }

    run():string{

        return `${this.name}在運動`
    }
}


class Web extends Person{

    constructor(name:string){
        super(name)
    }

    work(){

        console.log(`${this.name}在工做`)
    }
}
複製代碼

子類訪問會報錯


抽象類(abstract)

typescript中的抽象類使用abstract定義,它是提供其餘類繼承的基類,不能直接被實例化。抽象類中abstract定義的抽象方法不包含具體實現,但必須在派生類中實現。
首先使用abstract定義抽象類Animal和抽象方法eat

abstract class Animal {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract eat(): any; //抽象方法不包含具體實現而且必須在派生類中實現。
  run() {
    console.log("其餘方法能夠不實現");
  }
}
複製代碼

而後在子類Dog實現具體方法eat

class Dog extends Animal {
  //抽象類的子類必須實現抽象類裏面的抽象方法
  constructor(name: any) {
    super(name);
  }
  eat() {
    console.log(this.name + "吃糧食");
  }
}

var d = new Dog("小花花");
d.eat();
複製代碼

若是沒有定義eat方法,就會報錯;

4)接口(Interfaces)

在 TypeScript 中,咱們可使用接口(Interfaces) 定義來對屬性、函數、類、可索引數組或對象進行規範和限制

屬性接口

屬性接口通常是對json對象類型的約束,定義以下:

interface FullName{
firstName:string;  //注意;結束
readonly secondName:string;
optionalVal?:string;
}
function printName(name:FullName){
// 必須傳入對象  firstName  secondName
console.log(name.firstName+'--'+name.secondName);
}
var obj={   /*傳入的參數必須包含 firstName  secondName*/
age:20,
firstName:'張',
secondName:'三'
};
printName(obj);
複製代碼

上面interface定義了一個接口FullName ,接着定義了一個變量 obj傳入printName中,它的鍵值必須具備 FullName中定義的firstName和secondName,且類型必須相同。這樣,咱們就約束了obj 的數據類型必須和接口 FullName 一致.

其中readonly是隻讀的意思,不能從新賦值,與const的區別在於,const用於定義變量readonly用於定義屬性;

而定義時optionalVal後添加?號,表示這個是可選的屬性,傳入時無關緊要

函數類型接口

函數類型接口定義時對傳入參數和返回值進行約束。

// 加密的函數類型接口
interface encrypt{
(key:string,value:string):string;
}
var md5:encrypt=function(key:string,value:string):string{
    //模擬操做
    return key+value;
}
console.log(md5('name','zhangsan'));
複製代碼

上面定義了encrypt函數接口,規定了傳入參數和返回值都是string類型。

  • 可索引類型接口
    TypeScript 定義數組或對象接口時,索引簽名必須是 string 或者 numbersymbols
//定義數組接口
interface UserArr{
    [index:number]:string
}
var arr:UserArr=['aaa','bbb'];
console.log(arr[0]);

//定義對象接口
interface UserObj{
    [index:string]:string
}
var arr:UserObj={name:'張三'};
複製代碼

class類型接口

定義class類型接口和抽象類差很少,不過是使用implements來實現具體的派生類,implements這個單詞有器具、工具、落實、實現的意思。看看怎 樣implements:

//定義class類型
interface Animal {
name: string;
eat(str: string): void;
}

class Cat implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(food: string) {
console.log(this.name + "吃" + food);
}
}

var c = new Cat("小花");
c.eat("什麼貓砂?");
複製代碼

Animalname屬性和eat方法,Cat實現Animal這個類,一樣必須有nameeat方法

接口擴展

接口的擴展至關於接口的繼承,使用extends進行繼承。

interface Animal{
    eat():void;
}
interface Person extends Animal{
    work():void;
}

class Programmer{
    public name:string;
    constructor(name:string){
        this.name=name;
    }
    coding(code:string){
        console.log(this.name+code)
    }
}
class WebDeveloper extends Programmer implements Person{
    constructor(name:string){
       super(name)
    }
    eat(){
        console.log(this.name+'喜歡吃饅頭')
    }
    work(){
        console.log(this.name+'寫代碼');
    }
}
var wo=new WebDeveloper('小陳');
wo.coding('寫ts代碼');
複製代碼

上面代碼先定義了Animal類接口,而後Person接口擴展了AnimalProgrammer具體進行Person實現,WebDeveloper繼承自Programmer,WebDeveloper中定義的方法eatwork都沒有返回值,與AnimalPerson中定義的類型一致。

5) 泛型

定義函數時,若是傳入參數類型和返回值不肯定時,用any能夠搞定,先看以下代碼:

function getData(value:any):any{
return '哈哈哈';
}
複製代碼

Tadashize!使用any,意味着放棄了類型檢查,傳入的參數類型和返回的參數類型能夠不一致,當咱們函數傳入參數類型以及返回值類型不肯定,但又但願傳入參數類型以及返回值類型是相同的,那怎麼辦?因而泛型就登場了,泛型就是用來解決類、接口、方法的複用性、以及對不特定數據類型的支持的。

定義一個泛型函數

function getData<T>(value:T):T{
   return value;
}
getData<number>(123);

getData<string>('這是一個泛型');
複製代碼

其中T表示泛型,要是喜歡用A或V也行,getData函數調用時,限定了參數類型是numberstring

定義一個泛型類

下面是求最小值的泛型類實現。

class MinClas<T>{
public list:T[]=[];
add(value:T):void{
    this.list.push(value);
}
min():T{        
    var minNum=this.list[0];
    for(var i=0;i<this.list.length;i++){
        if(minNum>this.list[i]){
            minNum=this.list[i];
        }
    }
    return minNum;
}
}
/*實例化類 而且制定了類的T表明的類型是number*/
var m1=new MinClas<number>();   
m1.add(11);
m1.add(3);
m1.add(2);
console.log(m1.min())
複製代碼

定義一個泛型接口

  • firstly,第一種方法能夠先定義接口,在調用使用時再限定類型:
interface ConfigFn{
        <T>(value:T):T;
    }
    var getData:ConfigFn=function<T>(value:T):T{
        return value;
    }
    getData<string>('張三');
複製代碼
  • secondly,第二種是在定義時限定類型,直接調用使用
interface ConfigFn<T>{
        (value:T):T;
    }
    function getData<T>(value:T):T{
        return value;
    }
    var myGetData:ConfigFn<string>=getData;     
    myGetData('20');
複製代碼

ts在vue中使用

水了那麼多,下面開始寫點項目相關。。。

路由配置


首先,新建文件,修改後綴名爲 .ts ,而後完事。

//index.ts
import Vue from "vue";
import Router from "vue-router";
import routes from "./routes";
Vue.use(Router);

export default new Router({
mode: "hash",
base: process.env.BASE_URL,
routes
});


//routes.ts
export default [
{
    path: "/xxxx",
    name: "xxxx",
    component: () => import("@/views/xxxxx.vue"),
    meta: {
        title: ""
    }
}
]
複製代碼

坑點一:路由鉤子不生效

這實際上是vue-class-component的坑點,在組件中使用beforeRouteEnter等路由鉤子函數時,得先在main.ts主入口文件進行註冊

//main.ts
import Component from 'vue-class-component'

// Register the router hooks with their names
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
'beforeRouteUpdate' // for vue-router 2.2+
])
複製代碼

而後在組件引入使用,

//其餘component.vue
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
class MyComp extends Vue {
beforeRouteEnter (to, from, next) {
console.log('beforeRouteEnter')
next()
}

beforeRouteLeave (to, from, next) {
console.log('beforeRouteLeave')
next()
}
}
複製代碼

坑點二:路由沒法跳轉

使用this.$router.push()時,跳轉不生效,得先在shims-vue.d.ts文件中添加定義

import Vue from 'vue';
import VueRouter, {Route} from 'vue-router';

declare module "*.vue" {
export default Vue;
}

declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter; // 這表示this下有這個東西
$route: Route;
}
}
//而後就可使用啦
複製代碼


而後聊點其餘吧,天天水一水,總有一天升10級。。。

history and hash

長話短說,hash模式經過監聽window.onhashchange事件來監測url變化

if (!window.HashChangeEvent)(function(){
  var lastURL = document.URL;
  window.addEventListener("hashchange", function(event){
    Object.defineProperty(event, "oldURL",
    {enumerable:true,configurable:true,value:lastURL});
    Object.defineProperty(event, "newURL",
    {enumerable:true,configurable:true,value:document.URL});
    lastURL = document.URL;
  });
}());
複製代碼

hashchange事件參數有兩個屬性newURL(跳轉後的路徑url)和oldURL (跳轉前的路徑url)


history模式,是調用historypushState() 和 replaceState()修改路徑url

//pushState
let stateObj = {
    foo: "bar",
};

history.pushState(stateObj, "page 2", "bar.html");

//replaceState
history.replaceState(stateObj, "page 3", "bar2.html");
複製代碼

路由懶加載

不就是按需加載嘛......通常有三種,just show the code:

一是使用vue的異步組件

//自動將你的構建代碼切割成多個包
    {
        path: '/demo',
        name: 'Demo',
        component: resolve =>
        require(['../components/Demo'], resolve)
    }
複製代碼

二是webpack提供的require.ensure()


這會把多個路由指定相同的chunkName,會合並打包成一個js文件

{
        path: '/demo',
        name: 'Demo',
        component: resolve => require.ensure([], () =>
        resolve(require('../components/Demo')), 'demo')
    },
    {
        path: '/hihihi',
        name: 'hihihi',
        // component: hihihi
        component: resolve => require.ensure([], () =>
        resolve(require('../components/hihihi')), 'demo')
    }
複製代碼

三是webpack 的import()


沒錯,這種實際上是第二種的升級版,建議使用

{
    path: "/xxxx",
    name: "xxxx",
    component: () => import("@/views/xxxxx.vue"),
    meta: {
        title: ""
    }
}
複製代碼

vue-property-decorator使用

vue-property-decorator 是vue官方推薦的typescript支持庫,依賴於vue-class-component, 在vue中可使用TypeScript裝飾符

如圖,它提供了7個裝飾符和一個函數(Mixins),分別是:

@Emit ;//對應vue中的emit
@Inject  ;//對應vue中的Inject
@Model ;//對應vue中的Model
@Prop  ;//對應vue中的Prop
@Provide  ;//對應vue中的Provide
@Watch  ;//對應vue中的Watch
@Component  ;//對應vue中的component
Mixins  ;//對應vue中的mixins
複製代碼

安裝

npm i vue-class-component vue-property-decorator --save
複製代碼
  • 基礎使用

@Component 使用

首先引入Component

import { Component, Vue } from "vue-property-decorator";
複製代碼

而後註冊組件

@Component({
components: {
your-component
}
})
複製代碼

@Emit使用

//引入
import { Vue, Component, Emit } from 'vue-property-decorator'

//使用
@Component
export default class YourComponent extends Vue {
count = 0

@Emit()
addToCount(n: number) {
this.count += n
}

@Emit('reset')
resetCount() {
this.count = 0
}

@Emit()
returnValue() {
return 10
}

@Emit()
promise() {
return new Promise(resolve => {
  setTimeout(() => {
    resolve(20)
  }, 0)
})
}
}
複製代碼

上面會被對應編譯成

export default {
data() {
return {
  count: 0
}
},
methods: {
addToCount(n) {
  this.count += n
  this.$emit('add-to-count', n)
},
resetCount() {
  this.count = 0
  this.$emit('reset')
},
returnValue() {
  this.$emit('return-value', 10)
},
promise() {
  const promise = new Promise(resolve => {
    setTimeout(() => {
      resolve(20)
    }, 0)
  })

  promise.then(value => {
    this.$emit('promise', value)
  })
}
}
}
複製代碼

@Prop使用

這個也簡單,先引入,而後在組件內,使用@prop修飾符定義prop,內在與原來的prop是同樣的,不過是寫法上的花樣不一樣

//引入
import { Component, Vue, Prop } from
"vue-property-decorator";

//使用
@Component({})
export default class eventVideo extends Vue {
  // 定義字符串類型
  @Prop(String) public positionleft!: string;
  @Prop(String) public positiontop!: string;
  
  //定義Number類型
  @Prop(Number) outerHeight!: number;
  
  //定義數組類型
  @Prop() public colorArr: Array<any>;
}
複製代碼

或者定義時使用interface,能夠這樣寫:

// define interface eventDetailArr
interface eventDetailArr {
  [index: number]: any;
}

@Component({})
export default class eventDetail extends Vue {
  @Prop() eventDetailData!: eventDetailArr[];
}
複製代碼

@Inject與@Provide

    在vue組件傳值的時候,有prop經常使用用於父子組件傳值的形式,有ref等直接操做簡單粗暴的形式,有event-busemit傳值的形式,有vuex狀態管理的形式,對於層級嵌套不是不少的時候,使用propemit挺方便,可是對於太太爺爺輩傳話,一輩一輩傳,混亂且麻煩,又不想用vuex這種傳家寶,那就能夠用injectprovide,既保證基因代代相傳,又使用簡潔便捷

官方文檔表示,provide傳入的是對象或返回對象函數,inject則能夠多樣

inject傳入的值是對象,默認有 fromdefault屬性, from 表示其源屬性,若是你 provide的屬性和 inject的不是同一個名字的話。 default表示默認值。

首先在組件內引入定義,

//太爺爺輩.vue--provide
<script lang="tsx">
import { Component, Inject, Provide, Vue } from
"vue-property-decorator";

@Component({})
export default class 太爺爺 extends Vue {
  //提供基因
  // Provide weather components position
  @Provide("weather__position_left")
  weather__position_left = "57%";
  @Provide("weather__position__top")
  weather__position__top = "40px";
}
</script>

//十八世孫
//weatherInfo.vue inject
<script lang="tsx">
import { Component, Vue, Prop, Inject, Provide } from
"vue-property-decorator";
@Component({
  components: {}
})
export default class weatherinfo extends Vue {
  //遺傳基因
  // Inject weather components position
  @Inject("weather__position_left") readonly
  weather__position_left!: string;
  @Inject("weather__position__top") readonly
  weather__position__top!: string;
  
  async mounted() {
    this.$nextTick(() => {
      console.log(this.weather__position_left,
      this.weather__position__top)
    });
  }
}
</script>
複製代碼

最後舒適提醒:
provideinject在咱們日常的項目並不推薦用哦,想一想頂層組件provide了值,要是子組件多級複用,想改變,就一改全改,形成污染了,其實在基礎組件庫才適合使用,哈哈

@Watch

引入:

import { Component, Vue, Prop, Watch } from "vue-property-decorator";
複製代碼

使用:

上面代碼等同於:

watch: {
        'stationData': [
            {
                handler: 'onStationDataChange',
                immediate: true,
                deep: true
            }
        ]
    },
    methods: {
        onStationDataChange(val, oldVal) { },
    }
複製代碼

handler是處理事件,immediate屬性設置爲true會讓stationData在初始定義時就執行handler方法,deep設置爲true會進行對象深度層級的監聽,給對象全部屬性添加監聽器,一般這樣開銷就會增大了,因此替代方法是直接監聽對象的屬性,而不用設置deep:true.

vuex改造

官方圖樣鎮樓

項目中主要借用了 vuex-class庫,對 vuex進行改造,目錄結構以下:


首先看index.ts

定義和導出vuex

import Vue from "vue";
import Vuex, { Store } from "vuex";
import actions from "./actions";
import mutations from "./mutations";
import state from "./state";
import getters from "./getters";
// modules

Vue.use(Vuex);

const store: Store<any> = new Vuex.Store({
  actions,
  mutations,
  getters,
  state,
  modules: {
    //添加自定義模塊
  }
});

export default store;

複製代碼

定義types


經過定義一個接口RootStateTypes,在RootStateTypes中定義types

//types.ts
export interface RootStateTypes {
  eventItem: object; 
  ......
}
複製代碼

定義state

首先引入RootStateTypes,而後再定義state,state的類型與RootStateTypes定義的類型約束一致

//state.ts
import { RootStateTypes } from "./types";
const state: RootStateTypes = {
  eventItem: {}
};

export default state;

複製代碼

定義getters

//getters.ts
import state from "./state";
import { RootStateTypes } from "./types";
import { GetterTree } from "vuex";

const getters: GetterTree<RootStateTypes, any> = {
  eventItem: (state: RootStateTypes) => state.eventItem
};

export default getters;
複製代碼

定義getters時,同時引入泛型接口GetterTree進行約束,getters中使用的stateRootStateTypes類型保持一致,GetterTreevuex/types/index.d.ts中定義以下:

定義mutations

//mutations.ts
import state from "./state";
import { RootStateTypes } from "./types";
import { MutationTree } from "vuex";

const mutations: MutationTree<RootStateTypes> = {
  CLICK_EVENT(state: RootStateTypes, data: object) {
    state.eventItem = data;
  }
};

export default mutations;
複製代碼

定義mutations時,同時引入泛型接口MutationTree進行約束,mutations中使用的stateRootStateTypes類型保持一致,MutationTreevuex/types/index.d.ts中定義以下:

定義actions

//actions.ts
import state from "./state";
import { RootStateTypes } from "./types";
import { ActionTree } from "vuex";

import { $important } from "@/api/importantData";

const actions: ActionTree<RootStateTypes, any> = {
  // set default event
  async SET_EVENT_ASYN({ commit, state: RootStateTypes }) {
    let eventData = null;
    await $important.getIngEventList().then((accidentIcon: any) => {
      if (accidentIcon.length > 0) {
        eventData = {
          ...accidentIcon[0],
          type: accidentIcon[0].eventtypeName.includes("計劃")
            ? "construction"
            : "accident"
        };
      }
    });
    commit("CLICK_EVENT", eventData);
  }
};

export default actions;

複製代碼

定義actions時,同時引入泛型接口ActionTree進行約束,actions中使用的stateRootStateTypes類型保持一致,ActionTreevuex/types/index.d.ts中定義以下:

引入使用

使用@State,@Getter,@Action,@Mutation分別引入StateGetterActionMutation,就是在前面添加@修飾符就行了

//someComponent.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from 'vuex-class'

export default class eventDetail extends Vue {
      @State eventItem
      @Getter eventItem
      @Action SET_EVENT_ASYN
      @Mutation CLICK_EVENT
    
      created () {
        this.eventItem // -> store.state.eventItem
        this.eventItem // -> store.state.eventItem
        this.SET_EVENT_ASYN({ value: obj }) // -> store.dispatch('SET_EVENT_ASYN', { value: obj })
        this.CLICK_EVENT({ value: true }) // -> store.commit('CLICK_EVENT', { value: obj })
      }
}
複製代碼

TSX在vue中使用


TSX其實就是JSX語法結合typescript一塊兒使用,在vue中使用jsx

render函數的使用


先看代碼:

<script lang="tsx">
import { Component, Vue, Prop, Inject, Provide } from "vue-property-decorator";
@Component({
components: {}
})
export default class weatherinfo extends Vue {
// Inject weather components position
@Inject("weather__position_left") readonly weather__position_left!: string;
@Inject("weather__position__top") readonly weather__position__top!: string;

//weather information
WeatherInfo = null;
//weather refresh interval
weatherTimer = null;

async mounted() {
this.$nextTick(() => {
});
}

render(){
const weatherAttrs = {
  style:`left:${this.weather__position_left};top:${this.weather__position__top}`,
  class:`weather__info__container flex`
}
const weatherNode= this.WeatherInfo?
  (<div {...{attrs:weatherAttrs}}>
      <div class="flex flex-ver weather__info__item flex-between" >
          <span>{this.WeatherInfo.realTime.weather}</span>
          <div>
              <img class="weather__icon"
              src={require('@/assets/images/mapIcon/temperature.png')} />
              <span>{this.WeatherInfo.realTime.temperature}℃</span>
          </div>
      </div>
      <div class="flex flex-ver weather__info__item ">
          <img class="weather__icon"
          src={require('@/assets/images/mapIcon/humidity.png')}/>
          <span>{this.WeatherInfo.realTime.humidity}</span>
      </div>
  </div>):''
return weatherNode
}
}
</script>
複製代碼

使用時,在render函數中返回html內容,綁定數據的語法和vue有點相似,又不一樣

值的綁定

使用{}進行值的綁定

render() {
return  (<span>{this.WeatherInfo.realTime.humidity}</span>)
}
複製代碼

標籤屬性的綁定

標籤屬性的綁定能夠在標籤上直接用{}進行綁定

render() {
return (<img class="weather__icon"
src={require('@/assets/images/mapIcon/temperature.png')} />)
}
複製代碼

也能夠進行定義對象存儲屬性,而後經過...解構展開屬性,

render(){
const weatherAttrs = {
  style:`left:${this.weather__position_left};top:${this.weather__position__top}`,
  class:`weather__info__container flex`
}
return (<div {...{attrs:weatherAttrs}}></div>)
}
複製代碼

這個屬性定義,能夠參考vue官方羅列的屬性

{
class: {
foo: true,
bar: false
},
style: {
color: 'red',
fontSize: '14px'
},
attrs: {
id: 'foo'
},
props: {
myProp: 'bar'
},
domProps: {
innerHTML: 'baz'
},
on: {
click: this.clickHandler
},
nativeOn: {
click: this.nativeClickHandler
},
directives: [
{
  name: 'my-custom-directive',
  value: '2',
  expression: '1 + 1',
  arg: 'foo',
  modifiers: {
    bar: true
  }
}
],
scopedSlots: {
default: props => createElement('span', props.text)
},
key: 'myKey',
ref: 'myRef',
refInFor: true
}
複製代碼

事件的綁定

標籤的事件綁定可使用on-click這種形式,也可使用onCliCK,若是是定義組件的事件,要用nativeOnClick,以下:

render (h) {
return (
<div
  id="foo"
  domPropsInnerHTML="bar"
  onClick={this.clickHandler}
  nativeOnClick={this.nativeClickHandler}
  class={{ foo: true, bar: false }}
  style={{ color: 'red', fontSize: '14px' }}
  key="key"
  ref="ref"
  refInFor
  slot="slot">
</div>
)
}
複製代碼

要傳參數的話,用這種onClick={()=>{this.clickHandler(value)}}形式

return(
<ul class="table__content__item flex flex-between flex-ver" on-click={(e: any) =>
this.$emit('iconclick',element)}>
    <li><img src={element.icon}/></li>
    <li class="flex flex-center">
        <span class="amount">{element.amount}</span>
        <span>{element.title}</span>
    </li>
</ul>
)
複製代碼

條件渲染

v-if這樣的指令,能夠改寫成三元條件判斷就行

const weatherNode=(this.WeatherInfo?(
<div class="flex flex-ver weather__info__item flex-between" >
<div class="flex flex-ver flex-around">
    <img class="weather__icon" src={weatherIcon}/>
    <span>{this.WeatherInfo["realTime"]["temperature"]}℃</span>
</div>
<div class="flex flex-center">
    <span>{this.WeatherInfo["realTime"]["city"]}</span>
    <span>{this.WeatherInfo["realTime"].weather}</span>
</div>
</div>):'')

return weatherNode
複製代碼

循環渲染

v-for這樣的循環渲染能夠經過遍歷數組方法mapforEach或其餘來實現

render(){
const nodeAttrs = {
    style:`left:${this.positionleft};top:${this.positiontop}`,
    class:`list__container`
}

const renderNode=
(<div {...{attrs:nodeAttrs}}>
    {/**組件能夠直接渲染*/}
    <panneltitle headerText="xxx展現"></panneltitle>
    <div class="container flex flex-wrap flex-around">
        {
            Object.values(this.tableData).map(element => {
                return(
                    <ul class="table__content__item flex flex-between flex-ver"
                    on-click={(e: any) =>
                    this.$emit('iconclick',element)}>
                        <li><img src={element.icon}/></li>
                        <li class="flex flex-center">
                            <span class="amount">
                                {element.amount}
                            </span>
                            <span>{element.title}</span>
                        </li>
                    </ul>
                )
            })
        }
    </div>
</div>)
return renderNode
}
複製代碼

vue相關使用總結

eventBus

eventBus在跨組件通訊時比較經常使用,不像用vuex那麼繁瑣,本質是實例化另外一個vue,而後使用$emit$on發佈和監聽事件

//在eventBus.ts中定義
    import Vue from 'vue'
    export default new Vue()
複製代碼

comp.vue發佈:

//引入
    import eventBus from "eventBus.ts"
    
    //使用
    eventBus.$emit("topic",payload)
複製代碼

otherComp.vue訂閱:

//引入
    import eventBus from "eventBus.ts"
    
    //使用
    eventBus.$on("topic",payload=>{
        console.log(payload+"do some 騷操做")
    })
複製代碼

其實這不少人都用過,只是這裏說一個坑點(當時沒意識到),eventBus是全局實例,在組件中使用時,即便組件銷燬了,它依然會監聽,就致使有些冗餘開銷和訂閱,本來只想在當前組件有效的時候才監聽,結果多處接收到信息,這就很惱人了。因此記得在組件銷燬前off解除訂閱。

beforeDestroy(){
        eventBus.$off("topic")
    }
複製代碼

若是以爲每次監聽,又得解除,這樣操做很麻煩,網上找了其餘大神的方法,有興趣能夠看看。

構造組件

vue中能夠經過擴展實例的方法進行高階組件的構建,好比button、pannel等,徹底能夠構建一套本身的基礎組件。

下面看看一個簡單的栗子
首先目錄結構以下:

pannel.tsx中進行組件基本內容定義:

/**scss */
import "./pannel.scss";

/**components */
import {
Component,
Vue,
Prop,
Inject,
Provide,
Watch,
Emit
} from "vue-property-decorator";

@Component({
components: {}
})
export default class pannel extends Vue {
/**prop */
@Prop() pannelStyle!: string;
@Prop() pannelClass!: string;
@Prop() content!: string;
/**property */
/**show or not */
private visible: boolean = false;

/**emit */
/**close pannel */
@Emit("close")
private handleClose(e) {
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
this.visible = false;
}

/**transition enter*/
@Emit("enter")
private afterEnter() {
console.log("enter");
}

/**transition leave*/
@Emit("closed")
private afterLeave() {}

/**mounted */
private async mounted() {
this.$nextTick(() => {});
}

/**render */
private render() {
const nodeAttrs = {
  style: `${this.pannelStyle}`,
  class: `pannel__container ${this.pannelClass}`
};
const renderNode = this.visible ? (
  <transition name="fade" on-after-leave={this.afterLeave} on-enter={this.afterEnter} > <div {...{ attrs: nodeAttrs }}> <a class="close__btn" on-click={this.handleClose}> × </a> {this.content} </div> </transition>
) : (
  ""
);
return renderNode;
}
}
複製代碼

pannel.scss樣式以下

.pannel__container{
background: #040f20;
opacity: 0.8;
padding: 20px;
width:580px;
height: 320px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-100%,-100%);
z-index: 2;
.close__btn{
    color: #e9e9e9;
    position: absolute;
    top: 10px;
    right: 10px;
    display: block;
    font-size: 25px;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: #d34242;
    font-style: normal;
    line-height: 38px;
    text-align: center;
    text-transform: none;
    text-rendering: auto;
}
}
複製代碼

接着在pannelConstructor.ts使用Vue.extend進行實例擴展

import Vue from "vue";
import pannel from "./pannel";

// 擴展實例構造器
const PannelConstructor = Vue.extend(pannel);

const pannelInstances = [];
let initID = 1;

/**
* remove Pannel instance(移除實例)
* @param {vm} instance Pannel instance
*/
const removeInstance = (instance: any) => {
if (!instance) return;
let length = pannelInstances.length;
let index = pannelInstances.findIndex(
instanceItem => instance.id === instanceItem.id
);
pannelInstances.splice(index, 1);
length = index = null;
};

/**
*實例化組件
* @param options pannel options :Style,Class,slot
*/
const showPannel = (options: any) => {
const { ...rest } = options;
const instance: any = new PannelConstructor({
propsData: {
  ...rest
}
});

const id = `pannel__${initID++}`;
instance.id = id;
instance.vm = instance.$mount();
document.body.appendChild(instance.vm.$el);
instance.vm.visible = true;

pannelInstances.push(instance);
instance.vm.$on("closed", () => {
removeInstance(instance);
document.body.removeChild(instance.vm.$el);
instance.vm.$destroy();
});
instance.vm.$on("close", () => {
instance.vm.visible = false;
});
return instance.vm;
};

export default showPannel;

複製代碼

index.ts中註冊組件與掛載到Vue.prototype原型上

//@ts-ignore
import pannel from "./pannel";
import showPannel from "./pannelConstructor";

export default (Vue: any) => {
Vue.component(pannel.name, pannel);
Vue.prototype.$showPannel = showPannel;
};

複製代碼

main.ts中引入,以插件形式全局註冊

import Pannel from "@/common/pannel";
Vue.use(Pannel);
複製代碼

而後就能夠在組件中直接this調用或經過標籤使用

this.specialPannel = this.$showPannel({
  pannelStyle:
    "width: 960px;height: 540px;transform: translate(-100%,-50%);"
});
複製代碼

總結

就這樣!!這裏對typescript的一些基礎使用、vue使用的一些注意點進行了簡單總結介紹,嘗試了二者的磨合,整體而言,還算順利,不過對typescript的使用仍是比較淺顯,在摸索的過程當中看了下ant-design等開源庫的源碼,發現我的對typescript的使用簡直是小巫見大巫(這個詞用得對不對??),還有很長一段路要努力哎,六月份據說vue3.0要來了,聽說會提供更好的typescript支持,仍是比較期待ing。。。最後感謝一塊兒敲代碼成長的朋友們,感謝衆多大神們的技術分享啦,在掘金水了那麼久,那些分享帖子着實在開發過程幫了很大忙啊!!!!

相關文章
相關標籤/搜索