2021年9月23日 星期四

vite vue3 ts踩過的坑

記錄一下踩過的坑

在src/interface/index.ts寫了如下

export interface iSelf {
id: number
phone: string
role: number
email?: string
name_ch?: string
name_en?: string
gender: number
identity_id?: string
birthday?: string
resident_status: string
}

export interface iUser extends iSelf {
checked: boolean
}


再引用時卻出現

Uncaught SyntaxError: The requested module 'src/interface/index.ts' does not provide an export named 'iUser'

這沒道理啊,一個檔案寫一個interface就正常,寫兩個就抱錯了!

網上高手給出ts3.8的export type特性

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html?fbclid=IwAR1pDqvqiYjNJkzFarak8i29xKAnPCCtK5fy2xgjUUmcS0BW4eRnqaesISo

但使用過後依然沒效

最終找到 https://github.com/vitejs/vite/issues/2117 這理尤大給出的答案,vite只支援ES Module,所以對於typescript的支援需要安裝套件

基本上安裝@rollup/plugin-typescript + tslib,然後到vite.config.ts加上plugin

import typescript from "@rollup/plugin-typescript"

export default defineConfig({
plugins: [vue(), typescript()],
})

然後src/interface/index.ts修改如下

/// <reference path="_user.d.ts" />

src/interface/_user.d.ts內容如下(對於src/interface模組進行擴充)

declare module "src/interface" {
interface iSelf {
id: number
phone: string
role: number
email?: string
}

interface iUser extends iSelf {
checked: boolean
}
}

使用方式如下(注意是import type)

import type { iUser } from "src/interface"
const datas: Array<iUser> = []

這樣運行就正常了

----------------------- 我是分隔線 ------------------------

以下不是常規方法,但總算是我試出來的分法,也記錄一下

如果不想引用套件,那src/interface/index.ts就要額外宣告變數

/// <reference path="_user.d.ts" />

export const iUser = {}
export const iSelf = {}

使用

import { iUser } from "src/interface"

但這樣有時還是會錯誤,iUser有時會被認為是any,所以還是乖乖正規吧

小結

看來一門技術只出來一年,還是有許多坑要採,沒關係要用就用得徹底一點,繼續踩坑吧

----------------------- 我是分隔線 ------------------------

9/5補充

不知為何當天需要安裝@rollup/plugin-typescript, tslib套件,vite原生就可以支援typescript才對,今天測試把套件拿掉結果一樣可以跑,所以就這樣吧~

2021年1月19日 星期二

javascript執行時間

今天來記錄一個簡單好用的功能,但我之前還真的沒用過,可以用來測試執行時間

console.time("hello")

// todo...

console.timeEnd("hello")
// hello: 0.0015 ms

vue自訂指令限制輸入內容

前幾天接到需求需要我限制欄位的輸入,而且還要有清空內容,閃現等特殊效果,首先就放棄了validate這方式,只好自己下去寫個自訂指令,紀錄一下

只能輸入數字,英文,還有底線,之後就可以在input上下v-policy指令,搭配裝飾子,就可以有不同效果啦

Vue.directive("policy", {
      bind(elargsvnode) {
        let handler = (e=> {
          if (/[^0-9a-zA-Z\-_]/.test(e.target.value)) {
            e.target.value = args.modifiers.clear
              ? ""
              : e.target.value.replace(/[^0-9a-zA-Z\-_]/"");
            const emitInput = () =>
              vnode.elm.dispatchEvent(new CustomEvent("input"));
            if (args.modifiers.lazy) {
              setTimeout(emitInput50);
            } else {
              emitInput();
            }
          }
        };
        el.addEventListener("input"handler);
      }
    });

這是只能輸入數字

Vue.directive("number", {
      bind(elargsvnode) {
        let handler = (e=> {
          let regex;
          if (args.modifiers.int) {
            regex = /([0-9]+).*/;
          } else {
            regex = /([0-9]+\.?[0-9]*).*/;
          }
          let replaceValue = e.target.value.replace(regex"$1");
          if (!isNumeric(replaceValue)) {
            replaceValue = "";
          }
          if (e.target.value != replaceValue) {
            e.target.value = args.modifiers.clear ? "" : replaceValue;
            const emitInput = () =>
              vnode.elm.dispatchEvent(new CustomEvent("input"));
            if (args.modifiers.lazy) {
              setTimeout(emitInput50);
            } else {
              emitInput();
            }
          }
        };
        el.addEventListener("input"handler);
      }
    });

雖然需求順利達成,但還是要小小抱怨一下,為什麼做這麼多違反自然定律的事情= =,UX老實說也沒有比較好,又會增加許多工程師的困擾,唉~


2021年1月13日 星期三

vue-router也可以下錨點

小編之前在想對於單頁式的形象頁面,難道現在強大的響應式前端框架就無用武之力了嗎?那到不一定,今天來介紹一下vue-router裡面一個不常用,但小編覺得很可以的功能

簡單說就是透過vue-router的selector來指定頁面上的tag,透過behavior來形容行徑的過程,預設是直接跳過去,小編這邊用smooth平滑過去

// js
export default new Router({
  scrollBehavior: function (tofromsavedPosition) {
    if (to.hash) {
      return {
        selector: to.hash,
        behavior: "smooth"
      };
    } else {
      return { x: 0y: 0 };
    }
  },
  mode: "history",
  routes: []
});

也可以透過RouterLink直接跳(滑)到指定錨點

// template
<router-link :to="{ name: 'Homepage', hash: '#enter' }">Enter</router-link>


vue component的強制重新render

不知道大家有沒有遇過想要重置component的時候呢,不論是在validate後想要消除紅匡,或是在某些特定場合取到資料後,需要重新繪製畫面,最直覺的作法應該是透過if消除畫面後再重新顯示吧,程式碼如下

<template>
<div v-if="show">
hello
<button type="button" @click="rerender()"></button>
</div>
</template>

<script>
export default {
data: () => ({
show: true
}),
methods: {
rerender() {
this.show = false
this.$nextTick(() => {
this.show = true
}))
}
}
}
</script>

但這樣小編總覺得不是這麼直覺跟簡潔!

那有沒有更簡單的方式呢?有的!只要利用key這個屬性讓這個component屬於一個全新的物件就可以了

<template>
<div :key="index">
hello
<button type="button" @click="rerender()"></button>
</div>
</template>

<script>
export default {
data: () => ({
index: 0
}),
methods: {
rerender() {
this.index++
}
}
}
</script>


2021年1月7日 星期四

改變Vue sub component樣式

相信元件帶給大家的好處不言而喻,但使用上總是有一些不方便之處,例如三方的元件樣式我能否調成自己喜歡的?我自己的元件在不同情境下樣式能否不一樣?難道最終都難逃考比爬斯特,重新寫一個的命運嗎?

小編的任務就是盡可能幫各位提早下班,第一個問題,我們可以寫個全域css去改變他

先假設我們的子元件長這樣

// hello.vue
<div class="component-hello">
  hello
</div>
<style scoped>
  .component-hello {
    colorred
  }
</style>

父元件長這樣

// App.vue
<div class="app">
  <hello />
</div>

<style>
  .app .component-hello {
    colorblue
  }
</style>

在先不考慮css分數的情況下,這樣寫原則上就可以覆蓋component裡面的樣式。

雖然一般三方class寫都得不錯,不太會有重複的情況,但css汙染在我心目中跟watch一樣危險,有很多不可控的因素,所謂的不可控,所謂的黑魔法,就是當下可能會覺得很爽,一個禮拜之後就會變成導致你加班的原因,且一個不小心就會演變成泥沼戰(就是比誰宣告比較兇),所以能不要互相汙染、互相傷害就不要去做這種事,能寫sooped就給他用力寫,為這社會帶來一絲和諧吧!

然而你會發現當你加上了scoped,你原本吃的到css一夕之間的突然都吃不到了,原因是因為scoped會為該component的所有tag注入一個唯一的屬性(亂數產生),但卻不會為子component注入該屬性,所以導致子component吃不到上層scoped的css。那這問題怎麼要解呢?

這就是今天寫這篇主題的目的:那就是使用v-deep,用法很簡單如下。

// App.vue
<div class="app">
  <hello />
</div>

<style scoped>
  .app /deep/ .component-hello {
    colorblue
  }
</style>

這樣就可以控制在App.vue下的component-hello顏色才會是藍色,這樣也同時解決了我們上述的第二個問題,同一個元件在不同class下可以有不同樣式就是這樣來的。

大部分語言也可以簡寫成如下,我以stylus為例

<style lang="stylus" scoped>
  .app >>> .component-hello 
    color blue
</style>

scss或sass語言也可以簡寫成如下

<style lang="stylus" scoped>
  .app ::deep .component-hello 
    color blue
</style>

打完收工下班!

Vue component的v-model

承繼上一篇,我們在撰寫component時,除了要接value,如果data改變,同時也要emit出去,所以程式碼就會如下

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    data() {
      this.$emit("input"this.data);
    },
    value: {
      immediate: true,
      handler() {
        this.data = this.value;
      }
    }
  }
};

乍看之下也沒什麼問題,小編也這樣寫了好幾年了,但小編一直對於宣告一個data:""感到不是很舒服,雖然只是個過水變數,但沒有更乾淨的寫法嗎?還有小編其實一直對於watch很感冒,watch用得好可以帶你上天堂,用不好你就好幾天不用下班,甚至忙到你小孩覺得你只是個很有趣的叔叔!所以能不用就不要用,因此這幾天在賺國產車時想到一個妙招,可以使用computed的set阿!!直接超級簡化,我們直接來看程式碼

export default {
  props: ["value"],
  computed: {
    data: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("input"val);
      }
    }
  }
};

是不是簡單很多呢,打完收工下班!

Vue的watch

這篇來介紹vue的watch的進階使用immediate,

使用情境:以前在子元件要接父層的值,並要隨時監聽變化覆寫data時,都會在mounted時覆寫第一次,之後就依watcher即時覆寫,我們來比較一下兩個寫法

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    value() {
      this.data = this.value;
    }
  },
  mounted() {
    this.data = this.value;
  }
};

雖然功能正常,但上面這寫法會造成邏輯分散,閱讀不易,我們來試一下immediate

export default {
  props: ["value"],
  data: () => ({
    data: ""
  }),
  watch: {
    value: {
      immediate: true,
      deep: true// 深層監聽
      handler() {
        this.data = this.value;
      }
    }
  }
};

這樣就可以在渲染之初就把父層丟進來的值寫入啦,是不是又更簡潔了呢

Vue的ngInit

AngularJS是小編踏入前端的第一個語言,雖然目前已經幾乎是時代的眼淚了,所以小編後來轉戰了vue,在渲染效能及編寫習慣上各方面小編都比較喜歡,但有個功能是我踏入vue至今難忘的功能就是ngInit,不知道各位有沒有遇過:一個變數只出現在某個tag中,但我又不想宣告再data裡面,且變數常出現,這時ngInit就可以幫助你少寫很多code。

小編以前都用這方法達成類似功能,但相較之下顯得攏長

<div v-for="a in ['ABC']" :key="a">
  {{ a }}
</div>

我也曾經查過directive:"v-init"、"vue ngInit",等相關資料,但始終找不到有相關功能,但就在今天,在幫一個同事解決效能問題時,無意中看到他寫了:set屬性,經同事一番解釋,沒想到這就是我夢寐以求的功能,廢話不多說我們直接來看怎麼使用

<div :set="a = 'ABC'">
  {{ a }}
</div>

就是這麽簡單,變數a無須註冊在data裡面,就可以在set的tag裡面重複使用,這功能在很多情境都非常好用。


2021/01/08補充

今天再查詢找到了一個挺有趣的解法,因為還牽扯到大小寫,所以我認為不夠直觀,對我來說這並不是最佳解法,但也有紀錄的價值

// js
Vue.directive('init', {
  bind: function(elbindingvnode) {
    // convert kebab-case to camelCase
    let arg = binding.arg.split('-').map((argindex=> {
      return (index > 0) ? arg[0].toUpperCase() + arg.substring(1) : arg;
    }).join('');
    vnode.context[arg] = binding.value;
  }
});

// template 
<div v-init:my-var="'a'">
    {{ myVar }}
</div>