天天看點

Vue+TS 踩坑記錄與方案總結

Vue+TS 踩坑記錄與方案總結

  • ​​前言​​
  • 整個 vue 項目的目錄結構
  • ​​main.ts 中,提示import App from './App.vue'處,找不到 App.vue 這個子產品​​
  • ​​main.ts 中,往 Vue 的原型 prototype 上挂載全局變量​​
  • ​​全局元件注冊​​
  • ​​SFC 單 vue 檔案元件的基本寫法和結構​​
  • ​​computed 計算屬性的寫法​​
  • watch 監聽器的使用
  • ​​同一個 vue 頁面中使用​​
  • ​​父子兩個 vue 頁面傳值後使用 watch 監聽​​
  • ​​Watch 監聽 store 中的資料改變​​
  • ​​vue+ts 中,使用 filter 過濾器​​
  • ​​自定義指令 過濾器【待補充】​​
  • ​​watch 監聽 router 的變化​​
  • ​​main.ts 中注冊路由導航守衛并在元件中使用路由鈎子函數​​
  • ​​父子傳值 - 子元件修改觸發父元件的方法執行​​
  • ​​@Prop 預設參數​​
  • ​​中央總線注冊與使用【待解決】​​
  • ​​vue + ts 中使用 vue-echarts​​
  • ​​vue + ts 中使用 Element-ui​​
  • ​​全局 scss 變量​​
  • ​​alias 别名設定​​
  • ​​請求接口的代理設定​​
  • ​​本地服務域名修改​​
  • vue + ts 在 vscode 中的問題
  • ​​vue-cli 配置了 resolve alias 來聲明的路徑别名,在引用了 ts 後,vscode 會報錯不能識别、子產品查找失敗:​​

前言

vue 和 TypeScript 結合的情況下,很多寫法和我們平時的寫法都不太一樣,這裡總結我項目開發過程中遇到的問題和問題的解決方案有些問題可能還沒解決,歡迎各位大佬給與提點。

另外,使用本文前可以先看​​​vue 官方文檔關于 typescript 的使用講解​​

整個 vue 項目的目錄結構

  • 大體用 vue-cli 建立的項目,結構基本不變。

這裡隻寫我後來為了解決問題改動的地方

main.ts 中,提示​

​import App from './App.vue'​

​處,找不到 App.vue 這個子產品

解決方案: 1、将 shims-vue.d.ts 檔案一分為二。

2、在 shims-vue.d.ts 檔案同級目錄下建立 vue.d.ts(名字不一定叫 vue,如 xxx.d.ts 也可以),然後此檔案包含代碼如下

// vue.d.ts
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}      

3、而原來的 shims-vue.d.ts 代碼修改、新增如下:

// shims-vue.d.ts
import Vue from 'vue'
import VueRouter, { Route } from 'vue-router'
import { Store } from 'vuex'
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter;
$route: Route;
$store: Store<any>;
// 以下是在main.ts中挂載到Vue.prototype上的變量
$api: any;
$mock: any;
$configs: any;
}
}      

main.ts 中,往 Vue 的原型 prototype 上挂載全局變量

1、main.ts 配置

// main.ts
import api from "./api/request";
import mock from "./api/mock";
import configs from "./utils/config";
Vue.prototype.\(api = api;
Vue.prototype.\)mock = mock;
Vue.prototype.$configs = configs;      

2、shims-vue.d.ts 配置

// shims-vue.d.ts 新增如下
declare module 'vue/types/vue' {
  interface Vue {
    // ...
    // 以下是在main.ts中挂載到Vue.prototype上的變量
    $api: any;
    $mock: any;
    $configs: any;
  }
}      

全局元件注冊

注冊

// main.ts
import Page from "@/components/page.vue";
import AllComponent from "@/common/AllComponent.vue";
Vue.component("Page", Page);
Vue.component("all-component", AllComponent);      

使用

寫法一:
<Page />
寫法二:
<all-component />      

SFC 單 vue 檔案元件的基本寫法和結構

一個簡陋的 demo,展示 ts 下的 vue 檔案中,對于相關功能的使用,重點關注``裡的代碼

<template>
  <!-- 結構示例,指令基礎用法同vue -->
  <div class="minos-system-setting" v-if="hideHeader">
    <h3>結構示例</h3>
    <span>{{ selfKey1 }}</span>
    <ul>
      <li :key="item" v-for="item in fatherKey">{{ item }}</li>
    </ul>
    <button @click="addText">追加文字</button>
    <AnotherVue
      :class="['default-class', selfKey1.length > 10 ? 'one' : 'two']"
    />
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from "vue-property-decorator";
import { Route } from "vue-router";
import AnotherVue from "@/components/AnotherVue.vue";
@Component({
// 元件注冊
components: {
AnotherVue
// 'another-vue': AnotherVue
},
// 過濾器
filters: {
filterFn1() {}
},
// 屬性傳遞
props: {
hideHeader: {
type: Boolean,
required: false,
default: false // 預設屬性的預設值
}
}
})
export default class ComponentName extends Vue {
@Prop({
type: Boolean,
required: false,
default: false // 預設屬性的預設值
})
private hideHeader!: boolean | undefined;
@Prop() private fatherKey: string[]; // 其他沒有預設值的傳值
selfKey1: string = "自己的一個變量";
// 生命周期
created() {}
mounted() {}
// 計算屬性
get computedKey() {
return this.selfKey1.length;
}
// 監聽器
@Watch("computedKey")
getcomputedKey(newVal) {
console.log(newVal);
}
// 導航守衛函數
private beforeRouteEnter(to: Route, from: Route, next: () => void): void {
console.log("beforeRouteEnter", to, from, next);
next();
}
// 方法
addText() {
this.selfKey1 += ",追加文字!";
}
}
</script>
<style lang="scss" scoped>
@import "@/assets/styles/demo.scss";
</style>      

computed 計算屬性的寫法

// 計算屬性
get computedKey() {
  return this.selfKey1.length
}      

watch 監聽器的使用

同一個 vue 頁面中使用

import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
@Watch('boxHeight')
getboxHeight(val) { // get+上邊括号裡的名字
// xxx
}      

父子兩個 vue 頁面傳值後使用 watch 監聽

子元件監聽從父元件傳過來的值 1、父元件用屬性傳值【前提是父元件引入子元件、注冊并調用了】

<ziZuJian :oneKey="oneKeyObj" />      

2、子元件要使用的工具引入工作

import { Component, Vue, Prop, Watch } from "vue-property-decorator";      

3、子元件 Prop 接受

export default class ZiZuJian extends Vue {
  @Prop() private oneKey: object
}      

4、子元件 Watch 監聽

@Watch('oneKey')
getoneKey(newVal,oldVal) {
  // 監聽成功後要做
  log(newVal)
  this.myfunction(newVal)
}      

5、父元件(内部)改動值,會被子元件監聽

export default class FuZuJian extends Vue {
  oneKeyObj = {}
  ...
  mounted(){
    $.ajax().then(()=>{
      // 适時情況下改動props傳遞的值,就會被子元件監聽到改變
      oneKeyObj = {
        name : '測試'
      }
      oneKeyObj.age = 18
    })
  }
}      

Watch 監聽 store 中的資料改變

主要思路是計算屬性擷取 state 裡的資料,watch 再監聽計算屬性

import { Component, Vue, Prop, Watch } from 'vue-property-decorator' // 引入Watch
get stateSomeKey() { // 計算屬性
  // 監聽state下的stateSomeKey對象中的keyName屬性,return傳回該值
  return this['$store'].state.stateSomeKey.keyName
}
@Watch('stateSomeKey') // 與上邊計算屬性同名
getstateSomeKey(val) { // get+上邊括号裡的名字
  // 監聽到變化後,執行對應的内容
  this.myFunction()
  ...
}      

其中,第七行,監聽器那裡也可以這麽寫

@Watch('stateSomeKey') // 與上邊計算屬性同名
watchMenuState(val) { // 這裡可以這麼寫:或用watch+上邊括号裡的名字也可以(雖然不太确定為什麼,隻是代碼這麼寫成功了)
  // 下同
  // ...
}      

vue+ts 中,使用 filter 過濾器

定義:(在@Component 裡邊,寫 filters,注意 s 單詞)

<script lang="ts">
  import { Component, Vue, Prop } from "vue-property-decorator";
  @Component({
    filters: {
      filterValue(value) {
        return Number(value).toLocaleString();
      }
      // otherFilterFn(value) { 其他filter示例
      //   return ...
      // }
    },
    components: {}
  })
  export default class Container extends Vue {
    // ...
  }
</script>      

使用:同之前,正常使用:

<span v-if="showSpan">{{showValue | filterValue}}</span>      

自定義指令 過濾器【待補充】

// 待補充      

watch 監聽 router 的變化

1、shims-vue.d.ts 的設定

// shims-vue.d.ts
import Vue from 'vue'
import VueRouter, {Route} from 'vue-router';
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter; // 這表示this下有這個東西
$route: Route;
}
}      

2、main.ts 的設定

// main.ts
import { Component } from "vue-class-component";
Vue.config.productionTip = false;
Component.registerHooks([
  "beforeRouteEnter", //進入路由之前
  "beforeRouteLeave", //離開路由之前
  "beforeRouteUpdate"
]);      

3、需要監聽路由鈎子的 SCF 元件:

<script lang="ts">
  // xxx.vue 的script标簽内
  import { Component, Vue, Prop, Watch } from "vue-property-decorator";
  import { Route, RawLocation } from 'vue-router';
  // # 下邊兩段,看你需要什麼了:
// 1/監聽路由變化
@Watch('$route',{ immediate: true })
private changeRouter(route: Route){
console.log(route)
}
// 2/定義路由鈎子函數
private beforeRouteEnter(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteEnter', to, from, next)
next(); // 沒有next将不會進入路由内部,跟vue文檔用法一緻
}
private beforeRouteUpdate(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteUpdate'); // 暫時不生效,版本問題
next();
}
private beforeRouteLeave(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteLeave');
next();
}
</script>      

監聽路由的第二種寫法 (如果隻是想更新視圖的話可以考慮監聽路由)

@Watch('$route')
routeWatch() {
  this.loadData();
}      

main.ts 中注冊路由導航守衛并在元件中使用路由鈎子函數

基本同上

1、shims-vue.d.ts 的設定

// shims-vue.d.ts
import Vue from 'vue'
import VueRouter, {Route} from 'vue-router';
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter; // 這表示this下有這個東西
$route: Route;
}
}      

2、main.ts 的設定

// main.ts
import { Component } from "vue-class-component";
Component.registerHooks([
  "beforeRouteEnter", //進入路由之前
  "beforeRouteLeave", //離開路由之前
  "beforeRouteUpdate"
]);      

3、需要監聽路由鈎子的 SCF 元件:

<script lang="ts">
  // xxx.vue 的script标簽内
  import { Component, Vue, Prop, Watch } from "vue-property-decorator";
  import { Route, RawLocation } from 'vue-router';
  // # 下邊兩段,看你需要什麼了:
// 1/監聽路由變化
@Watch('$route',{ immediate: true })
private changeRouter(route: Route){
console.log(route)
}
// 2/定義路由鈎子函數
private beforeRouteEnter(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteEnter', to, from, next)
next(); // 沒有next将不會進入路由内部,跟vue文檔用法一緻
}
private beforeRouteUpdate(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteUpdate'); // 暫時不生效,版本問題
next();
}
private beforeRouteLeave(to: Route, from: Route, next: () => void): void {
console.log('beforeRouteLeave');
next();
}
</script>      

父子傳值 - 子元件修改觸發父元件的方法執行

父元件内部:

1、調用子元件、并綁定傳值:

<ZiZuJian @chuanDiGuoQu="FuQinZiJiYong"></ZiZuJian>      

準備好一會會被子元件觸發的函數:

FuQinZiJiYong(){
  console.log('我是父親内部待被觸發的方法')
}      

子元件 ZiZuJian 内部在需要觸發的地方執行$emit

export default class Menu extends Vue {
  // 在需要觸發的地方,執行如下代碼
  this.$emit('chuanDiGuoQu', '')
}      

最後還有另一種網友總結很麻煩的寫法:參見位址

@Prop 預設參數

第一種:github 找到的 demo 這樣。如下代碼中​

​hideHeader​

​就是由預設參數的父元件傳過來的屬性

export default class ComponentName extends Vue {
  @Prop({
    type: Boolean,
    required: false,
    default: false // 預設屬性的預設值
  })
  private hideHeader!: boolean | undefined
}      

第二種:vue 原生的寫法,并寫到了@component 構造器中就好了: 如果不傳值此函數預設就是 true,傳 false 就是 false 了。并且能嚴格判斷隻能傳 Boolean 類型。挺好。

@Component({
  props: {
    hideHeader: {
      type: Boolean,
      required: false,
      default: false // 預設屬性的預設值
    }
  }
})      

中央總線注冊與使用【待解決】

// 待解決      

vue + ts 中使用 vue-echarts

安裝

npm i -S vue-echarts echarts      

main.ts 中引入并注冊

// main.ts
// 引用
import ECharts from "vue-echarts";
// 用到的子產品要單獨引用
import "echarts/lib/chart/line"; // 線圖為例,其他圖一樣
import "echarts/lib/component/title.js"; // 标題
import "echarts/lib/component/legend"; // 圖例
import "echarts/lib/component/tooltip"; // 提示框
import "echarts/lib/component/toolbox"; // 工具(如下載下傳功能與按鈕)
// 注冊
Vue.component("v-chart", ECharts);      

vue.config.js 中設定

// vue.config.js
module.exports = {
  // For Vue CLI 3+, add vue-echarts and resize-detector into transpileDependencies in vue.config.js like this:
  transpileDependencies: ["vue-echarts", "resize-detector"]
};      

tsconfig.json 中也要設定

// tsconfig.json
{
  "compilerOptions": {
    "types": ["webpack-env", "echarts"]
  }
}      

SFC 應用

<v-chart :options="echartsOptions" id="myCharts" ref="myCharts" />      

vue + ts 中使用 Element-ui

// main.ts
import ElementUI from "element-ui";
Vue.use(ElementUI);      

全局 scss 變量

在 assets/styles 下建立_variable.scss 檔案,用于存放 scss 變量。

然後再 vue.config.js 中設定全局變量

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      sass: {
        prependData: `
          @import "@/assets/styles/_variable.scss";
        `
      }
    }
  }
};      

alias 别名設定

同時解決問題​

​alias 配置的路徑别名,在 vscode 中報錯子產品查找失敗​

​​和問題​

​vue-cli 配置了 resolve alias 來聲明的路徑别名,在引用了 ts 後,vscode 會報錯不能識别、子產品查找失敗​

​。其中,vscode 報錯在 win 環境還需要一個插件安裝,解決方案見下邊 vue.config.js 配置

// vue.config.js
module.exports = {
  chainWebpack: config => {
    // 别名配置
    config.resolve.alias
      .set("comp", resolve("src/components"))
      .set("css", resolve("src/assets/styles"));
    // ...同上,路徑核對好就行
  }
};      

jsconfig.json 配置。注意這裡的名字要和上邊 set 後邊的名字保持一緻

// jsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": [
        "src/*" // 這個本來就有
      ],
      // 後邊追加
      "comp/*": [
        "src/components/*"
      ],
      "css/*": [
        "src/assets/styles/*"
      ],
      // ... 同上,路徑核對好就行
    },
  }
};      

SCF 使用設定的别名

// main.ts
import MyError from "view/error/Error.vue";
/* SCF單頁中scss路徑引用 */
@import "css/_variable.scss";      

請求接口的代理設定

vue.config.js 配置

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      "/api": {
        target: "http://11.11.11.111/", // 示例ip位址,也可以填域名,需要的是後端接口位址的相同部分
        changeOrigin: true,
        pathRewrite: {
          "^/api": ""
        }
      }
    }
  }
};      

axios 請求位址時的寫法:

注意​​

​/api​

​一定要有,且在路徑的最前邊,代替相同的路徑。

axios
  .get("/api/wo/de/di/zhi") // 前邊的'/api'一定要有,它代表的就是vue.config.js中proxy.target的路徑
  .then(() => {
    // 接口成功...
  });      

本地服務域名修改

vue.config.js 配置

// vue.config.js
module.exports = {
  devServer: {
    disableHostCheck: true, // 用域名代替localhost,禁用主機檢查
    host: "www.haha.com"
    // 另外端口也可以在這裡改,隻不過我寫到了package.json總,見下邊
  }
};      

package.json dev 指令的配置

{
  "scripts": {
    "dev": "npm run serve",
    "serve": "vue-cli-service serve --port 80 --open", # 端口設定為80,--open運作完畢後自動打開位址
  }
}      

本地 host 配置

127.0.0.1 www.haha.com # 這裡注意和vue.config.js中的host的值對應      

此時,​

​npm run dev​

​​成功後,浏覽器跑項目輸入位址​

​http://www.haha.com​

​即可

vue + ts 在 vscode 中的問題

vue-cli 配置了 resolve alias 來聲明的路徑别名,在引用了 ts 後,vscode 會報錯不能識别、子產品查找失敗:

1、擴充商店安裝插件 - Path Intellisense

2、配置代碼(vscode setting.json 中設定)

"path-intellisense.mappings": {
  "@": "\${workspaceRoot}/src"
}      
// jsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"]
    }
  }
}      

繼續閱讀