Skip to content

VueJS组件自定义(上)

前言

注: 这是阅读《循序渐进Vue.js 3前端开发实战》和官方文档等做的笔记

在Vue框架中,将网页甚至整个网站抽象为一个应用程序,即Web App,而这个Web App主要由各种组件组成;网站渲染的起点元素被称为 根组件

Vue App实例:根组件

创建一个Vue App实例:

const App = Vue.createApp({});

注意到提供的参数为空,这便是一个默认的参数,可以通过传入参数提供一些配置选项。

data()选项

data()选项是需要配置为一个JS函数,返回的是这个App所需要的全局数据:

const AppData = {
  count: 0
}

const App = Vue.createApp({
  data(){
    return AppData;
  }
})

props选项

props选项是用于接收父组件传递的数据:

const App = Vue.createApp({
  props:["title"]
})

这里的根组件App一般不需要接收父组件传递的属性值。

computed选项

computed选项是组件的计算属性,可以专门定义getter或setter函数来对读取、赋值进行分别操作:

const App = Vue.createApp({
  computed:{
    name:{
      get(){
        return this.name;
      },
      set(newName){
        this.name = newName
      }
    },
    age:{
      get(){
        return this.age;
      }
    }
  }
})

methods选项

methods选项是用来定义组件中使用的函数的地方:

const App = Vue.createApp({
  methods:{
    click(){
      this.count++;
    }
  }
})

watch选项

watch选项是用来监听属性变化的,从而立即做出相应操作:

const App = Vue.createApp({
  watch:{
    name(value, newValue){
      console.log(`name change from ${value} to ${newValue}`);
    }
  }
})

自定义组件:选项与方法

Vue App实例也是一个组件,只不过它是根组件,因此要定义一个组件,也是只需要包含相应的选项与方法即可:

const AlertComponent = {
  data(){
    return {
      msg: "Alert Message",
      count: 0
    }
  },
  methods:{
    click(){
      alert(this.msg + this.count++)
    }
  },
  template: `<div><button @click="click">Button</button></div>`
}

它走路像一只鸭子,看起来也像一只鸭子,那么它就是一只鸭子;好了,这就是定义了一个组件,其中template选项定义的是组件的HTML模板。

对于选项和方法的设置,使得不同的组件具备不同的功能与特性。

挂载组件:映射为标签

将这个组件挂载到根组件,也就是Vue App实例上:

const App = Vue.createApp();
App.component("my-alert", AlertComponent);
App.mount("#Application");

通过component方法将组件的名字会映射到具体的组件定义,可以作为标签直接使用:

<div id="Application">
  <my-alert></my-alert>
</div>

因为该组件只在App实例中挂载了,因此它只能在对应挂载标签内部使用,比如这里是id为Application的div标签,在外部是找不到这个组件定义的。

外部属性:灵活设置

所谓外部属性,即可以通过外部传递进来的属性,也就是前面提到的父组件通过props属性传递数据进来:

const AlertComponent = {
  props: ["title"],
  data(){
    return {
      msg: "Alert Message",
      count: 0
    }
  },
  methods:{
    click(){
      alert(this.msg + this.count++)
    }
  },
  template: `<div><button @click="click">Button: {{ title }}</button></div>`
}

这个props定义的外部属性,这里是一个列表形式,但其还有另外的形式,比如检查类型、设置默认值、数据校验等。

外部属性列表的元素可以是任意多个,然后可以在模板部分直接访问到,对这个组件作为标签使用的时候,可以通过父组件传递不同的外部属性值:

<my-alert title="Button 1"></my-alert>
<my-alert title="Button 2"></my-alert>

Vue是单向数据流,即数据只可以从父组件流向子组件,如果子组件要将数据传回父组件,只能通过其它方式,比如响应事件。

组件产生事件:响应父组件

如果想要父组件知道子组件内部的反馈并进一步采取对应的操作,则需要子组件产生事件供父组件捕获,它是通过$emit来产生事件的:

const AlertComponent = {
  props: ["title"],
  methods:{
    click(){
      this.$emit('myclick', this.title);
    }
  },
  template: `
  <div>
      <button @click="$emit('myclick')">Button A: {{ title }}</button>
      <button @click="$emit('myclick', 100)">Button B: {{ title }}</button>
      <button @click="click">Button C: {{ title }}</button>
  </div>
  `
}

这个组件中通过三个方式产生事件,一个是默认参数或无参数,另一个是提供实参100,还有一个在函数中产生事件。

父组件使用这个组件标签的时候,如果想捕获这个事件并进行相应的操作,那么可以这样:

<my-alert title="Button 1" @myclick="myfunc"></my-alert>

当然这里myfunc必须在父组件中已经定义的方法,它会在子组件产生myclick事件时自动捕获并被调用:

const App = Vue.createApp({
  methods:{
    myfunc(param){
      console.log("$emit myclick has let the myfunc to be called with param:", param);
    }
  }
});

提供响应事件信号的方式,让父组件这个调用者可以根据子组件的反馈进行相应的操作,这是非常必要的。

组件支持v-model指令:双向绑定

通过v-bind指令也可以实现v-model指令的功能,就是通过先取值再赋值的形式,但v-model将两步化为一步,实现双向绑定效果。

如果要让一个自定义组件也支持v-model指令,则可以这样:

const InputComponent = {
  props: ["modelValue"],
  methods:{
    action(event){
      this.$emit('update:modelValue', event.target.value);
    }
  },
  template: `
  <div>
      <input @input="action" :value="modelValue"/>
  </div>
  `
}

其原理就是v-model指令默认会传递一个modelValue外部属性到子组件,并捕获子组件产生的update:modelValue事件,在父组件中将作为实参的新值替换旧值,使用方法:

App.component("my-input", InputComponent);

<my-input v-model="inputText"></my-input>

组件的插槽(slot):定制化布局

比如,一个容器类组件,它需要包裹一些内部元素:

<my-container>Hello, Here is a container defined by myself!</my-container>

默认在组件渲染的时候是会丢掉的,但组件内部的元素其实是已经被捕获到一个插槽中的,只要在组件内部显式的调用这个插槽就可以让组件内部元素显示:

const ContainerComponent = {
  template: `
  <div>
      <h1>
        <slot>默认内容</slot>
      </h1>
  </div>
  `
}

这样通过slot就将实例化组件时提供的内部元素可以显示了;如果没有提供内部元素,则插槽会使用默认内容,这非常实用。

还可以提供多个插槽,以在组件内部提供更高的定制性,需要使用插槽名字区分,比如容器类组件内部进行定制化布局:

const ContainerComponent = {
  template: `
  <div>
        <slot name="slot1">默认内容1</slot>
        <slot name="slot2">默认内容2: This is Part II.</slot>
        <slot name="slot3">默认内容3</slot>
  </div>
  `
}

那么使用的时候,指定对应插槽名字即可:

<my-container>
  <template v-slot:slot1>
    <h1>This is Part I.</h1>
  </template>

  <template #slot3>
    <h1>This is Part III.</h1>
  </template>
</my-container>

其中,v-slot:可以用缩写#来代替。

动态组件:component标签

动态组件是Vue的一个高级功能,当需要根据情况将某个位置渲染为不同组件时,则需要用到它:

<div id="Application">
  <component :is="componentName"></component>
</div>

component标签很特殊,它通过is属性提供的组件名字,从而在该位置渲染为对应组件,因此只需要通过改变is属性值就可以控制组件的切换。

属性命名规则

在定义组件的时候,对于外部属性和响应事件等的命名规则是小写字母驼峰式,即小写字母开头,后面每个单词首字母大写,比如:

const myComponent = {
  props:["myCatWeight", "color"],
  methods:{
    click(){
      this.$emit("changeMyCatName", "Kitty");
    }
  }
}

但当这个组件作为标签用于HTML中时,传递这些外部属性和响应事件的写法变成了用-符号连接的驼峰命名法,比如:

<div id="Application">
  <my-component @change-my-cat-name="func1"  my-cat-weight="3.14" color="black"></my-component>
</div>

其实,Vue在构建的时候编译器会将组件内部的属性命名根据规则进行转换,转换后就和HTML中的写法对应上了。

全局组件

Vue App实例,也就是根组件,通过component方法定义或映射的组件被称为全局组件,虽然初期使用起来会比较方便,但随着组件数量增加会变得难以维护,而且有诸多限制,比如全局组件内部的模板属性值是通过字符串形式定义的,也无法在组件内部使用自己的CSS样式等。

与之对应的,是将组件定义为单文件组件,即每个组件单独一个文件,在文件中包含了该组件的模板、脚本、CSS样式等,便于开发大型项目。