Vue学习笔记 ④

news2025/1/16 16:39:46

文章目录

  • template 选项
  • Vue 生命周期
    • 生命周期图示
    • 生命周期钩子
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeUpdate
      • updated
      • beforeDestroy
      • destroyed
  • 组件基础
    • 组件是什么?
    • 组件注册
      • 全局组件
      • 局部组件
      • 组件名
      • 组件复用
      • 自闭合组件
      • 组件的 data 选项
      • 单个根元素
  • 组件\_Prop
    • 注册自定义特性
    • Prop 的大小写
    • 传递静态或动态 Prop
      • 传递一个对象的所有属性
  • 组件\_Prop 验证
  • 组件\_单向数据流
  • 组件\_非 Prop 特性
    • 替换/合并已有的特性
    • 禁用特性继承
  • 组件\_监听组件事件
    • 使用事件抛出一个值
    • 事件名
    • 将原生事件绑定到组件
    • 在组件上使用 v-model
    • .sync 修饰符
    • v-model VS .sync
  • 组件\_插槽
    • 插槽内容
    • 编译作用域
    • 后备内容
    • 具名插槽
    • 作用域插槽
      • 独占默认插槽的缩写语法
      • 解构插槽 Prop
    • 动态插槽名
    • 具名插槽的缩写
    • 废弃了的语法
      • 带有 slot 特性的具名插槽
      • 带有 slot-scope 特性的作用域插槽

template 选项

关于 el

提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTML 元素 实例。

如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。

template

一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素,挂载元素的内容都将被忽略。

<div id="app"></div>
const vm = new Vue({
  el: "#app",
  template: `
    <div id="ceshi">xxx</div>
  `,
});

Vue 初始化到挂载的流程

Vue 生命周期

每个 Vue 实例在被创建时都要经过一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

生命周期图示

生命周期钩子

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算

beforeCreate

在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

<div id="app">
  <div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
  el: "#app",
  data: {
    msg: "hellow world",
  },
  beforeCreate() {
    console.log(this.msg); // undefined
    console.log(this.handleClick); // undefined
    console.log("-----beforeCreate-----");
  },
  methods: {
    handleClick() {
      console.log(handleClick);
    },
  },
  watch: {
    msg: {
      handler() {
        console.log("侦听msg的值");
      },
      immediate: true,
    },
  },
});

打印顺序:

undefined;
undefined;
-----beforeCreate---- - 侦听msg的值;

created

在实例创建完成后被立即调用。

在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。

如果要在第一时间调用 methods 中的方法,或者操作 data 中的数据,可在此钩子中进行操作。
需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。

此时,可以进行数据请求,将请求回来的值赋值给 data 中的数据。

<div id="app">
  <div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
  el: "#app",
  data: {
    msg: "hellow world",
  },
  created() {
    console.log(this.msg); // hello world
    console.log(this.handleClick); // function () {...}
    console.log(this.$el); // undefined
    console.log("----------created-------");
  },
  methods: {
    handleClick() {
      console.log(handleClick);
    },
  },
  watch: {
    msg: {
      handler() {
        console.log("侦听msg的值");
      },
      immediate: true,
    },
  },
});

打印顺序:

侦听msg的值
hellow world
ƒ handleClick () { console.log(handleClick); }
undefined
----------created-------

beforeMount

在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换 el 对应的元素。

在此钩子函数中,可以获取到模板最初始的状态。

此时,可以拿到 vm.$el,只不过为旧模板

const vm = new Vue({
  el: "#app",
  beforeMount() {
    console.log(this.$el);
  },
});

mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。

在该钩子函数中的 vm.$el 为新模板。

执行完该钩子函数后,代表实例已经被完全创建好。

如果要在第一时间,操作页面上的 dom 节点时,可以在此钩子函数中操作

const vm = new Vue({
  el: "#app",
  mounted() {
    console.log(this.$el);
  },
});

beforeUpdate

数据更新时调用,发生在虚拟 DOM 打补丁之前。此时数据已经更新,但是 DOM 还未更新

<div id="app">{{ msg }}</div>
const vm = new Vue({
  el: "#app",
  data: {
    msg: "hellow world",
  },
  beforeUpdate() {
    console.log(this.msg);
    console.log(this.$el);
  },
  methods: {
    handleClick() {
      console.log("handleClick");
    },
  },
});
this.msg = "xxx";

updated

数据更改导致 DOM 重新渲染后,会执行该钩子函数。

此时数据和 dom 同步。

beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用。

可以在该钩子函数中,清除定时器。

<div id="app">{{ msg }}</div>
const vm = new Vue({
  el: "#app",
  data: {
    msg: "hellow world",
    timer: 0,
  },
  created() {
    this.timer = setInterval(() => {
      console.log("xxx");
    }, 500);
  },
  beforeDestroy() {
    clearInterval(this.timer);
  },
});

destroyed

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除。

组件基础

组件是什么?

组件是可复用的 Vue 实例,且带有一个名字,例如名字为 shanshan-cmp,那么我们则可以在一个通过 new Vue 创建的根实例中,把这个组件作为自定义元素来使用:

<div id="app">
  <shanshan-cmp></shanshan-cmp>
</div>
const vm = new Vue({
  el: "#app",
});

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

组件注册

全局组件

Vue.component

利用 Vue.component 创建的组件组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。

参数:

  • {string}
  • {Function | Object} [definition]

用法:
注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称。

示例:

<div id="app">
  <button-counter></button-counter>
</div>
Vue.component("button-counter", {
  data() {
    return {
      count: 0,
    };
  },
  template: `
    <button @click="count ++">你按了我{{ count }}次</button>
  `,
});

const vm = new Vue({
  el: "#app",
});

局部组件

在 components 选项中定义要使用的组件。
对于 components 对象中的每一个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

示例:

<div id="#app">
  <button-counter></button-counter>
</div>
const buttonCounter = {
  data() {
    return {
      count: 0,
    };
  },
  template: `
    <button @click="count ++">你按了我{{ count }}次</button>
  `,
};

const vm = new Vue({
  el: "#app",
  components: {
    "button-counter": buttonCounter,
  },
});

组件名

在注册一个组件的时候,我们始终需要给它一个名字。你给予组件的名字可能依赖于你打算拿它来做什么,所以命名要语义化。

组件名大小写

定义组件名的方式有两种:

使用 kebab-case (横短线分隔命名)

Vue.component("my-component", {
  /***/
});

当使用 kebab-case 定义一个组件时,你必须在引用这个自定义元素时使用 kebab-case,例如:<my-component></my-component>

使用 PascalCase (大驼峰命名)

Vue.component("MyComponent", {
  /***/
});

当使用 PascalCase 定义一个组件时,你在引用这个自定义元素时两种命名法都可以。也就是说<my-component-name><MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即字符串模板或单文件组件) 中使用时只有 kebab-case 是有效的。

另:我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。

组件复用

可以将组件进行任意次数的复用:

<div id="#app">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

自闭合组件

在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的——但在 DOM 模板里永远不要这样做。

自闭合组件表示它们不仅没有内容,而且刻意没有内容。其不同之处就好像书上的一页白纸对比贴有“本页有意留白”标签的白纸。而且没有了额外的闭合标签,你的代码也更简洁。

不幸的是,HTML 并不支持自闭合的自定义元素——只有官方的“空”元素。所以上述策略仅适用于进入 DOM 之前 Vue 的模板编译器能够触达的地方,然后再产出符合 DOM 规范的 HTML。

组件的 data 选项

当我们定义一个组件时,它的 data 并不是像这样直接提供一个对象:

data: {
  count: 0;
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

data () {
  return {
    count: 0
  }
}

如果 Vue 没有这条规则,点击一个按钮就可能会像下面一样影响到其它所有实例:

avatar

单个根元素

每个组件必须只有一个根元素,当模板的元素大于 1 时,可以将模板的内容包裹在一个父元素内。

组件_Prop

注册自定义特性

组件默认只是写好结构、样式和行为,使用的数据应由外界传递给组件。

如何传递?注册需要接收的 prop,将数据作为一个自定义特性传递给组件。

如:

<div id="app">
  <video-item
    title="羊村摇"
    poster="https://developer.duyiedu.com/bz/video/955bac93ccb7f240d25a79b2ff6a9fdbda9537bc.jpg@320w_200h.webp"
    play="638000"
    rank="1207"
  ></video-item>
</div>
Vue.component("video-item", {
  props: ["title", "poster", "play", "rank"],
});

在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样:

<div id="app">
  <video-item
    title="羊村摇"
    poster="https://developer.duyiedu.com/bz/video/955bac93ccb7f240d25a79b2ff6a9fdbda9537bc.jpg@320w_200h.webp"
    play="638000"
    rank="1207"
  ></video-item>
</div>
Vue.component("video-item", {
  props: ["title", "poster", "play", "rank"],
  template: `<div>{{ title }}</div>`,
});

Prop 的大小写

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。故:当 传递的 prop 为 短横线分隔命名时,组件内 的 props 应为 驼峰命名 。
如:

<div id="app">
  <!-- 在 HTML 中是 kebab-case 的 -->
  <video-item sub-title="hello!"></video-item>
</div>
Vue.component("video-item", {
  // 在 JavaScript 中是 camelCase 的
  props: ["subTitle"],
  template: "<h3>{{ postTitle }}</h3>",
});

要注意的是:如果使用的是字符串模板,那么这个限制就不存在了。

传递静态或动态 Prop

像这样,我们已经知道了可以给 prop 传入一个静态的值:

<video-item title="羊村摇"></video-item>

若想要传递一个动态的值,可以配合 v-bind 指令进行传递,如:

<video-item :title="title"></video-item>

传递一个对象的所有属性

如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind 。例如,对于一个给定的对象 person:

person: {
  name: 'shanshan',
  age: 18
}

传递全部属性:

<my-component v-bind="person"></my-component>

上述代码等价于:

<my-component :name="person.name" :age="person.age"></my-component>

组件_Prop 验证

我们可以为组件的 prop 指定验证要求,例如你可以要求一个 prop 的类型为什么。如果说需求没有被满足的话,那么 Vue 会在浏览器控制台中进行警告,这在开发一个会被别人用到的组件时非常的有帮助。

为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

Vue.component("my-component", {
  props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
    callback: Function,
    contactsPromise: Promise,
  },
});

上述代码中,对 prop 进行了基础的类型检查,类型值可以为下列原生构造函数中的一种:StringNumberBooleanArrayObjectDateFunctionSymbol、任何自定义构造函数、或上述内容组成的数组。
需要注意的是nullundefined 会通过任何类型验证。
除基础类型检查外,我们还可以配置高级选项,对 prop 进行其他验证,如:类型检测、自定义验证和设置默认值。
如:

Vue.component("my-component", {
  props: {
    title: {
      type: String, // 检查 prop 是否为给定的类型
      default: "杉杉最美", // 为该 prop 指定一个默认值,对象或数组的默认值必须从一个工厂函数返回,如:default () { return {a: 1, b: 10} },
      required: true, // 定义该 prop 是否是必填项
      validator(prop) {
        // 自定义验证函数,该prop的值回作为唯一的参数代入,若函数返回一个falsy的值,那么就代表验证失败
        return prop.length < 140;
      },
    },
  },
});

为了更好的团队合作,在提交的代码中,prop 的定义应该尽量详细,至少需要指定其类型。

组件_单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

这里有两种常见的试图改变一个 prop 的情形:

  1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用,在后续操作中,会将这个值进行改变。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}
  1. 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

组件_非 Prop 特性

非 Prop 特性指的是,一个未被组件注册的特性。当组件接收了一个非 Prop 特性时,该特性会被添加到这个组件的根元素上。

替换/合并已有的特性

想象一下 <my-cmp> 的模板是这样的:

<input type="date" class="b" />

为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:

<my-cmp class="my-cmp"></my-cmp>

在这种情况下,我们定义了两个不同的 class 的值:

  • my-cmp,这是在组件的模板内设置好的
  • b,这是从组件的父级传入的

对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type=“text” 就会替换掉 type=“date” 并把它破坏!庆幸的是,class 和 style 特性会稍微智能一些,即两边的值会被合并起来,从而得到最终的值:my-cmp b。

禁用特性继承

如果不希望组件的根元素继承特性,那么可以在组件选项中设置 inheritAttrs: false。如:

Vue.component("my-cmp", {
  inheritAttrs: false,
  // ...
});

在这种情况下,非常适合去配合实例的 $attrs 属性使用,这个属性是一个对象,键名为传递的特性名,键值为传递特性值。

{
  required: true,
  placeholder: 'Enter your username'
}

使用 inheritAttrs: false$attrs 相互配合,我们就可以手动决定这些特性会被赋予哪个元素。如:

Vue.component("base-input", {
  inheritAttrs: false,
  props: ["label", "value"],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
  `,
});

注意:inheritAttrs: false 选项不会影响 style 和 class 的绑定。

组件_监听组件事件

首先,我们来写一个博文组件,如:

Vue.component("blog-post", {
  props: {
    post: {
      type: Object,
    },
  },
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>放大字号</button>
      <div>{{ post.content }}</div>
    </div>
  `,
});
<div id="app">
  <div :style="{fontSize: postFontSize + 'em'}">
    <blog-post v-for="post in posts" :key="post.id" :post="post"> </blog-post>
  </div>
</div>
const vm = new Vue({
  el: "#app",
  data: {
    posts: [
      { title: "标题1", content: "正文内容", id: 0 },
      { title: "标题2", content: "正文内容", id: 1 },
      { title: "标题3", content: "正文内容", id: 2 },
    ],
    postFontSize: 1,
  },
});

可以看到每一个博文组件中,都有一个按钮,可以去放大页面中字体的字号,也就是说,当点击这个按钮时,我们要告诉父组件改变postFontSize数据去放大所有博文的文本。碰见这样的情况,该如何做呢?

Vue 实例提供了一个自定义事件来解决这个问题。父组件可以像处理原生 DOM 元素一样,通过 v-on指令,监听子组件实例的任意事件,如:

<div id="app">
  <div :style="{fontSize: postFontSize + 'em'}">
    <blog-post ... @enlarge-text="postFontSize += 0.1"> </blog-post>
  </div>
</div>

那么,怎么样能够去监听到一个 enlarge-text这么奇怪的事件呢?这就需要在组件内,去主动触发一个自定义事件了。

如何触发?
通过调用 $emit 方法 并传入事件名称来触发一个事件,如:

Vue.component('blog-post', {
  props: {
    ...
  },
  template: `
    <div class="blog-post">
      ...
      <button @click="$emit('enlarge-text')">放大字号</button>
      ...
    </div>
  `,
})

这样,父组件就可以接收该事件,更新数据 pageFontSize 的值了。

使用事件抛出一个值

在有些情况下,我们可能想让 <blog-post>组件决定它的文本要放大多少。这是可以使用 $emit 的第二个参数来提供这个值,如:

Vue.component('blog-post', {
  props: {
    ...
  },
  template: `
    <div class="blog-post">
      ...
      <button @click="$emit('enlarge-text', 0.2)">放大字号</button>
      ...
    </div>
  `,
})

在父组件监听这个事件时,可以通过 $event 访问到被抛出的这个值:

<div id="app">
  <div :style="{fontSize: postFontSize + 'em'}">
    <blog-post ... @enlarge-text="postFontSize += $event"> </blog-post>
  </div>
</div>

或者,将这个事件处理函数写成一个方法:

<div id="app">
  <div :style="{fontSize: postFontSize + 'em'}">
    <blog-post ... @enlarge-text="onEnlargeText"> </blog-post>
  </div>
</div>

那么,这个值将会作为第一个参数,传入这个方法:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

事件名

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所有的名称。如果触发一个 camelCase 名字的事件:

this.$emit("myEvent");

则监听这个名字的 kabab-case 版本是不会有任何效果的。

<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

与组件和 prop 不同的是,事件名不会被当作一个 JS 变量名或者属性名,所以就没有理由使用 camelCase 或 PascalCase 了。

并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写,所以 @myEvent 将会变成 @myevent,导致 myEvent 不可能被监听到。

因此,推荐始终使用 kebab-case 的事件名

将原生事件绑定到组件

在组件上去监听事件时,我们监听的是组件的自动触发的自定义事件,但是在一些情況下,我们可能想要在一个组件的根元素上直接监听一个原生事件。这是,可以使用 v-on 指令的 .native 修饰符,如:

<base-input @focus.native="onFocus" @blur.native="onBlur"></base-input>
Vue.component("base-input", {
  template: `
    <input type="text" />
  `,
});

这样处理,在有些时候是很有用的,不过在尝试监听一个类似<input>元素时,这并不是一个好主意,例如<base-input>组件可能做了重构,如:

<label>
  姓名:
  <input type="text" />
</label>

可以看到,此时组件的根元素实际上是一个

为了解决这个问题,Vue 提供了一个$listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

{
  focus: function (event) { /* ... */ }
  blur: function (event) { /* ... */ },
}

有了这个 $listeners 属性,我们可以配合 v-on=“$listeners” 将所有的事件监听器指向这个组件的某个特定的子元素,如:

Vue.component("base-input", {
  template: `
    <label>
      名字:
      <input v-on="$listeners" />
    </label>
  `,
});

在组件上使用 v-model

由于自定义事件的出现,在组件上也可以使用 v-model 指令。

在 input 元素上使用 v-mode 指令时,相当于绑定了 value 特性以及监听了 input 事件:

<input v-model="searchText" />

等价于:

<input :value="searchText" @input="searchText = $event.target.value" />

当把 v-model 指令用在组件上时:

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

则等价于:

<base-input :value="searchText" @input="searchText = $event" />

同 input 元素一样,在组件上使用 v-model 指令,也是绑定了 value 特性,监听了 input 事件。

所以,为了让 v-model 指令正常工作,这个组件内的<input>必须:

  • 将其 value 特性绑定到一个叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
    如:
Vue.component("base-input", {
  props: ["value"],
  template: `
    <input 
      :value="value"
      @input="$emit('input', $event.target.value)"
    />
  `,
});

这样操作后,v-model 就可以在这个组件上工作起来了。

通过上面的学习,我们知道了,一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。碰到这样的情况,我们可以利用 model 选项来避免冲突:

Vue.component("base-checkbox", {
  model: {
    prop: "checked",
    event: "change",
  },
  props: {
    checked: Boolean,
  },
  template: `
    <input
      type="checkbox"
      :checked="checked"
      @change="$emit('change', $event.target.checked)"
    >
  `,
});

使用组件:

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。

.sync 修饰符

除了使用 v-model 指令实现组件与外部数据的双向绑定外,我们还可以用 v-bind 指令的修饰符 .sync 来实现。

那么,该如何实现呢?
先回忆一下,不利用 v-model 指令来实现组件的双向数据绑定:

<base-input :value="searchText" @input="searchText = $event"></base-input>
Vue.component("base-input", {
  props: ["value"],
  template: `
    <input 
      :value="value"
      @input="$emit('input', $event.target.value)"
    />
  `,
});

那么,我们也可以试着,将监听的事件名进行更改,如:

<base-input
  :value="searchText"
  @update:value="searchText = $event"
></base-input>
Vue.component("base-input", {
  props: ["value"],
  template: `
    <input 
      :value="value"
      @input="$emit('update:value', $event.target.value)"
    />
  `,
});

这样也是可以实现双向数据绑定的,那么和 .sync 修饰符 有什么关系呢?
此时,我们对代码进行修改:

<base-input :value.sync="searchText"></base-input>
Vue.component("base-input", {
  props: ["value"],
  template: `
    <input 
      :value="value"
      @input="$emit('update:value', $event.target.value)"
    />
  `,
});

所以,.sync 修饰符 本质上也是一个语法糖,在组件上使用:

<base-input :value.sync="searchText"></base-input>

等价于:

<base-input :value="searchText" @update:value="searchText = $event" />

当我们用一个对象同时设置多个 prop 时,也可以将.sync 修饰符和 v-bind 配合使用:

<base-input v-bind.sync="obj"></base-input>

注意:

  • 带有.sync 修饰符的 v-bind 指令,只能提供想要绑定的属性名,不能和表达式一起使用,如::title.sync="1+1",这样操作是无效的
  • v-bind.sync 用在 一个字面量对象上,如 v-bind.sync="{ title: 'haha' }",是无法工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

v-model VS .sync

先明确一件事情,在 vue 1.x 时,就已经支持 .sync 语法,但是此时的 .sync 可以完全在子组件中修改父组件的状态,造成整个状态的变换很难追溯,所以官方在 2.0 时移除了这个特性。然后在 vue2.3 时,.sync 又回归了,跟以往不同的是,现在的.sync 完完全全就是一个语法糖的作用,跟 v-model 的实现原理是一样的,也不容易破环院有的数据模型,所以使用上更安全也更方便。

  • 两者都是用于实现双向数据传递的,实现方式都是语法糖,最终通过 prop + 事件 来达成目的。
  • vue 1.x 的 .sync 和 v-model 是完全两个东西,vue 2.3 之后可以理解为一类特性,使用场景略微有区别
  • 当一个组件对外只暴露一个受控的状态,切都符合统一标准的时候,我们会使用 v-model 来处理。.sync 则更为灵活,凡是需要双向数据传递时,都可以去使用。

组件_插槽

和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:

<my-cmp> Something bad happened. </my-cmp>

如果有这样的需求,我们就可以通过插槽来做。

插槽内容

通过插槽,我们可以这样合成组件:

<my-cmp> 写在组件标签结构中的内容 </my-cmp>

组件模板中可以写成:

<div>
  <slot></slot>
</div>

当组件渲染时,<slot></slot>将会被替换为“写在组件标签结构中的内容”。
插槽内可以包含任何模板代码,包括 HTML 和其他组件。
如果<my-cmp>没有包含<slot>元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

编译作用域

当在插槽中使用数据时:

<my-cmp> 这是插槽中使用的数据:{{ user }} </my-cmp>

该插槽跟模板的其他地方一样可以访问相同的实例属性,也就是相同的“作用域”,而不能访问<my-cmp>的作用域。
请记住:
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

后备内容

我们可以设置默认插槽,它会在没有提供内容时被渲染,如,在<my-cmp>组件中:

Vue.compopnent("my-cmp", {
  template: `
    <button type="submit">
      <slot></slot>
    </button>
  `,
});

我们希望这个<button>内绝大多数情况下都渲染文本“Submit”,此时就可以将“Submit”作为后备内容,如:

Vue.compopnent("my-cmp", {
  template: `
    <button type="submit">
      <slot>Submit</slot>
    </button>
  `,
});

当使用组件未提供插槽时,后备内容将会被渲染。如果提供插槽,则后备内容将会被取代。

具名插槽

有时我们需要多个插槽,如<my-cmp>组件:

Vue.compopnent("my-cmp", {
  template: `
    <div class="container">
      <header>
        <!-- 页头 -->
      </header>
      <main>
        <!-- 主要内容 -->
      </main>
      <footer>
        <!-- 页脚 -->
      </footer>
    </div>
  `,
});

此时,可以在<slot>元素上使用一个特殊的特性:name。利用这个特性定义额外的插槽:

Vue.compopnent("my-cmp", {
  template: `
    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `,
});

一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<my-cmp>
  <template v-slot:header>
    <h1>头部</h1>
  </template>

  <p>内容</p>
  <p>内容</p>

  <template v-slot:footer>
    <p>底部</p>
  </template>
</my-cmp>

现在<template>元素中的所有内容都会被传入相应的插槽。任何没有被包裹在带有v-slot<template>中的内容都会被视为默认插槽的内容。
为了模板更清晰,也可以写成以下这样:

<my-cmp>
  <template v-slot:header>
    <h1>头部</h1>
  </template>

  <template v-slot:default>
    <p>内容</p>
    <p>内容</p>
  </template>

  <template v-slot:footer>
    <p>底部</p>
  </template>
</my-cmp>

注意:v-slot 只能添加在<template>上,只有一种例外情况。

作用域插槽

为了能够让插槽内容访问子组件的数据,我们可以将子组件的数据作为<slot>元素的一个特性绑定上去:

Vue.component("my-cmp", {
  data() {
    return {
      user: {
        name: "杉杉",
        age: 18,
      },
    };
  },
  template: `
    <span>
      <slot v-bind:user="user"></slot>
    </span>
  `,
});

绑定在 <slot> 元素上的特性被称为插槽 prop
那么在父级作用域中,我们可以给v-slot带一个值来定义我们提供的插槽 prop 的名字:

<div id="app">
  <my-cmp>
    <template v-slot:default="slotProps"> {{ slotProps.user.name }} </template>
  </my-cmp>
</div>

独占默认插槽的缩写语法

当被提供的内容只有默认插槽时,组件的标签可以被当作插槽的模板来使用,此时,可以将v-slot直接用在组件上:

<my-cmp v-slot:default="slotProps"> {{ slotProps.user.name }} </my-cmp>

也可以更简单:

<my-cmp v-slot="slotProps"> {{ slotProps.user.name }} </my-cmp>

注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确

<!-- 无效,会导致警告 -->
<my-cmp v-slot="slotProps">
  {{ slotProps.user.name }}
  <template v-slot:other="otherSlotProps">
    slotProps 在这里是不合法的
  </template>
</my-cmp>

只要出现多个插槽,就需要为所有的插槽使用完整的基于<template>的语法。

解构插槽 Prop

我们可以使用解构传入具体的插槽 prop,如:

<my-cmp v-slot="{ user }"> {{ user.name }} </my-cmp>

这样模板会更简洁,尤其是在为插槽提供了多个 prop 时。
此外还可以有其他可能,如 prop 重命名:

<my-cmp v-slot="{ user: person }"> {{ person.name }} </my-cmp>

以及自定义后备内容,当插槽 prop 是 undefined 时生效:

<my-cmp v-slot="{ user = { name: 'Guest' } }"> {{ user.name }} </my-cmp>

动态插槽名

Vue 2.6.0 新增

<my-cmp>
  <template v-slot:[dynamicSlotName]> ... </template>
</my-cmp>

具名插槽的缩写

Vue 2.6.0 新增

v-onv-bind一样,v-slot也有缩写,将v-slot:替换为#

<my-cmp>
  <template #header>
    <h1>头部</h1>
  </template>

  <template #default>
    <p>内容</p>
    <p>内容</p>
  </template>

  <template #footer>
    <p>底部</p>
  </template>
</my-cmp>

当然,和其它指令一样,该缩写只在其有参数的时候才可用。

废弃了的语法

带有 slot 特性的具名插槽

自 2.6.0 起被废弃

<my-cmp>
  <template slot="header">
    <h1>头部</h1>
  </template>

  <template>
    <p>内容</p>
    <p>内容</p>
  </template>

  <template slot="footer">
    <p>底部</p>
  </template>
</my-cmp>

带有 slot-scope 特性的作用域插槽

自 2.6.0 起被废弃

<my-cmp>
  <template slot="default" slot-scope="slotProps">
    {{ slotProps.user.name }}
  </template>
</my-cmp>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么排除无效问卷?

目录 1、答题限制 1&#xff09;设置问卷验证码/密码 2&#xff09;设置填写唯一链接 2、题目设置 1&#xff09;设置陷阱题目 2&#xff09;打乱题目顺序 3&#xff09;设置跳转逻辑 4&#xff09;题目数量限制 3、问卷筛选 问卷调查是一种能够在进行社会调研时帮助我…

SAP ABAP发送HTML格式的邮件

输入参数&#xff1a; I_SUBJECT&#xff1a;内容的简短描述 IT_MESSAGE_BODY&#xff1a;邮件主体部分&#xff08;convert string to table&#xff09; IT_ATTACHMENTS&#xff1a;附件内容 I_SENDER_MAIL&#xff1a;发送者的电子邮件地址 I_ATTMSG_CHECK&#xff1a;…

浙大MBA/MPA/MEM复试中的五大认知误区

在每年的研究生复试过程中都不乏大意失荆州者&#xff0c;经过联考初试的洗礼走到复试环节&#xff0c;却因为录取排名的变化而止步于录取大门口。在这些案例中&#xff0c;不少考生其实是针对复试这个环节有诸多认知误区&#xff0c;本期杭州达立易考结合MBA/MEM/MPA在职类硕士…

CentOS配置静态IP

CentOS配置静态IP1.获取VMnet8虚拟网卡的网段2.编辑虚拟机虚拟网络3.虚拟机中网络配置4.验证配置1.获取VMnet8虚拟网卡的网段 通过ipconfig -all查询VMnet8网卡的网段&#xff0c;这个等会需要使用 这里是&#xff1a;192.168.177 这里注意&#xff0c;还需要记录一下宿主机&…

rust字符串

字符串类型 诸位在入门rust的时候&#xff0c;要认真&#xff0c;因为字符串类型在rust中有好几种&#xff0c;一不小心就搞混了类型&#xff0c;导致代码编译报错。好在有强大的rust-analyzer和vscode帮助我们。我们直接通过一段代码来开始认识rust的字符串类型。 fn main()…

nginx学习笔记5(小d课堂)

全局异常兜底数据返回 我们如果访问的路径不存在&#xff0c;会给我们返回一个404.那么我们就可以通过nginx配置去返回兜底数据&#xff1a; 修改完配置&#xff0c;重启nginx。 这样我们就返回我们的兜底数据了。 nginx封禁恶意ip 我们现在随便选择一个ip进行封禁测试。 我们现…

.Net Core 6.0 WebApi配置跨域

ASP.Net Core 6.0 WebApi配置跨域 Program.cs文件代码如下&#xff1a; //配置跨域 builder.Services.AddCors(cor > {var cors configuration.GetSection("CorsUrls").GetChildren().Select(p > p.Value);cor.AddPolicy("Cors", policy >{poli…

92、【树与二叉树】leetcode ——111. 二叉树的最小深度:层次遍历+先序DFS+后序DFS[子问题分解](C++版本)

题目描述 原题链接&#xff1a;111. 二叉树的最小深度 解题思路 1、迭代法&#xff1a;层序遍历BFS 最小深度的特点是第一次遍历到结点的左右指针为NULL&#xff0c;此时该路径为最短路径。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* …

探索SpringMVC-组件之HandlerExceptionResolver

前言 在介绍完Handler、HandlerAdapter、HandlerMapping之后&#xff0c;剩下的比较关键的组件就是HandlerExceptionResolver、ViewResolver。其他的像国际化、主题、文件上传、重定向&#xff0c;这些锦上添花的组件都是一个框架需要关心的。但不是我们平常使用的核心功能&am…

蓝桥杯省赛习题练习(三)

题目来源&#xff1a;2022年第十三届省赛(B组)真题 目录1.九进制转十进制运行结果2. 顺子日期运行结果3. 刷题统计运行结果4. 积木画1.九进制转十进制 问题描述&#xff1a;九进制正整数 (2022)9 转换成十进制等于多少&#xff1f; #include<stdio.h> #include<math.h…

一文带你深入了解线程池

目录一. 什么是线程池二. 为什么要使用线程池三. 线程池的参数四. 线程池的工作流程五. 使用Executors 创建常见的功能线程池一. 什么是线程池 简单来说&#xff0c;线程池就是提前创建好一批线程&#xff0c;当有任务的时候&#xff0c;从池子中取出一个线程去执行该任务&…

再说多线程(二)——细说Monitor类

在上一节我们已经讨论了使用Lock来保证并发程序的一致性&#xff0c;Lock锁是借助了Monitor类的功能。本节将详细的介绍Monitor类&#xff0c;以及如何通过Monitor类的成员函数实现并行程序的一致性。1.Monitor类介绍根据微软的说法&#xff0c;C#中的监视器类提供了一种同步对…

Microsoft Visual SourceSafe的使用

1、介绍 Microsoft Visual SourceSafe&#xff0c;简称vss。是一款早期微软推出的版本管理工具。跟据官方的定义&#xff0c;vss有两种控制模式&#xff1a;独占&#xff08;Lock-Modify-Unlock Model&#xff09;和并行&#xff08;Copy-Modify-Merge Model&#xff09;。独占…

程序的安装——软件安装包的制作、软件源的使用

读书笔记 —— 《嵌入式C语言自我修养》 软件安装 linux 安装包的制作 编译 软件安装包路径 使用dpkg命令来制作安装包 及 安装包的卸载 软件仓库 更新源 查看具体需要更新的软件包 更新软件包 软件安装 软件安装的过程其实就是将一个可执行文件安装到ROM的过…

安全狗云原生安全从1.X到2.X的演变之路(1)

随着云计算技术的蓬勃发展&#xff0c;传统上云实践中的应用升级缓慢、架构臃肿、无法快速迭代等“痛点”日益明显。能够有效解决这些“痛点”的云原生技术正蓬勃发展&#xff0c;成为赋能业务创新的重要推动力&#xff0c;并已经应用到企业核心业务。然而&#xff0c;云原生技…

大型数据中心分层分布式谐波治理方案设计与效果分析

摘要&#xff1a;数据中心行业在国民经济中起到了不可替代的作用,但其繁多的非线性电力负载,如通讯系统、大型计算机、网络控制设备、变频空调、各种数码办公设备、灯光调控系统、UPS、监控系统等给其供电系统带来了严重的谐波干扰,对大型数据中心的运行安全造成了较大的威胁,为…

200:vue+openlayers 添加删除多边形,modify feature,双向互动颜色显示

第200个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中添加删除多边形,每绘制一个,左侧列表出一个信息。 hover左侧文字,右边地图上红色显示图形,点击选中右侧地图上某feature,变成蓝色高亮,同时左侧也会蓝色显示,做到双向互动。 高亮显示某feature,…

如何删除图片数据中的重复数据

我们在工作中经常这种情况&#xff0c;leader给你一堆数据&#xff0c;让你用这些没有清洗过的数据完成项目。痛苦的是&#xff0c;这批数据居然存在很多重复的样本。那如何删除这些冗余数据呢&#xff1f;imagehash库非常好用。 github地址&#xff1a;https://github.com/ch…

络达开发----如何开启DMIC

芯片型号&#xff1a;AB1565 功能模块&#xff1a;数字MIC接口的使用 AB1656评估板上支持两路数字MIC&#xff0c;分别为DMIC0和DMIC1&#xff0c;如果图1所示&#xff0c;分别 可以由GPIO_2/3/4/5/13/14/15/16来当数字MIC的接口。 图1&#xff1a;支持DMIC的IO口但是评估板上…

MAC M1使用Rosetta安装python3.6

在使用网上提到的brew和pyenv安装的时候&#xff0c;我的电脑总会报BUILD FAILED错误。 找了一天才找到解决办法&#xff0c;真的十分痛苦&#xff0c;特此记录一下&#xff0c;让别的小伙伴也不再迷茫。 解决办法参考网址&#xff1a;click here&#xff08;需要VPN&#xff…