vue?props使用typescript自定義類型的方法實例
前言
Base: vue@3.2.33 + typescript@4.1.6 + npm@8.5.0
嘗試解決將ts中自定義的interface/type,傳vue的props屬性的問題。
記錄一下過程和思路。
一、問題定位
官方文檔中說,props自定義類型檢查是可能的。
In addition, type can also be a custom class or constructor function and the assertion will be made with an instanceof check. For example, given the following class:
https://vuejs.org/guide/components/props.html#boolean-casting
但注意到,文檔給出的支持類型為class或者constructor function。
官方給的example也是基于class的
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
export default {
props: {
author: Person
}
}
但如果使用ts中的interface,就會報錯'Person' only refers to a type, but is being used as a value here.
見下面代碼:
interface Person {
firstName: string
lastName: string
}
//錯誤用法
export default {
props: {
author: Person
}
}
歸根結底是vue源碼里的定義方式
declare interface PropOptions<T = any, D = T> {
type?: PropType<T> | true | null;
required?: boolean;
default?: D | DefaultFactory<D> | null | undefined | object;
validator?(value: unknown): boolean;
}
export declare type PropType<T> = PropConstructor<T> | PropConstructor<T>[];
declare type PropConstructor<T = any> = {
new (...args: any[]): T & {};
} | {
(): T;
} | PropMethod<T>;
declare type PropMethod<T, TConstructor = any> = [T] extends [
((...args: any) => any) | undefined
] ? {
new (): TConstructor;
(): T;
readonly prototype: TConstructor;
} : never;
對type屬性的檢查是PropType<T>
一路查找定義到PropConstructor<T>
可以看到PropConstructor的定義
{ new ( ..args: any[]): T & {} },class類型。擁有“接受任何參數并返回指定類型的構造函數”的一個class。{ (): T }函數類型,一個無參傳入并返回指定類型的函數。PropMethod<T>類型。
其中, PropMethod<T> 類型使用了extends條件類型,用于包裹T為任意函數類型or undefined的情況,此時會需要一個含有構造函數的函數類型,這個函數類型的實例在調用時會返回T。
總結一下,PropConstructor<T>的要求無非就是
要么你是class,構造函數返回T,
要么傳入一個無參函數返回T。
要么把這個無參函數包裹在一個有構造函數的class里(用PropMethod泛型)
二、初級解決方案
上面部分我們已經定位到了問題。
我們本質上是要通過PropType的校驗。
解法一,函數法
參照PropConstructor的要求寫就可以了。
用一個 ()=>T 類型的函數為type賦值,通過校驗。
interface Person {
firstName: string
lastName: string
}
const PersonTypeHelper = function (): Person {
return {} as Person
}
export default {
props: {
author: {
type: PersonTypeHelper
}
}
}
上面這個思路可以繼續(xù)優(yōu)化
//更簡單一點不寫實現
declare var PersonType : ()=> Person
export default {
props: {
author: {
type: PersonType
}
}
}
這樣仍然很麻煩,不能為每個自定義interface 都寫一個func / var吧。
我們可以繼續(xù)優(yōu)化,寫一個更通用的var。
declare var commonType: ()=> any
export default {
props: {
author: {
//使用時把Person修改成其他自定義類型即可
type: commonType as ()=> Person
}
}
}這樣寫能過ide的類型推導,但是run起來會報錯
ReferenceError: PersonType is not defined
當然這并不重要。因為就算寫了實現也不會通過類型校驗。
我會在下個章節(jié)解決類型校驗問題,此處先展示思路。
解法二 PropType泛型
上面這個思路,需要每次聲明一個commonType變量。
對某些庫函數黨來說,可能不太舒服。
既然思路是繞過PropType的校驗,直接使用PropType不是最直觀的嗎?
interface Person {
firstName: string
lastName: string
}
import type { PropType } from "vue"
export default {
props: {
author: {
type: Object as PropType<Person>
// type: {} as PropType<Person>
}
}
}
這樣做其實跟使用commonType沒啥本質區(qū)別。
因為import type {PropType}也需要一行。
聲明一個commonType也需要一行。
我們其實也可以使用PropMethod泛型,但是由于vue沒有做直接export,日常使用不太方便。
上面兩個解法都能過ide類型推導,但是runtime時有點問題。
三、props的校驗過程
前面的解法,在運行過程中可能會報警告,類型校驗不通過。[Vue warn]: Invalid prop: type check failed for prop "formItems". Expected , got Array
為了解決這個警告。
我們來讀一下校驗類型的源碼。
傳入的value是實際獲得的對象,type是我們在 {type: Person}里傳入的值。
function assertType(value, type) {
let valid;
const expectedType = getType(type);
if (isSimpleType(expectedType)) {
const t = typeof value;
valid = t === expectedType.toLowerCase();
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type;
}
}
else if (expectedType === 'Object') {
valid = isObject(value);
}
else if (expectedType === 'Array') {
valid = isArray(value);
}
else if (expectedType === 'null') {
valid = value === null;
}
else {
valid = value instanceof type;
}
return {
valid,
expectedType
};
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol,BigInt');
// use function string name to check type constructors
// so that it works across vms / iframes.
function getType(ctor) {
const match = ctor && ctor.toString().match(/^\s*function (\w+)/);
return match ? match[1] : ctor === null ? 'null' : '';
}
前面的代碼就是區(qū)分簡單數據類型,對象、數組,判空。
最后一個是,如果type不在上述內置類型中,就使用 value instanceof type 來判斷。
這里的關鍵在于getType函數。
vue的getType函數,假定我們傳入的是一個constructor,
假定我們的constructor寫法是
function ctorName(){ ...}
試圖捕獲這個ctorName。
如果不這么寫, 返回的expectedType名稱就是空字符串。
這解釋了,如果我們僅僅讀了文檔,老老實實地按文檔說的,傳入了一個構造函數,會報錯。
class Person{
constructor (){...}
}
props:{
type: Person.constructor
}
因為傳入 Person.constructor在vue內部會被解析成getType函數匹配為 ‘Function’ 類型。
構造函數沒有名字,默認名字是function Function(){ somecode}
于是發(fā)生類型不匹配
TS2769: No overload matches this call.
The last overload gave the following error.
Type '{ type: Function; }' is not assignable to type 'Prop<unknown, unknown> | null'.
Types of property 'type' are incompatible.
Type 'Function' is not assignable to type 'true | PropType<unknown> | null | undefined'.
Type 'Function' is missing the following properties from type 'PropConstructor<unknown>[]': pop, push, concat, join, and 28 more.
當然這個不重要,重要的是 valid = value instanceof type 這句。
instanceof 關鍵詞的原理是不斷回溯左值的__proto__,直到其找到一個等于 右值.prototype的原型,就返回true。遍歷到根 null就返回false。
function instanceof(L, R) {
// L為左值,R為右值
var R = R.prototype
var L = L.__proto__
while (true) {
if (L === null) {
return false
}
if (L === R) {
return true
}
L = L.__proto__
}
}
在上述例子中,校驗失敗的核心原因在于。
如果我們傳入一個匿名函數 ()=> T 作為type右值。
由于匿名函數是沒有prototype的,
所以傳入任意value instanceof anonymous 會返回false。
如果我們采用PropType<T>來寫也有問題,
{
type: Object as PropType<Person>
}
右值中的類型會變成Object。
于是在instanceof比較的時候 , R.prototype === Object.prototype。
因此傳入任意非空數據都會通過vue的校驗。
因為任意非空數據遍歷 __proto__都會來到Object.prototype。
所以上面兩個解法,其實本質上是,
無論傳入什么都會報警告和無論傳入什么都不報警告的區(qū)別。
四、后話
我試了另外幾種思路,由于interface只在編譯時起作用,本身并不能設置prototype。
所以無論怎么折騰interface / type 的泛型,都不太好解決這個問題。
目前來看的一種折中方式是使用class。
class有prototype。
但是需要嚴格地約定,在傳入端也使用class的constructor構造數據。
這樣才能將原型存進數據里。
但是這種做法對一些小型接口其實并不友好。
例如
export interface Person {
name: string
age: number
}
用class的話就非得改成
export class Person {
name: string
age: number
constructor(n,a) {
this.name = n
this.age = a
}
}
然后在傳入的地方使用構造函數。
import {Person} from "./types"
const data = new Person("aa",123)
這顯然不如我們直接使用對象字面量方便。
const data = {name:"aa", age:123}
這歸根結底是因為,instanceof是基于原型校驗,而非值校驗的。
使用對象字面量并不能通過校驗。
當然了,vue其實是支持我們寫一個自定義的validator()的。
上面的探索只是想嘗試繞過這個步驟。
custom validator相關源碼。
function validateProp(name, value, prop, isAbsent) {
const { type, required, validator } = prop;
// required!
if (required && isAbsent) {
warn('Missing required prop: "' + name + '"');
return;
}
// missing but optional
if (value == null && !prop.required) {
return;
}
// type check
if (type != null && type !== true) {
let isValid = false;
const types = isArray(type) ? type : [type];
const expectedTypes = [];
// value is valid as long as one of the specified types match
for (let i = 0; i < types.length && !isValid; i++) {
const { valid, expectedType } = assertType(value, types[i]);
expectedTypes.push(expectedType || '');
isValid = valid;
}
if (!isValid) {
warn(getInvalidTypeMessage(name, value, expectedTypes));
return;
}
}
// custom validator
if (validator && !validator(value)) {
warn('Invalid prop: custom validator check failed for prop "' + name + '".');
}
}
需要注意的是,vue這里做了double check。
如果有傳入type,先在type做一次校驗。
通過type校驗后,再看有沒有validator,如果有額外做一次。
這提示我們,
對于復雜的自定義數據類型,
type本身不能成為校驗工具,最好不寫,減少一次運算。
export default {
props: {
author: {
validator: function (value: Person) {
return typeof value.name === "string" && typeof value.age === "number"
}
}
}
}
參考
- //https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
- //https://frontendsociety.com/using-a-typescript-interfaces-and-types-as-a-prop-type-in-vuejs-508ab3f83480
- //https://blog.csdn.net/qq_34998786/article/details/120300361
總結
到此這篇關于vue props使用typescript自定義類型的文章就介紹到這了,更多相關vue props使用ts自定義類型內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue?Router解決多路由復用同一組件頁面不刷新問題(場景分析)
這篇文章主要介紹了Vue?Router解決多路由復用同一組件頁面不刷新問題,多路由復用同一組件的場景分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08
Vue預渲染:prerender-spa-plugin生成靜態(tài)HTML與vue-meta-info更新meta
Vue.js中,prerender-spa-plugin和vue-meta-info插件的結合使用,提供了解決SEO問題的方案,prerender-spa-plugin通過預渲染技術生成靜態(tài)HTML,而vue-meta-info則能動態(tài)管理頁面元數據,本文將探討如何使用這兩個工具優(yōu)化Vue.js項目的SEO表現,包括安裝、配置及注意事項2024-10-10
Vue?vant-ui使用van-uploader實現頭像上傳功能
這篇文章主要介紹了Vue?vant-ui使用van-uploader實現頭像圖片上傳,項目中是使用有贊vant-ui框架實現的頭像上傳替換功能,用到了封裝的圖片壓縮封裝之后再去上傳圖片this.$imgUpload.imgZip(),本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2022-05-05

