Skip to content

Vue2 速通

2020-10-01-view(s)-comment(s)- min read

Basic example

html
<div id="app">{{ message }}</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      message: "foo",
    },
  });
</script>

和 React 一样,Vue 通过虚拟 DOM 对元素进行更高效管理,而不同于 React 的 JSX,Vue 通过元素、JS 分离的模板语法进行开发。上述是一个简单的 Vue 示例,通过 el 绑定元素、通过 data 中的 message 实现从存储到展示的单向数据流(一旦 message 发生变化,元素中的值(即视图)也会立刻变化)。

data & methods & computed & watch

data

当一个 Vue 实例被创建时,data 中所有的属性将被加到 Vue 的响应式系统中,当属性值发生变化,视图立即会发生响应(作出更新)。

需要注意的是,data 总是一个对象,用来存储应用的诸多状态。 可以通过 $ 前缀访问诸如 data、methods 等的属性:

javascript
var vm = new Vue({
  el: "#app",
  data: { a: 1 },
});

vm.$data === { a: 1 }; // => true
vm.$el === document.getElementById("app"); // => true

vm.a = 2;

methods

顾名思义,methods 用以存放应用中的一些方法,你比如:

html
<div id="app">
  <div>
    <button v-on:click="increase">{{ counter }}</button>
    <button v-on:click="decrease">{{ counter }}</button>
  </div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: { counter: 0 },
    methods: {
      increase() {
        this.counter += 1;
      },
      decrease() {
        this.counter -= 1;
      },
    },
  });
</script>

其中,绑定事件时,也可以 v-on:click="increase()",毕竟是 Vue。

另外,由于用到了 this,于是 methods 里的方法不能写箭头函数,除了上述的写法,也可以:

javascript
methods: {
  increase: function(){this.counter += 1},
  decrease: function(){this.counter -= 1}
}

computed

虽然模板中可以写表达式,但是当逻辑逐渐复杂起来的时候,出于易读、好维护的角度,我们更希望通过 带返回的函数 的形式,将这些逻辑组织起来,computed 便是为此而生,你比如:

html
<div id="app">
  <div>{{ message.split('').reverse().join('') }}</div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: { message: "foo, bar." },
  });
</script>

模板中包含了一段关于反转字符串的逻辑,通过 computed,虽然本质上一个意思,但不论是复用还是维护的时候,都非常方便:

html
<div id="app">
  <div>{{ reversedMessage }}</div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: { message: "foo, bar." },
    computed: {
      reversedMessage: function () {
        return this.message.split("").reverse().join("");
      },
    },
  });
</script>

另外,不同于 methods 中的方法,computed 基于响应式依赖进行缓存,无需显式调用,当所依赖的状态变量(例子中的 message )发生变化时,自动更新计算值。

比起将 computed 中的属性看做方法,把之理解为与 data 中同等的状态变量更合适。事实上,默认情况下为 computed 中属性提供的方法(上述 reversedMessage 对应的方法),会被作为该属性的 getter 方法(一般也只需要 getter 方法),在必要的时候,我们可以显式地提供(同时或者只一者)setter、getter 方法:

html
<div id="app">
  <div>
    firstName: {{ firstName }} <br />
    lastName: {{ lastName }} <br />
    fullName: {{ fullName }}
  </div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: { firstName: "Foo", lastName: "Bar" },
    computed: {
      fullName: {
        get: function () {
          return this.firstName + " " + this.lastName;
        },
        set: function (fullName) {
          const nameArr = fullName.split(" ");
          this.firstName = nameArr[0];
          this.lastName = nameArr[1];
        },
      },
    },
  });
  vm.fullName = "Bar Foo";
</script>

当为 fullName 赋值时,set 内逻辑将被执行,即 firstName、lastName 会同步更新。

watch

上述 computed 中提到,依赖某个变量的状态自动更新计算值,实际上 Vue 提供了另一种更通用地、对属性值进行监听的方式:watch。不似 computed 侧重点在于自动更新计算值,其作用侧重于自动回调。说得更明白些,当一个状态变量(或多个)的改变,引起另一个状态变量的改变时,应优先选用 computed,此外的情景才应当选择 watch。

一个化简为繁的例子如下:

html
<div id="app">
  <div>{{ fullName }}</div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: { firstName: "Foo", lastName: "Bar", fullName: "" },
    watch: {
      firstName: function () {
        this.fullName = this.firstName + " " + this.lastName;
      },
      lastName: function () {
        this.fullName = this.firstName + " " + this.lastName;
      },
    },
  });
  vm.firstName = "Bar";
  vm.lastName = "Foo";
</script>

生命周期与生命周期 Hooks

Vue 应用的生命周期如下(官网):

生命周期

针对不同的阶段,框架提供了一些所谓的生命周期钩子,比如 created,可用来在实例被创建后执行一些定制化的初始逻辑。从写法上看,这些钩子们和 data 是同级属性,此外还包括 mountedupdateddestroyed

此间的注意点依然是不要将逻辑写成箭头函数,其原因仍然是 js 中 this 上下文在普通函数、箭头函数中有所差异。

基础模板语法

v-bind

v-bind 用于绑定一些 html attribute,你比如 title、id、class、style、href 等等等等:

html
<div id="app">
  <div v-bind:title="title">{{ message }}</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      title: "this is a title",
      message: "foo",
    },
  });
</script>

绑定 class 时:

html
<div id="app">
  <div v-bind:class="{foo: isFoo, bar: isBar}">{{ message }}</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      isFoo: true,
      isBar: true,
      message: "foo",
    },
  });
</script>
<style>
  .foo {
    background-color: bisque;
  }
  .bar {
    color: burlywood;
  }
</style>

绑定 style:

html
<div id="app">
  <div v-bind:style="{color: foo, 'background-color': bar}">{{ message }}</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      foo: "burlywood",
      bar: "bisque",
      message: "foo",
    },
  });
</script>

v-bind:attr 可以缩写为 :attr

html
<div id="app">
  <button :disabled="foo">click me</button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      foo: true,
    },
  });
</script>

数组语法:

html
<div id="app">
  <button :style="[foo, bar]">click me</button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      foo: {
        "background-color": "bisque",
      },
      bar: {
        color: "burlywood",
        display: ["-webkit-box", "-ms-flexbox", "flex"], // 多重值常用于兼容
      },
    },
  });
</script>

v-on

用于监听 DOM 事件,你比如:

html
<div id="app">
  <button v-on:click="giveResponse">foo</button>
  <button v-on:click="giveResponse()">bar</button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      giveResponse: () => alert("u just click a btn"),
    },
  });
</script>

当然,直接写表达式也是支持的:

html
<div id="app">
  <button v-on:click="btnName==='foo'?btnName='bar':btnName='foo'">
    {{ btnName }}
  </button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      btnName: "bar",
    },
  });
</script>

很多时候,还需要获取到原生 DOM 事件的 event 参数:

html
<div id="app">
  <button v-on:click="giveResponse($event)">foo</button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      giveResponse: (ev) => alert(ev.clientX),
    },
  });
</script>

此外,vue 支持对事件进行修饰:

  • .stop - 阻止事件继续传播(stopPropagation
  • .prevent - 禁止默认行为(比如 submit 后重载页面)
  • .capture - 将由内部元素触发的事件先行处理,而后才交给内部元素
  • .self - 只有事件是当前元素本身触发的才处理
  • .once - 事件只响应一把
  • .passive - 即 addEventListener 中的 passive 参数,可以浅显地理解为通过设置 passive 告诉 js 引擎事件的默认行为(似乎是为 scroll 而生)不想被阻止。默认行为会立刻发生,而不会等到事件绑定的函数执行完成才进行。这个立意本身可能就与 prevent 相反,所以二者连用从逻辑意义上是 confusing 的,从结果上看,会因为 prevent 被忽略掉。

修饰语法是支持串联的,你比如:

html
<div id="app" v-on:click.once.capture="capture">
  <button v-on:click="giveResponse($event)">foo</button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      giveResponse: (ev) => alert(ev.clientX),
      capture: () => alert("captured"),
    },
  });
</script>

特殊地,修饰可以是按键,你比如:

html
<input id="app" v-on:keyup.arrow-left="pageLeftUp" />
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      pageLeftUp: () => alert("page left is up"),
    },
  });
</script>

特殊的特殊地,可以将点击事件与按键(经过探究仅限于系统按键,诸如 meta、shift 等等)事件串联,实现对诸如“在按下 shift 的情况下点击鼠标左键”的响应:

html
<button id="app" v-on:click.shift.left="shiftLeftClick">foo</button>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      shiftLeftClick: () => alert("shift and left mouse button"),
    },
  });
</script>

其中,值得一提的是,绑定 click 事件时可以修饰以 .leftmiddleright,分别用以仅响应鼠标左、中、右键。

此外,2.5.0 新增了 .exact 修饰,用于配合系统修饰(meta、ctrl、shift 等)使用,表示目标按键有且仅有。你比如按且仅按下 shift、鼠标左键时才响应可以描述为:

html
<button id="app" v-on:click.shift.left.exact="shiftLeftClick">foo</button>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      shiftLeftClick: () => alert("exactly are shift and left mouse button"),
    },
  });
</script>

另外,v-on:event 可以缩写为 @event,你比如:

html
<button id="app" @click.shift.left.exact="shiftLeftClick">foo</button>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      shiftLeftClick: () => alert("shift and left mouse button"),
    },
  });
</script>

v-if

v-if 用于条件性渲染,当条件为 truthy 值的时候元素才会被渲染。你比如:

html
<div id="app">
  <div v-if="showFoo">foo</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      showFoo: true,
    },
  });
</script>

v-if 之后可以接 v-else-if

html
<div id="app">
  <div v-if="showFoo">foo</div>
  <div v-else-if="showBar">bar</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      showFoo: false,
      showBar: true,
    },
  });
</script>

当然,v-else 乃兵家必备:

html
<div id="app">
  <div v-if="showFoo">foo</div>
  <div v-else-if="showBar">bar</div>
  <div v-else>baz</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      showFoo: false,
      showBar: false,
    },
  });
</script>

其中需要注意的是,v-else-ifv-else 需要紧跟着 v-if 或者 v-else-if。另外,v-show 也用来条件渲染,其与 v-if 区别在于,v-show 实际控制 css 属性 display,当条件为 falsy 时,display 设置为 none,元素虽不展示,但仍在内存中。相比之下,使用 v-if 时,只有当条件为 truthy 时,元素才会被渲染。

html
<div id="app">
  <div v-show="showFoo">foo</div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      showFoo: true,
    },
  });
</script>

v-for

通过 v-for 可以更直观地实现譬如 react 中:

jsx
<div id="app">
  {aList.map((item) => (
    <div>{item}</div>
  ))}
</div>

为:

html
<div id="app">
  <div v-for="item in aList">
    <div>{{ item }}</div>
  </div>
</div>

若其中,aList 为一个朴实无华的字符串列表:['foo', 'bar', 'baz'],二者最终都会被渲染成:

html
<div id="app">
  <div>
    <div>foo</div>
    <div>bar</div>
    <div>baz</div>
  </div>
</div>

v-model

v-model 用于诸如 inputtextareaselect 等表单元素上,创建双向数据绑定(内存里值的改变 会引起 展示的更新,而因为交互产生的组件状态的变化 也会使得 内存中值的更新)。

不似 React,Vue 通过数据双向绑定(语法糖),为开发者屏蔽了组件事件、属性上的差异,你比如说在 React 中为 checkbox、textarea 分别建立双向数据流需要这样:

html
<div>
  <input type="checkbox" checked={checked === "foo"} onChange={() =>
  setChecked(checked === "foo" ? "" : "foo")} />
  <br />
  <textarea value={theText} onChange={(ev: { target: { value: string } }) =>
  setTheText(ev.target.value) } />
</div>

相应地,要声明两个状态变量:

javascript
const [checked, setChecked] = useState < string > "";
const [theText, setTheText] = useState < string > "";

而 Vue 中:

html
<div id="app">
  <input type="checkbox" v-model="checked" value="foo" />
  <br />
  <textarea v-model="theText"></textarea>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      checked: false,
      theText: "",
    },
  });
</script>

相比之下,Vue 更加简洁,同时不需要开发者自行处理诸如 onChange 等的事件,开发体验更加友好。

  • 在使用 v-model 时,元素原生的 value、checked、selected 属性的值将会被忽略,元素经渲染后,属性的真实值仅来源于 Vue 实例(更具体而言,data 中)。
  • 而示例中,由于 input 的类型是 checkbox,故此时 value 属性,原生意义上本该不起什么作用,但 Vue 将之利用起来,用以判断 checkbox 是否被勾选。

Vue 在很多情况下会显得更加方便,主要是因为框架很多时候不仅实现了原子性的功能,还会在此基础上再兼容更多一些的情况。比如上述例子中,需要的是一组 checkbox,这时,在 React 中,可能需要这样做:

html
<div>
  <input type="checkbox" checked={checkedList.includes("foo")} onChange={() =>
  setCheckedList( checkedList.includes("foo") ? checkedList.filter((checked:
  string) => checked !== "foo") : checkedList.concat("foo") ) } /> <input
  type="checkbox" checked={checkedList.includes("bar")} onChange={() =>
  setCheckedList( checkedList.includes("bar") ? checkedList.filter((checked:
  string) => checked !== "bar") : checkedList.concat("bar") ) } /> <input
  type="checkbox" checked={checkedList.includes("baz")} onChange={() =>
  setCheckedList( checkedList.includes("baz") ? checkedList.filter((checked:
  string) => checked !== "baz") : checkedList.concat("baz") ) } />
</div>

相应地,需要声明的状态变量变为(另一种做法是,声明三个状态变量,三个 input 分别处理事件,两种本质上是一个意思):

typescript
const [checkedList, setCheckedList] = useState<string[]>([]);

而 Vue 中,则:

html
<div id="app">
  <input type="checkbox" v-model="checkedList" value="foo" />
  <input type="checkbox" v-model="checkedList" value="bar" />
  <input type="checkbox" v-model="checkedList" value="baz" />
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      checkedList: [],
    },
  });
</script>

语法糖万岁。

另外,也可以对 v-model 进行一些修饰:

  • .lazy - 由交互产生的元素值的变化,不立即更新到相关状态变量(比如汉字输入时,有拼音这个过程,而通过 lazy 可以忽略该过程,这样可以减少更新的次数,减少开销)。
  • .number - 事实上即便将 input 的 type 设为 number,读取元素值的时候,得到的依然是字符串。通过 .number 可以将元素值自动转化成数值(当值无法被 parseInt 的时候,则得到的仍然是原本的值)。
  • .trim - 该修饰用以过滤输入首尾的空白字符。

v-once

表示元素只会被渲染一把,此后状态变量的变化不会引起之展示的更新。

v-html

表示与元素绑定的变量,是一段可解析的 html 字符串,进而渲染时会用解析后的该 html 替换使用 v-html 的元素。你比如:

html
<div id="app">
  <div v-html="theValue"></div>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      theValue: '<span style="color:red;">foo</span>',
    },
  });
</script>

div 部分会被渲染成:

html
<div id="app">
  <span style="color:red;">foo</span>
</div>

通过 key 阻止复用

Vue 在更新展示时,本着提升效率以及减小开销的原则,会复用一些元素。而有的时候,我们不希望元素被复用(或者说我们希望它更新),此时便可以使用 key。你比如说:

html
<div id="app">
  <div v-if="loginType === 'username'">
    <label>Username</label>
    <input placeholder="Enter your username" />
  </div>
  <div v-else>
    <label>Email</label>
    <input placeholder="Enter your email address" />
  </div>
  <button
    v-on:click='loginType==="username"?loginType="email":loginType="username"'
  >
    toggle login type
  </button>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      loginType: "username",
    },
  });
</script>

这样当切换登录方式时,会发现此前的输入仍然在 input 元素中,这是不合理的,于是加入 key:

html
<div v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username" />
</div>
<div v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email" />
</div>

组件化

何为 Vue 组件

所谓 Vue 组件,即一个可复用的 Vue 实例,一个基本的组件声明示例如下:

html
<div id="app">
  <button-counter></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    data: function () {
      return {
        count: 0,
      };
    },
    template: '<button v-on:click="count++">{{count}}</button>',
  });
  new Vue({ el: "#app" });
</script>

复用

上述组件,复用起来就像这样:

html
<div id="app">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    data: function () {
      return {
        count: 0,
      };
    },
    template: '<button v-on:click="count++">{{count}}</button>',
  });
  new Vue({ el: "#app" });
</script>

值得注意的是,组件的 data 不似普通实例那样直接是一个对象,组件的 data 应当是一个函数,通过返回值提供出一个对象。这是因为组件每复用一把,就相当于增加一个该组件的实例,data 直接对应一个对象会导致诸个组件的实例共用同一个响应状态,其中一个实例的状态变化会同步作用到其他组件上,大多数情况下这显然是不合理的。而通过函数,每个实例会维护各自的状态,互相独立。

组件的 Props

有时,在实例化某个组件的时候,我们希望传入一些定制化的内容,一个简单的例子是某个用于表示问候的组件,在实例化时可能需要传入不同的问候语:

javascript
Vue.component("greeting", {
  template: "<div> Hello, Vue :) </div>",
});

props 便是为此而生的:

html
<div id="app">
  <greeting msg="Foo"></greeting>
</div>
<script>
  Vue.component("greeting", {
    props: ["msg"],
    template: "<div> Hello, {{ msg }} :) </div>",
  });
  new Vue({ el: "#app" });
</script>

此外,我们可以在实例化组件时,借助 v-bind 将传入的内容绑定为动态变量。

监听组件内事件

有时,我们需要为组件内的一些元素(比如说 button )绑定一些方法,而这些方法通常又是与外层状态紧密相关的,这时我们可以使用 $emit

html
<div id="app">
  <button-counter @on-click="onClick" :count="count"></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    props: ["count"],
    template:
      '<button @click="$emit(`on-click`)"> Click Me {{ count }} Times </button>',
  });
  new Vue({
    el: "#app",
    data: {
      count: 0,
    },
    methods: {
      onClick() {
        this.count += 1;
      },
    },
  });
</script>

其中,需要注意的是,$emit 函数的实参可以说是字符串模板里的字符串模板,传入其中的函数名不能是驼峰式的命名,需要变成 kebab-case 的命名方式(短横线分隔命名法)。

而当我们监听到事件后,不只是想调用函数,还想向函数中传入一些由组件抛出的固定值(变化步长等)的时候,可以这样:

html
<div id="app">
  <button-counter @on-click="onClick($event)" :count="count"></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    props: ["count"],
    template:
      '<button @click="$emit(`on-click`, 2)"> Click Me {{ count }} Times </button>',
  });
  new Vue({
    el: "#app",
    data: {
      count: 0,
    },
    methods: {
      onClick(step) {
        this.count += step;
      },
    },
  });
</script>

组件上使用 v-model

前文中有说到,v-model 通过语法糖为开发者屏蔽了各种表单元素属性、事件上的差异与细节,实际上,一般情况下(不修饰以 .lazy),v-model 主要粘合的是元素的 value 以及 input 事件,这意思是:

html
<input v-model="searchText" />

与:

html
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
/>

等价。

同理,只要我们的自定义组件暴露出了 value、以及 input 事件,那么我们便可以将 v-model 作用在之上:

html
<div id="app">
  <button-counter v-model="count"></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    props: ["value"],
    template:
      '<button @click="$emit(`input`, value + 1)"> Click Me {{ value }} Times </button>',
  });
  new Vue({
    el: "#app",
    data: {
      count: 0,
    },
  });
</script>

插槽

何为插槽

有时,我们想要动态地向自定义的组件中再塞点什么,但在没有和组件商量好的情况下, 组件是不会答应的,你比如:

html
<div id="app">
  <simple-div> Want To Add More </simple-div>
</div>
<script>
  Vue.component("simple-div", {
    template: "<div> Only This Sentence </div>",
  });
  new Vue({
    el: "#app",
  });
</script>

Want To Add More 是不会被渲染出来的。这时我们可以通过 slot(插槽)实现想要的效果:

html
<div id="app">
  <simple-div> More Added </simple-div>
</div>
<script>
  Vue.component("simple-div", {
    template: `
      <div> 
        More Than This Sentence
        <br/>
        <slot></slot>
  		</div>
		`,
  });
  new Vue({
    el: "#app",
  });
</script>

缺省值

slot 标签间的内容,将作为缺省值,当未提供具体内容时,直接显示:

html
<div id="app">
  <simple-div></simple-div>
</div>
<script>
  Vue.component("simple-div", {
    template: `
      <div> 
        More Than This Sentence
        <br/>
        <slot>Default Content</slot>
      </div>
    `,
  });
  new Vue({
    el: "#app",
  });
</script>

此时,DefaultContent 将被显示。

具名插槽

顾名思义,具名插槽即是有名字的插槽。在某个自定义组件内,我们可能想在后继多处补充内容,为了让内容归到相应位置,我们需要给插槽一个名称。最典型的是 Web 中的 header、content 以及 footer。你比如:

html
<div id="app">
  <simple-div>
    <template v-slot:header>header</template>
    <p>content 1</p>
    <p>content 2</p>
    <template v-slot:footer>footer</template>
    <p>content 3</p>
  </simple-div>
</div>
<script>
  Vue.component("simple-div", {
    template: `
<div> 
<header>
<slot name="header"></slot>
  </header>
<content>
<slot></slot>
  </content>
<footer>
<slot name="footer"></slot>
  </footer>
  </div>
`,
  });
  new Vue({
    el: "#app",
  });
</script>

其中,没有被 template 包裹的部分不论与其他 template 的位置关系,会被归到默认插槽(也就是组件模板中,插槽标签中没有提供 name 属性的插槽)中,当然显式把这些元素归到默认插槽中也是可以的:

html
<template v-slot:default>
  <p>content 1</p>
  <p>content 2</p>
  <p>content 3</p>
</template>

此外,动态指令参数也可以用在 v-slot 上:

html
<template v-slot:[dynamicSlotName]>
  <p>content 1</p>
  <p>content 2</p>
  <p>content 3</p>
</template>

再外,具名插槽也是可以缩写的,用 #

html
<template #header> header </template>

而 default 插槽缩写时必须带上 default:

html
<template #default> header </template>

插槽 Props

通过插槽的 Props,我们可以在外部访问组件的内部状态,其声明需要在 slot 元素上使用 v-bind。与 v-bind 紧接的是我们在外部可使用的、该状态的别称,v-bind 其后的引号内为该状态在内部的名称:

html
<div id="app">
  <simple-div>
    <template v-slot:default="slotProps">
      {{ slotProps.userProp.lastName }}
    </template>
  </simple-div>
</div>
<script>
  Vue.component("simple-div", {
    data: function () {
      return {
        user: {
          firstName: "Foo",
          lastName: "Bar",
        },
      };
    },
    template: `
<div> 
<slot v-bind:userProp='user'>{{ user.firstName }}</slot>
  </div>
`,
  });
  new Vue({
    el: "#app",
  });
</script>

当然,slotProps 是随便起名称,可以替换成任意名称,也完全可以解构使用:

html
<template v-slot:default="{ userProp }"> {{ userProp.lastName }} </template>

甚至也可以为读取不到内部属性的情况准备一个默认值:

html
<template v-slot:default="{ userProp = { lastName: 'foo'} }">
  {{ userProp.lastName }}
</template>

独占默认插槽

当组件中只有默认插槽时,可以将 v-slot 放在组件中(不必借助 template)使用,用于绑定一些动态变量,除了这种情况,v-slot 只能用在 template 元素上:

html
<div id="app">
  <simple-div v-slot:default="msg">{{ msg }}</simple-div>
</div>
<script>
  Vue.component("simple-div", {
    data: function () {
      return {
        msg: "Hello",
      };
    },
    template: `
<div> 
<slot v-bind:message="msg"></slot>
  </div>
`,
  });
  new Vue({
    el: "#app",
  });
</script>

或者直接不写 default 也是 Okay 的:

html
<simple-div v-slot="slotProps">{{ slotProps.message }}</simple-div>

通过 is 变身

component 中

有时,我们希望某处是个动态的组件(根据状态切换之实质),这时,我们可以使用 is

html
<div id="app">
  <component :is="isNormal?`span`:`simple-div`"></component>
</div>
<script>
  Vue.component("simple-div", {
    template: `
<div> 
<slot> Hello </slot>
  </div>
`,
  });
  new Vue({
    el: "#app",
    data: {
      isNormal: false,
    },
  });
</script>

上述例子也表明 is 也可用于普通 HTML 元素之上(虽然有一些特殊之处,暂且不论)。

一些限制元素中

另外,在一些特殊 HTML 元素,诸如 ul、ol、table、select,从语法上来看,其中的元素的类型是有严格限制的,比如 ul 中 只能是 li 元素,此时也可以通过 is 来瞒天过海:

html
<ul id="app">
  <li :is="`simple-div`"></li>
</ul>
<script>
  Vue.component("simple-div", {
    template: `
      <div> 
        <slot> Hello </slot>
      </div>
    `,
  });
  new Vue({
    el: "#app",
    data: {
      isNormal: false,
    },
  });
</script>

但这个限制烦恼,在以下的 Vue 编程中遇不到:

  • 字符串模板中(比如,template:'...'
  • 单文件组件(即 xxx.vue 中)
  • <script type="text/x-template"></script>