前言
我個(gè)人對(duì)更嚴(yán)格類型限制沒有積極的看法,畢竟各類轉(zhuǎn)類型的騷寫法寫習(xí)慣了。
然鵝最近的一個(gè)項(xiàng)目中,是 TypeScript
+ Vue
,毛計(jì)喇,學(xué)之...…真香!
注意此篇標(biāo)題的“前”,本文旨在講Ts混入框架的使用,不講Class API
1. 使用官方腳手架構(gòu)建
npm install -g @vue/cli# ORyarn global add @vue/cli
新的 Vue CLI
工具允許開發(fā)者 使用 TypeScript
集成環(huán)境 創(chuàng)建新項(xiàng)目。
只需運(yùn)行 vue create my-app
。
然后,命令行會(huì)要求選擇預(yù)設(shè)。使用箭頭鍵選擇 Manually select features
。
接下來,只需確保選擇了 TypeScript
和 Babel
選項(xiàng),如下圖:
完成此操作后,它會(huì)詢問你是否要使用 class-style component syntax
。
然后配置其余設(shè)置,使其看起來如下圖所示。
Vue CLI工具現(xiàn)在將安裝所有依賴項(xiàng)并設(shè)置項(xiàng)目。
接下來就跑項(xiàng)目喇。
總之,先跑起來再說。
2. 項(xiàng)目目錄解析
通過 tree
指令查看目錄結(jié)構(gòu)后可發(fā)現(xiàn)其結(jié)構(gòu)和正常構(gòu)建的大有不同。
這里主要關(guān)注 shims-tsx.d.ts
和 shims-vue.d.ts
兩個(gè)文件
兩句話概括:
shims-tsx.d.ts
,允許你以 .tsx
結(jié)尾的文件,在 Vue
項(xiàng)目中編寫 jsx
代碼shims-vue.d.ts
主要用于 TypeScript
識(shí)別 .vue
文件, Ts
默認(rèn)并不支持導(dǎo)入 vue
文件,這個(gè)文件告訴 ts
導(dǎo)入 .vue
文件都按 VueConstructor<Vue>
處理。此時(shí)我們打開親切的 src/components/HelloWorld.vue
,將會(huì)發(fā)現(xiàn)寫法已大有不同
<template> <div class="hello"> <h1>{{ msg }}</h1> <!-- 省略 --> </div></template><script lang="ts">import { Component, Prop, Vue } from 'vue-property-decorator';@Componentexport default class HelloWorld extends Vue { @Prop() private msg!: string;}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped></style>
至此,準(zhǔn)備開啟新的篇章 TypeScript
極速入門 和 vue-property-decorator
3. TypeScript 極速入門
3.1 基本類型和擴(kuò)展類型
Typescript
與 Javascript
共享相同的基本類型,但有一些額外的類型。
Tuple
enum
Any
與 Void
1. 基本類型合集
// 數(shù)字,二、八、十六進(jìn)制都支持let decLiteral: number = 6;let hexLiteral: number = 0xf00d;// 字符串,單雙引都行l(wèi)et name: string = "bob";let sentence: string = `Hello, my name is ${ name }.// 數(shù)組,第二種方式是使用數(shù)組泛型,Array<元素類型>:let list: number[] = [1, 2, 3];let list: Array<number> = [1, 2, 3];let u: undefined = undefined;let n: null = null;
2. 特殊類型
1. 元組 Tuple
想象 元組 作為有組織的數(shù)組,你需要以正確的順序預(yù)定義數(shù)據(jù)類型。
const messyArray = [' something', 2, true, undefined, null];const tuple: [number, string, string] = [24, "Indrek" , "Lasn"]
如果不遵循 為元組 預(yù)設(shè)排序的索引規(guī)則,那么 Typescript
會(huì)警告。
( tuple
第一項(xiàng)應(yīng)為 number
類型)
2. 枚舉 enum
*
enum
類型是對(duì)JavaScript標(biāo)準(zhǔn)數(shù)據(jù)類型的一個(gè)補(bǔ)充。 像C#等其它語言一樣,使用枚舉類型可以為一組數(shù)值賦予友好的名字。
// 默認(rèn)情況從0開始為元素編號(hào),也可手動(dòng)為1開始enum Color {Red = 1, Green = 2, Blue = 4}let c: Color = Color.Green;let colorName: string = Color[2];console.log(colorName); // 輸出'Green'因?yàn)樯厦娲a里它的值是2
另一個(gè)很好的例子是使用枚舉來存儲(chǔ)應(yīng)用程序狀態(tài)。
3. Void
在 Typescript
中, 你必須在函數(shù)中定義返回類型 。像這樣:
若沒有返回值,則會(huì)報(bào)錯(cuò):
我們可以將其返回值定義為 void
:
此時(shí)將無法 return
4. Any
Emmm...就是什么類型都行,當(dāng)你無法確認(rèn)在處理什么類型時(shí)可以用這個(gè)。
但要慎重使用,用多了就失去使用Ts的意義。
let person: any = "前端勸退師"person = 25person = true
主要應(yīng)用場(chǎng)景有:
5. Never
用很粗淺的話來描述就是:" Never
是你永遠(yuǎn)得不到的爸爸。"
具體的行為是:
throw new Error(message)return error("Something failed")while (true) {} // 存在無法達(dá)到的終點(diǎn)
3. 類型斷言
簡(jiǎn)略的定義是:可以用來手動(dòng)指定一個(gè)值的類型。
有兩種寫法,尖括號(hào)和 as
:
let someValue: any = "this is a string";let strLength: number = (<string>someValue).length;let strLength: number = (someValue as string).length;
使用例子有:
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法:
function getLength(something: string | number): number { return something.length;}// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.
如果你訪問長(zhǎng)度將會(huì)報(bào)錯(cuò),而有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問其中一個(gè)類型的屬性或方法,此時(shí)需要斷言才不會(huì)報(bào)錯(cuò):
function getLength(something: string | number): number { if ((<string>something).length) { return (<string>something).length; } else { return something.toString().length; }}
3.2 泛型: Generics
軟件工程的一個(gè)主要部分就是構(gòu)建組件,構(gòu)建的組件不僅需要具有明確的定義和統(tǒng)一的接口,同時(shí)也需要組件可復(fù)用。支持現(xiàn)有的數(shù)據(jù)類型和將來添加的數(shù)據(jù)類型的組件為大型軟件系統(tǒng)的開發(fā)過程提供很好的靈活性。
在 C#
和 Java
中,可以使用"泛型"來創(chuàng)建可復(fù)用的組件,并且組件可支持多種數(shù)據(jù)類型。這樣便可以讓用戶根據(jù)自己的數(shù)據(jù)類型來使用組件。
1. 泛型方法
在TypeScript里, 聲明泛型方法 有以下兩種方式:
function gen_func1<T>(arg: T): T { return arg;}// 或者let gen_func2: <T>(arg: T) => T = function (arg) { return arg;}
調(diào)用方式也有兩種:
gen_func1<string>('Hello world');gen_func2('Hello world'); // 第二種調(diào)用方式可省略類型參數(shù),因?yàn)榫幾g器會(huì)根據(jù)傳入?yún)?shù)來自動(dòng)識(shí)別對(duì)應(yīng)的類型。
2. 泛型與 Any
Ts
的特殊類型 Any
在具體使用時(shí),可以代替任意類型,咋一看兩者好像沒啥區(qū)別,其實(shí)不然:
// 方法一:帶有any參數(shù)的方法function any_func(arg: any): any { console.log(arg.length); return arg;}// 方法二:Array泛型方法function array_func<T>(arg: Array<T>): Array<T> { console.log(arg.length); return arg;}
方法一,打印了 arg
參數(shù)的 length
屬性。因?yàn)?any
可以代替任意類型,所以該方法在傳入?yún)?shù)不是數(shù)組或者帶有 length
屬性對(duì)象時(shí),會(huì)拋出異常。
方法二,定義了參數(shù)類型是 Array
的泛型類型,肯定會(huì)有 length
屬性,所以不會(huì)拋出異常。
3. 泛型類型
泛型接口:
interface Generics_interface<T> { (arg: T): T;} function func_demo<T>(arg: T): T { return arg;}let func1: Generics_interface<number> = func_demo;func1(123); // 正確類型的實(shí)際參數(shù)func1('123'); // 錯(cuò)誤類型的實(shí)際參數(shù)
3.3 自定義類型: Interface
vs Type alias
Interface
,國內(nèi)翻譯成接口。
Type alias
,類型別名。
以下內(nèi)容來自:
Typescript 中的 interface 和 type 到底有什么區(qū)別
1. 相同點(diǎn) 都可以用來描述一個(gè)對(duì)象或函數(shù):
interface User { name: string age: number}type User = { name: string age: number};interface SetUser { (name: string, age: number): void;}type SetUser = (name: string, age: number): void;
都允許拓展(extends):
interface
和 type
都可以拓展,并且兩者并不是相互獨(dú)立的,也就是說 interface
可以 extends type
, type
也可以 extends interface
。 雖然效果差不多,但是兩者語法不同 。
interface extends interface
interface Name { name: string; }interface User extends Name { age: number; }
type extends type
type Name = { name: string; }type User = Name & { age: number };
interface extends type
type Name = { name: string; }interface User extends Name { age: number; }
type extends interface
interface Name { name: string; }type User = Name & { age: number; }
2. 不同點(diǎn)
type
可以而 interface
不行
type
可以聲明基本類型別名,聯(lián)合類型,元組等類型
// 基本類型別名type Name = string// 聯(lián)合類型interface Dog { wong();}interface Cat { miao();}type Pet = Dog | Cat// 具體定義數(shù)組每個(gè)位置的類型type PetList = [Dog, Pet]
type
語句中還可以使用 typeof
獲取實(shí)例的 類型進(jìn)行賦值
// 當(dāng)你想獲取一個(gè)變量的類型時(shí),使用 typeoflet div = document.createElement('div');type B = typeof div
其他騷操作
type StringOrNumber = string | number; type Text = string | { text: string }; type NameLookup = Dictionary<string, Person>; type Callback<T> = (data: T) => void; type Pair<T> = [T, T]; type Coordinates = Pair<number>; type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
interface
可以而 type
不行
interface
能夠聲明合并
interface User { name: string age: number}interface User { sex: string}/*User 接口為 { name: string age: number sex: string }*/
interface
有可選屬性和只讀屬性
可選屬性
接口里的屬性不全都是必需的。 有些是只在某些條件下存在,或者根本不存在。 例如給函數(shù)傳入的參數(shù)對(duì)象中只有部分屬性賦值了。帶有可選屬性的接口與普通的接口定義差不多,只是在可選屬性名字定義的后面加一個(gè) ?
符號(hào)。如下所示
interface Person { name: string; age?: number; gender?: number;}
只讀屬性
顧名思義就是這個(gè)屬性是不可寫的,對(duì)象屬性只能在對(duì)象剛剛創(chuàng)建的時(shí)候修改其值。 你可以在屬性名前用 readonly
來指定只讀屬性,如下所示:
interface User { readonly loginName: string; password: string;}
上面的例子說明,當(dāng)完成User對(duì)象的初始化后loginName就不可以修改了。
3.4 實(shí)現(xiàn)與繼承: implements
vs extends
extends
很明顯就是ES6里面的類繼承,那么 implement
又是做什么的呢?它和 extends
有什么不同?
implement
,實(shí)現(xiàn)。與C#或Java里接口的基本作用一樣, TypeScript
也能夠用它來明確的強(qiáng)制一個(gè)類去符合某種契約
implement基本用法:
interface IDeveloper { name: string; age?: number;}// OKclass dev implements IDeveloper { name = 'Alex'; age = 20;}// OKclass dev2 implements IDeveloper { name = 'Alex';}// Errorclass dev3 implements IDeveloper { name = 'Alex'; age = '9';}
而 extends
是繼承父類,兩者其實(shí)可以混著用:
class A extends B implements C,D,E
搭配 interface
和 type
的用法有:
3.5 聲明文件與命名空間: declare
和 namespace
前面我們講到Vue項(xiàng)目中的 shims-tsx.d.ts
和 shims-vue.d.ts
,其初始內(nèi)容是這樣的:
// shims-tsx.d.tsimport Vue, { VNode } from 'vue';declare global { namespace JSX { // tslint:disable no-empty-interface interface Element extends VNode {} // tslint:disable no-empty-interface interface ElementClass extends Vue {} interface IntrinsicElements { [elem: string]: any; } }}// shims-vue.d.tsdeclare module '*.vue' { import Vue from 'vue'; export default Vue;}
declare
:當(dāng)使用第三方庫時(shí),我們需要引用它的聲明文件,才能獲得對(duì)應(yīng)的代碼補(bǔ)全、接口提示等功能。
這里列舉出幾個(gè)常用的:
namespace
:“內(nèi)部模塊”現(xiàn)在稱做“命名空間”
module X {
相當(dāng)于現(xiàn)在推薦的寫法 namespace X {
)
跟其他 JS 庫協(xié)同
類似模塊,同樣也可以通過為其他 JS 庫使用了命名空間的庫創(chuàng)建 .d.ts
文件的聲明文件,如為 D3
JS 庫,可以創(chuàng)建這樣的聲明文件:
declare namespace D3{ export interface Selectors { ... }}declare var d3: D3.Base;
所以上述兩個(gè)文件:
shims-tsx.d.ts
, 在全局變量 global
中批量命名了數(shù)個(gè)內(nèi)部模塊。 shims-vue.d.ts
,意思是告訴 TypeScript
*.vue
后綴的文件可以交給 vue
模塊來處理。3.6 訪問修飾符: private
、 public
、 protected
其實(shí)很好理解:
public
private
時(shí),它就不能在聲明它的類的外部訪問,比如:class Animal { private name: string; constructor(theName: string) { this.name = theName; }}let a = new Animal('Cat').name; //錯(cuò)誤,‘name'是私有的
protected
和 private
類似,但是, protected
成員在派生類中可以訪問
class Animal { protected name: string; constructor(theName: string) { this.name = theName; }}class Rhino extends Animal { constructor() { super('Rhino'); } getName() { console.log(this.name) //此處的name就是Animal類中的name }}
3.7 可選參數(shù) ( ?: )和非空斷言操作符(!.)
可選參數(shù)
function buildName(firstName: string, lastName?: string) { return firstName + ' ' + lastName}// 錯(cuò)誤演示buildName("firstName", "lastName", "lastName")// 正確演示buildName("firstName")// 正確演示buildName("firstName", "lastName")
非空斷言操作符:
能確定變量值一定不為空時(shí)使用。
與可選參數(shù) 不同的是,非空斷言操作符不會(huì)防止出現(xiàn) null 或 undefined。
let s = e!.name; // 斷言e是非空并訪問name屬性
4. Vue
組件的 Ts
寫法
從 vue2.5 之后,vue 對(duì) ts 有更好的支持。根據(jù)官方文檔,vue 結(jié)合 typescript ,有兩種書寫方式:
**Vue.extend **
import Vue from 'vue' const Component = Vue.extend({ // type inference enabled })
vue-class-component
import { Component, Vue, Prop } from 'vue-property-decorator'@Componentexport default class Test extends Vue { @Prop({ type: Object }) private test: { value: string }}
理想情況下, Vue.extend
的書寫方式,是學(xué)習(xí)成本最低的。在現(xiàn)有寫法的基礎(chǔ)上,幾乎 0 成本的遷移。
但是 Vue.extend
模式,需要與 mixins
結(jié)合使用。在 mixin 中定義的方法,不會(huì)被 typescript 識(shí)別到
,這就意味著會(huì)出現(xiàn) 丟失代碼提示、類型檢查、編譯報(bào)錯(cuò)等問題。
菜鳥才做選擇,大佬都挑最好的。直接講第二種吧:
4.1 vue-class-component
我們回到 src/components/HelloWorld.vue
<template> <div class="hello"> <h1>{{ msg }}</h1> <!-- 省略 --> </div></template><script lang="ts">import { Component, Prop, Vue } from 'vue-property-decorator';@Componentexport default class HelloWorld extends Vue { @Prop() private msg!: string;}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped></style>
有寫過 python
的同學(xué)應(yīng)該會(huì)發(fā)現(xiàn)似曾相識(shí):
vue-property-decorator
這個(gè)官方支持的庫里,提供了函數(shù) **裝飾器(修飾符)**語法
1. 函數(shù)修飾符 @
“@”,與其說是修飾函數(shù)倒不如說是引用、調(diào)用它修飾的函數(shù)。
或者用句大白話描述: @
: "下面的被我包圍了。"
舉個(gè)栗子,下面的一段代碼,里面兩個(gè)函數(shù),沒有被調(diào)用,也會(huì)有輸出結(jié)果:
test(f){ console.log("before ..."); f() console.log("after ..."); }@testfunc(){ console.log("func was called");}
直接運(yùn)行,輸出結(jié)果:
before ...
func was called
after ...
上面代碼可以看出來:
test
和 func
,沒有調(diào)用它們。但是,解釋器讀到函數(shù)修飾符“@”的時(shí)候,后面步驟會(huì)是這樣:
test
函數(shù), test
函數(shù)的入口參數(shù)就是那個(gè)叫“ func
”的函數(shù);test
函數(shù)被執(zhí)行,入口參數(shù)的(也就是 func
函數(shù))會(huì)被調(diào)用(執(zhí)行);換言之,修飾符帶的那個(gè)函數(shù)的入口參數(shù),就是下面的那個(gè)整個(gè)的函數(shù)。有點(diǎn)兒類似 JavaScrip
t里面的 function a (function () { ... });
2. vue-property-decorator
和 vuex-class
提供的裝飾器
vue-property-decorator
的裝飾器:
vuex-class
的裝飾器:
我們拿原始Vue組件模版來看:
import {componentA,componentB} from '@/components';export default { components: { componentA, componentB}, props: { propA: { type: Number }, propB: { default: 'default value' }, propC: { type: [String, Boolean] }, } // 組件數(shù)據(jù) data () { return { message: 'Hello' } }, // 計(jì)算屬性 computed: { reversedMessage () { return this.message.split('').reverse().join('') } // Vuex數(shù)據(jù) step() { return this.$store.state.count } }, methods: { changeMessage () { this.message = "Good bye" }, getName() { let name = this.$store.getters['person/name'] return name } }, // 生命周期 created () { }, mounted () { }, updated () { }, destroyed () { }}
以上模版替換成修飾符寫法則是:
import { Component, Vue, Prop } from 'vue-property-decorator';import { State, Getter } from 'vuex-class';import { count, name } from '@/person'import { componentA, componentB } from '@/components';@Component({ components:{ componentA, componentB},})export default class HelloWorld extends Vue{ @Prop(Number) readonly propA!: number | undefined @Prop({ default: 'default value' }) readonly propB!: string @Prop([String, Boolean]) readonly propC!: string | boolean | undefined // 原data message = 'Hello' // 計(jì)算屬性 private get reversedMessage (): string[] { return this.message.split('').reverse().join('') } // Vuex 數(shù)據(jù) @State((state: IRootState) => state . booking. currentStep) step!: number @Getter( 'person/name') name!: name // method public changeMessage (): void { this.message = 'Good bye' }, public getName(): string { let storeName = name return storeName } // 生命周期 private created ():void { }, private mounted ():void { }, private updated ():void { }, private destroyed ():void { }}
正如你所看到的,我們?cè)谏芷?列表那都添加 private XXXX
方法,因?yàn)檫@不應(yīng)該公開給其他組件。
而不對(duì) method
做私有約束的原因是,可能會(huì)用到 @Emit
來向父組件傳遞信息。
4.2 添加全局工具
引入全局模塊,需要改 main.ts
:
import Vue from 'vue';import App from './App.vue';import router from './router';import store from './store';Vue.config.productionTip = false;new Vue({ router, store, render: (h) => h(App),}).$mount('#app');
npm i VueI18n
import Vue from 'vue';import App from './App.vue';import router from './router';import store from './store';// 新模塊import i18n from './i18n';Vue.config.productionTip = false;new Vue({ router, store, i18n, // 新模塊 render: (h) => h(App),}).$mount('#app');
但僅僅這樣,還不夠。你需要?jiǎng)?src/vue-shim.d.ts
:
// 聲明全局方法declare module 'vue/types/vue' { interface Vue { readonly $i18n: VueI18Next; $t: TranslationFunction; }}
之后使用 this.$i18n()
的話就不會(huì)報(bào)錯(cuò)了。
4.3 Axios 使用與封裝
Axios的封裝千人千面
如果只是想簡(jiǎn)單在Ts里體驗(yàn)使用Axios,可以安裝vue-axios
簡(jiǎn)單使用Axios
$ npm i axios vue-axios
main.ts添加:
import Vue from 'vue'import axios from 'axios'import VueAxios from 'vue-axios'Vue.use(VueAxios, axios)
然后在組件內(nèi)使用:
Vue.axios.get(api).then((response) => { console.log(response.data)})this.axios.get(api).then((response) => { console.log(response.data)})this.$http.get(api).then((response) => { console.log(response.data)})
1. 新建文件 request.ts
文件目錄:
-api - main.ts // 實(shí)際調(diào)用-utils - request.ts // 接口封裝
2. request.ts
文件解析
import * as axios from 'axios';import store from '@/store';// 這里可根據(jù)具體使用的UI組件庫進(jìn)行替換import { Toast } from 'vant';import { AxiosResponse, AxiosRequestConfig } from 'axios'; /* baseURL 按實(shí)際項(xiàng)目來定義 */const baseURL = process.env.VUE_APP_URL; /* 創(chuàng)建axios實(shí)例 */const service = axios.default.create({ baseURL, timeout: 0, // 請(qǐng)求超時(shí)時(shí)間 maxContentLength: 4000,});service.interceptors.request.use((config: AxiosRequestConfig) => { return config;}, (error: any) => { Promise.reject(error);});service.interceptors.response.use( (response: AxiosResponse) => { if (response.status !== 200) { Toast.fail('請(qǐng)求錯(cuò)誤!'); } else { return response.data; } }, (error: any) => { return Promise.reject(error); }); export default service;
為了方便,我們還需要定義一套固定的 axios 返回的格式,新建 ajax.ts
:
export interface AjaxResponse { code: number; data: any; message: string;}
3. main.ts
接口調(diào)用:
// api/main.tsimport request from '../utils/request';// getexport function getSomeThings(params:any) { return request({ url: '/api/getSomethings', });}// postexport function postSomeThings(params:any) { return request({ url: '/api/postSomethings', methods: 'post', data: params });}
5. 編寫一個(gè)組件
為了減少時(shí)間,我們來替換掉 src/components/HelloWorld.vue
,做一個(gè)博客帖子組件:
<template> <div class="blogpost"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> <p class="meta">Written by {{ post.author }} on {{ date }}</p> </div></template><script lang="ts">import { Component, Prop, Vue } from 'vue-property-decorator';// 在這里對(duì)數(shù)據(jù)進(jìn)行類型約束export interface Post { title: string; body: string; author: string; datePosted: Date;}@Componentexport default class HelloWorld extends Vue { @Prop() private post!: Post; get date() { return `${this.post.datePosted.getDate()}/${this.post.datePosted.getMonth()}/${this.post.datePosted.getFullYear()}`; }}</script><style scoped>h2 { text-decoration: underline;}p.meta { font-style: italic;}</style>
然后在 Home.vue
中使用:
<template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"> <HelloWorld v-for="blogPost in blogPosts" :post="blogPost" :key="blogPost.title" /> </div></template><script lang="ts">import { Component, Vue } from 'vue-property-decorator';import HelloWorld, { Post } from '@/components/HelloWorld.vue'; // @ is an alias to /src@Component({ components: { HelloWorld, },})export default class Home extends Vue { private blogPosts: Post[] = [ { title: 'My first blogpost ever!', body: 'Lorem ipsum dolor sit amet.', author: 'Elke', datePosted: new Date(2019, 1, 18), }, { title: 'Look I am blogging!', body: 'Hurray for me, this is my second post!', author: 'Elke', datePosted: new Date(2019, 1, 19), }, { title: 'Another one?!', body: 'Another one!', author: 'Elke', datePosted: new Date(2019, 1, 20), }, ];}</script>
這時(shí)候運(yùn)行項(xiàng)目:
這就是簡(jiǎn)單的父子組件
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注