Vue 学习笔记

[TOC]

简介

Vue 是一个用于构建 Web 用户界面的渐进式 JavaScript 框架。其核心库只关注视图层(view layer),同时具备良好的第三方支持库生态用以应付构建复杂大型单页应用(SPA:Single-Page Application)。

MVVM 模型

Vue 虽然没有完全遵循 MVVM(Model-View-ViewModel)模型,当其设计也受到了 MVVM 的启发,以数据驱动界面,如下图所示:

Vue MVVM

Vue 中,充当 ViewModel 的是一个 Vue 实例(new Vue({})),该 Vue实例 作用于某一个 HTML 元素上,全权代理该元素节点的所有操作。

Vue实例 内部通过 DOM Listeners 可以观测到页面上 DOM 元素的变化,从而将该种变化同步更改到 Model 中的对应数据。

同时通过 Data Bindings,当 Model 中的数据改变时,则会对相应视图上的显示进行更改,从而实现了 View 和 Model 的数据双向绑定。

:传统的 Web 编程模型是 结构驱动,即要对一个 DOM 节点进行操作,第一步就是要获取该 DOM 节点对象,然后再修改数据更新到节点上。
Vue 的中心思想是 数据驱动,要更改界面,其实就是要更改数据。
简而言之,在 Vue 中,不应当考虑操作 DOM,而是专注于 操作数据

安装

Vue 的安装有多种方法,这里主要介绍两种方法:

  1. 通过<script>标签直接引入:
<!-- 对于制作原型或学习,你可以这样使用最新版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 或者-->
<!-- 对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>

:通过<script>标签引入,Vue会被注册为一个全局变量

  1. 直接使用官方提供的快速搭建复杂单页面应用 (SPA) 的脚手架 vue-cli
  • 首先全局安装该脚手架 vue-cli
npm install -g @vue/cli
vue create <project-name>

:使用 vue-cli 前需确保系统已安装 nodejs

以上两步操作完成,我们便创建完成一个 Vue 项目。

在项目的 package.json 中,可以看到 vue-cli 提供了两个脚本命令让我们运行与打包项目:

  • npm run serve:运行项目
  • npm run build:打包项目到 dist 文件夹

组件化

Vue 的两大特性为 数据驱动组件化

通常一个大的页面可以划分为许多个小区块,这些小区块有些结构是相似的,我们可以将这些相似的区块抽象出一个统一的结构,方便复用,这种抽象结构的方法即称为组件化。

在实际项目开发中,一个大的页面通常都是由许多个小的组件构造而成的,如下图所示:

组件化

Vue 提供了两种组件定义的方式:

<body>
    <div id="app">
        <my-component />
    </div>

    <script>
        const myComponent = Vue.component('my-component',{
            data(){
                return {
                    'message': 'Hello Global Component'
                }
            },
            template:`
            <h1>{{message}}</h1>
            `
        });
        const vm = new Vue({
            el: "#app",
        });
    </script>
</body>
  • 局部组件:对于全局组件来说,即使页面没有使用该组件,组件也会被注入到最终的构建结果中,导致了 JavaScript 文件的无谓增加。而局部组件可以做到按需加载,需要哪些组件,按需引入即可,更加灵活高效。
    定义方式:通过一个普通的 JavaScript 对象来定义组件,然后 Vue实例 按需引入需要的组件即可。
<body>
    <div id="app">
        <component-a></component-a>
        <component-b />
    </div>
    <script>
        const componentA = {
            template: `
            <h1>Component A</h1>
            `
        };
        const componentB = {
            template: `
            <h1>Component B</h1>
            `
        };

        const vm = new Vue({
            el: '#app',
            components: {  // 按需引入需要的组件
                componentA,
                componentB
            }
        });
    </script>
</body>

最佳实践:在 Vue 中,组件通常都定义到一个单独的.vue文件中,其他组件需要时,导入相应组件的.vue文件即可。

// MyComponent.vue
<template>
  <h1>{{message}}</h1>
</template>

<script>
export default {
  name: "MyCompoent",
  data() {
    return {
      message: "Hello MyComponent!"
    };
  }
};
</script>

<style scoped>
h1 {
  background: red;
}
</style>

可以直接使用以下命令直接运行.vue文件,查看组件展示效果:

vue serve MyComponent.vue --open

也可以在其他组件内导入该组件,进行使用:

<template>
  <div id="app">
    <h1>Parent Component</h1>
    <!-- 使用组件 -->
    <my-component />
  </div>
</template>

<script>
// 导入组件
import MyComponent from "./MyComponent.vue";

export default {
  name: "app",
  components: {
    MyComponent // 引入组件
  }
};
</script>

:在 Vue 中,组件实质是带有一个名字的 Vue实例,其性质与 Vue实例 基本一致(遵循 Vue实例 的生命周期等内容),特点是多了个组件复用功能。

Vue实例

Vue 实例充当 ViewModel 角色,负责 View 和 Model 之间的数据绑定:

new Vue(Options)

当创建一个 Vue 实例时,你可以传入一个选项对象Options,该Options的选项列表有如下可选:

下面列举一些Options常用选项

  • 选项 / 数据data
    描述:Vue 实例的数据对象,用于数据的存储与显示。
    类型:Object | Function
<body>
    <div id="app">
        <h1>{{message}}</h1>
    </div>
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                message: "Hello Vue!"
            }
        });
    </script>
</body>

:Vue 将会递归将data的属性转换为getter/setter,从而让data的属性能够响应数据变化。比如在控制台输入vm.message = 'Hi Vue!!!,可以观察到页面数据发生了更改。

组件 中的data属性必须是Fcuntion类型,其返回一个Object,原因是组件复用时,保证每个新组件都有独一的一份数据拷贝。

// MyComponent.vue
<template>
    <h1>{{message}}</h1>
</template>

<script>
export default {
    name: 'MyComponent',
    data(){       // 函数类型
        return {  // 返回数据对象
            'message': 'Hello Vue!'
        }
    }
}
</script>
  • 选项 / 数据props
    描述:该属性用于接收来自父组件的数据。
    类型:Array<string> | Object
    当传递的是Object类型时,则可以基于对象的语法使用以下选项:
    type:指定数据类型,该值可以为原生类型(StringNumberBooleanArrayObjectDateFunctionSymbol),自定义构造函数,或上述内容组成的数组。
    default:any:为该prop指定一个默认值。如果该prop没有被传入,则使用该默认值。对象或数组的默认值必须从一个工厂函数返回。
    required: Boolean:定义该prop是否为必填项。
    validator: Function:自定义验证函数,对该prop进行校验。
Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})
  • 选项 / 数据propsData
    描述:创建实例时传递props。主要作用是方便测试。
    类型:{ [key: string]: any }
var Comp = Vue.extend({
  props: ['msg'],
  template: '<div>{{ msg }}</div>'
})

var vm = new Comp({
  propsData: {
    msg: 'hello'
  }
})

propsData属性只能用于new创建的实例中。

  • 选项 / 数据methods
    描述:方法定义,Vue实例 可以直接访问这些方法,或在指令表达式中直接调用这些方法。
    类型:{ [key: string]: Function }
var vm = new Vue({
  data: { a: 1 },
  methods: {
    plus: function () {
      this.a++
    }
  }
})
vm.plus()
vm.a // 2

methods中的this自动绑定到当前 Vue实例。

  • 选项 / 数据computed
    描述:计算属性,主要用于对data数据进行计算转换。
    类型:{ [key: string]: Function | { get: Function, set: Function } }
<template>
  <div>
    <h1>获取数据: {{computedData}}</h1>
    <h1>设置数据:{{setData = 3}}</h1>
    <h1>获取数据: {{setData}}</h1>
  </div>
</template>

<script>
export default {
  name: "Computed",
  data() {
    return {
      count: 1
    };
  },
  computed: {
    // 只读取 data
    computedData() {
      return this.count + 10;
    },
    // 读取和设置 data
    setData: {
      get() {
        return this.count + 1;
      },
      set(value) {
        this.count = value;
      }
    }
  }
};
</script>

computed类型为Object,其具有如下特点:

  1. computed内部定义的属性为访问器属性,即具备gettersetter,且其内部this自动绑定到当前 Vue实例。
  2. computed会自动 缓存 计算结果,只有当依赖的响应式属性变化时,computed才会重新进行计算。
    缓存computedmethods的最大区别之处,methods每次调用一定会运行函数,而computed则不一定。
  • 选项 / 数据watch
    描述:侦听属性,用于监控datacomputed的数据,当数据变更时进行回调通知。
    类型:{ [key: string]: string | Function | Object | Array }
    watch类型为Object,其内部属性的类型有多种:string | Function | Object | Array,这里简单介绍 3 种:
  1. string:字符串表示回调函数名,当数据改变时,回调该函数:
const vm = new Vue({
    el: '#app',
    data: {
        a: 1
    },
    methods: {
        aChanged(value, oldValue) {
            console.log(`a changed: new:${value} --> old:${oldValue}`);
        }
    },
    watch: {
        a: 'aChanged'
    }
});
  1. Function:当数据改变时,直接回调该函数:
const vm = new Vue({
    el: '#app',
    data: {
        a: 1
    },
    watch: {
        a(value, oldValue) {
            console.log(`a changed: new:${value} --> old:${oldValue}`);
        }
    }
});
  1. Object:对监控的属性为对象时,Vue 默认只能监控到对象重新被赋值的变化,而如果需要监听对象内部属性的变化,则可使用该选项,其中:
    handler代表回调函数。
    deep用来控制监听对象属性的层级,deep=true时只要对象内部 property 改变(不管嵌套有多深),都会监听到。
    immediate用来设置是否立即产生回调。当immediate=true时,回调函数会立即被调用,传递的是属性当前的值。
const vm = new Vue({
    el: '#app',
    data: {
        a: {
            aa: {
                aaa: 3
            }
        }
    },
    watch: {
        a: {
            handler(value, oldValue) {
                console.log(`a changed: new:${value} --> old:${oldValue}`);
            },
            deep: true // 被监听对象的 property 改变时被调用,无论嵌套的有多深
        }
    }
});

:大多数情况下,观察和响应数据变更使用计算属性(computed)便足够了,但是当在数据变化时需要执行异步或开销较大的操作时,则此时使用侦听属性(watch)会更加适合。

  • 选项 / DOMel
    描述:设置 Vue实例 的挂载目标节点
    类型:string | Element
new Vue({
    el: '#app'
});

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

  • 选项 / DOMtemplate
    描述:字符串模板,模板会 替换 挂载的元素。
    类型:string
<div id="app"></div>

const vm = new Vue({
    el: '#app',
    template: '<h1>template</h1>' // <div> 会被 <h1> 完全覆盖
})
  • 选项 / DOMrender
    描述:字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个createElement方法作为第一个参数用来创建VNode
    类型:(createElement: () => VNode) => VNode
<div id="app"></div>

new Vue({
    render(createElement) {
        return createElement('div', {
            class: 'rendered'
        },
            [
                createElement('h1', {
                    domProps: {
                        innerHTML: 'div>h1 rendered by vue'
                    }
                })
            ]
        );
    }}).$mount('#app');

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML,只有在一些特殊场景下,比如模板冗长且具备重复元素,则此时使用渲染函数render通过编写 JavaScript 代码来渲染出页面会更加方便简洁。

生命周期

每个 Vue实例 在挂载到页面时,都会经历一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。在创建 Vue实例 的这整个过程中,Vue 为我们预留出了一些 Hook 点,方便我们在 Vue实例 创建过程的某个生命周期中进行一些操作。如下图所示:

vue lifecycle

:图片来源于网上,侵删。

这些预留的生命周期钩子函数总共有如下几个:

  • beforeCreate:Vue实例 初始化之后,此时datamethods中的数据还未进行初始化,因此无法获取。

  • created:表示 Vue实例 创建完成,但还未挂载到页面上,此时datamethods都已经初始化成功,可以对其进行调用获取,而挂载阶段未开始,所以$el属性目前不可见。

  • beforeMount:在挂载开始之前被调用,此时模板已在内存中被编译完成,只是尚未挂载到页面上,因此,此时页面上显示的还是未渲染的结构。

  • mounted:挂载完成,此时页面会显示我们渲染的视图。如果想要操作页面上的 DOM 节点,最早的时间就是该处。
    mounted不会 承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick替换掉mounted

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}
  • beforeUpdate:数据更新时调用,此时内存中data数据已更新,但页面中显示的数据还未更新,数据与页面不同步。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

  • updated:新数据成功渲染到页面,此时数据与页面处于同步状态。
    updated不会 承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick替换掉mounted

  • activated:激活状态,表示当前组件处于前台页面,用户可与该组件进行交互。
    :只有组件被内置组件keep-alive包裹时,该钩子才有可能被调用。

<template>
    <keep-alive>
        <my-component />
    </keep-alive>
</template>
  • deactivated:停用状态,表示当前组件处于后台页面,用户不能与之交互。
    :只有组件被内置组件keep-alive包裹时,该钩子才有可能被调用。

  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用,即此时实例的datamethods等所有数据完全可用。

  • destroyed:Vue 实例销毁后调用。此时 Vue实例 指向的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。

指令

指令 (Directives) 是带有v-前缀的特殊属性。

Vue 提供了以下内置的指令:

  • v-text:更新元素的textContent,该指令与使用{{ Mustache }}插值效果一样。
    类型:string
<span v-text="msg"></span>
<!-- 和下面的一样 -->
<span>{{msg}}</span>
  • v-html:更新元素的innerHTML
    类型:string
<template>
  <div id="app">
    <div v-html="html"></div>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      html: '<a href="https://www.baidu.com" >baidu</a>'
    };
  },
};
</script>

v-html的内容只会按普通 HTML 插入,不会作为 Vue 模板进行编译。
:在单文件组件里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。
:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用v-html,永不用在用户提交的内容上。

  • v-show:条件渲染,根据表达式之真假值,切换元素的displayCSS 属性。
    类型:any
<h1 v-show="ok">Hello!</h1>

v-show不支持<template>元素,也不支持v-else

  • v-if:条件渲染,根据表达式的值的真假条件渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。如果元素是<template>,将提出它的内容作为条件块。
    类型:any
<h1 v-if="awesome">Vue is awesome!</h1>

:当和v-if一起使用时,v-for的优先级比v-if更高。
v-show = false时只是把元素设置为:display:none,元素还留着 DOM 树上。
v-if = false时,元素会被整个移除,其上绑定的数据/组件都会被销毁。

  • v-elsev-ifv-if-else的分支。
    类型:无
<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
  • v-for:遍历源数据,渲染元素列表。
    类型:Array | Object | number | string | Iterable
<div v-for="item in items">
  {{ item.text }}
</div>
<!-- 另外也可以为数组索引指定别名 (或者用于对象的键):-->
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div>

v-for渲染元素时,默认使用“就地更新”策略,即当列表数据改变时,Vue 不会移动当前 DOM 元素来重新匹配数据项,而是根据索引位置重新渲染数据。比如:

现在我们有数据项:

data() {
    return {
        datas: [
            { id: 1, name: "one" },
            { id: 2, name: "two" },
            { id: 3, name: "three" }
        ]
    }

将这些数据项渲染到页面上:

<template>
  <div>
    <ul>
      <li v-for="(item,index) in datas">
        <input type="checkbox" />
        {{item.name}}
      </li>
    </ul>
</template>

我们使用v-for将每条数据项渲染到一个<li>上,此时显示效果如下:

如果此时我们勾选第一个<li>checkbox,即one勾选上,然后再往数据列表前面添加一个数据:this.datas.unshift({ id: 4, name: "four" }),则可以看到显示效果如下:

unshift

可以看到,我们想要的是one被勾选了,但是效果是数据列表首位被勾选。出现这种现象的原因就是v-for默认采用的“就地更新”策略:它会复用已渲染完成的 DOM 元素,然后只对变化的数据进行修改,比如这里复用了第一条<li><checkbox>one</li>,添加数据项,对第一条<li>来说,他的数据改变了,但是<checkbox>不包含在数据项里,因此只会修改数据,将one修改为four,而checkbox仍保持勾选状态。

因此,“就地更新”策略是高效的,但是 只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值) 的列表渲染输出

而要解决上述问题,只需为v-for提供一个key属性(key必须是唯一的),这样 Vue 就可以识别出数据项对应的渲染条目,从而重用和重新排序现有元素:

<template>
  <div>
    <ul>
      <li v-for="(item,index) in datas" :key="item.id">
        <input type="checkbox" />
        {{item.name}}
      </li>
    </ul>
</template>

由于新添加的数据id=4,当前已存在的<li>没有与之对应的标识key,因此 Vue 会重新渲染一个新的<li>,并将其与id=4对应起来,结果如下图所示:

v-for key

:“就地更新”策略其实就是使用索引作为节点标识,即:key=index

  • v-on:绑定事件监听器。
    缩写:@
    类型:Function | Inline Statement | Object
    参数:event
    修饰符:
    .stop:调用event.stopPropagation(),停止事件分发。
    .prevent:调用event.preventDefault(),取消事件的默认动作。
    .capture:添加事件侦听器时使用capture(捕获)模式。
    .self:只有当事件是从侦听器绑定的元素本身触发时才触发回调。
    .{keyCode | keyAlias}:只有当事件是从特定键触发时才触发回调。
    .native:监听组件根元素的原生事件。
    .once:只触发一次回调。
    .left: 只有当点击鼠标左键时触发。
    .right: 只有当点击鼠标右键时触发。
    .middle: 只有当点击鼠标中键时触发。
    .passive:以{ passive: true }模式添加侦听器
<!-- 方法处理器 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>

<!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 动态事件缩写 (2.6.0+) -->
<button @[event]="doThis"></button>

<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认行为 -->
<button @click.prevent="doThis"></button>

<!-- 阻止默认行为,没有表达式 -->
<form @submit.prevent></form>

<!--  串联修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 键修饰符,键别名 -->
<input @keyup.enter="onEnter">

<!-- 键修饰符,键代码 -->
<input @keyup.13="onEnter">

<!-- 点击回调只会触发一次 -->
<button v-on:click.once="doThis"></button>

<!-- 对象语法 (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

v-on用在普通元素上时,只能监听 原生 DOM 事件。用在自定义组件上时,也可以监听子组件触发的自定义事件

<my-component @my-event="handleThis"></my-component>

<!-- 内联语句 -->
<my-component @my-event="handleThis(123, $event)"></my-component>

<!-- 组件中的原生事件 -->
<my-component @click.native="onClick"></my-component>
  • v-bind:动态绑定
    缩写::
    类型:any (with argument) | Object (without argument)
    参数:attrOrProp (optional)
    修饰符
    .prop:被用于绑定 DOM 属性 (property)
    .camel:将 kebab-case 特性名转换为 camelCase(驼峰式)
    .sync:会扩展成一个更新父组件绑定值的 v-on 侦听器
<!-- 绑定一个属性 -->
<img :src="imageSrc">

<!-- 动态特性名 (2.6.0+) -->
<button :[key]="value"></button>

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">

<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 绑定一个有属性的对象 -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- 通过 prop 修饰符绑定 DOM 属性 -->
<div v-bind:text-content.prop="text"></div>

<!-- prop 绑定。“prop”必须在 my-component 中声明。-->
<my-component :prop="someThing"></my-component>

<!-- 通过 $props 将父组件的 props 一起传给子组件 -->
<child-component v-bind="$props"></child-component>

<!-- 支持绑定驼峰命名属性 -->
<svg :view-box.camel="viewBox"></svg>
  • v-model:表单控件与数据属性的双向绑定。
    修饰符
    .lazy:使用<input>change事件进行同步。
    .number:自动将字符串转为数字。
    .trim:输入首尾空格过滤。
<input type="text" v-model="message" />

<script>
export default {
  name: 'VModel',
  data() {
    return {
      message: ''
    };
  }
};
</script>
  • v-slot:插槽。
    缩写:#
    当我们定义组件的时候,有些内容可能需要由父组件传入,因此,此时可以使用插槽,预留出位置给到父组件进行自定义内容传入:
// 子组件:预留插槽
<template>
  <div>
    <h1>Son Component</h1>
    <!-- 预留插槽 -->
    <slot></slot>
  </div>
</template>

// 父组件:传入插槽内容
<template>
  <div>
    <h1>Parent Component</h1>
    <son-component>
        <h2>slot: content from Parent Component</h2>
    </son-component>
  </div>
</template>

后备内容:可以通过为<slot>内部提供默认内容,只有当父组件显示传入内容时,才会覆盖默认内容:

<slot>
    <h1>Default Content</h1>
</slot>

具名插槽:我们可以给插槽进行命名(使用name属性),这样父组件就可指定名字(使用v-slot指令)对特定的插槽进行覆盖:

// 子组件模板
<template>
  <div>
    <h1>Son Component</h1>
    <!-- 预留命名插槽 -->
    <slot name="header"></slot>
    <main>
      <!-- name="default" -->
      <slot></slot>
    </main>
    <slot name="footer"></slot>
  </div>
</template>

// 父组件
<template>
  <div>
    <h1>Parent Component</h1>
    <son-component>
      <template v-slot:header>
        <h2>替换 header 插槽</h2>
      </template>
      <h3>替换默认插槽</h3>
      <template #footer>
        <h2>替换 footer 插槽</h2>
      </template>
    </son-component>
  </div>
</template>

v-slot只能添加在一个<template>或 组件 上。
:默认插槽其实也是一个具名插槽,其名称为:default

插槽 prop:使用 插槽 prop 可以传递子组件的数据给到父组件,使父组件可以在覆盖插槽的内容上使用子组件的数据:

// 子组件
<slot :msg="message"></slot>

<script>
export default {
  name: 'SonComponent',
  data() {
    return {
      message: 'Hello from Son Component!'
    };
  }
};
</script>

// 父组件:slotProps 接收子组件的 插槽props
<son-component #default="slotProps">
    {{slotProps.msg}}
</son-component>
  • v-pre:跳过该元素及其子元素的编译过程。可以用来显示原始 Mustache 标签。
    类型:无
<span v-pre>{{ this will not be compiled }}</span>
  • v-cloak:这个指令保持在元素上直到关联实例结束编译。通常结合 CSS 规则来达到隐藏未编译的 Mustache 标签直到实例准备完毕。
    类型:无
[v-cloak] {
  display: none;
}

<div v-cloak>
  {{ message }}
</div>
  • v-once:只渲染元素和组件 一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<span v-once>This will never change: {{msg}}</span>
  • 自定义指令:在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

Vue 提供了两种自定义指令的方式:

  1. 全局指令:使用Vue.directive
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {...})
  1. 局部指令:组件中定义一个directives属性:
// 注册一个局部自定义指令 `v-focus`
directives: {
  focus: {...}
}
  • 钩子函数:一个指令定义对象可以提供如下几个钩子函数 (均为可选):
    bind:指令第一次绑定到元素时调用。改钩子只会被调用一次,可在此做一些初始化设置。
    inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
    update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
    componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    unbind:指令与元素解绑时调用。改钩子只会被调用一次,可在此做一些资源释放操作。

示例:使用自定义指令v-customtext模拟v-text

<template>
  <h1 v-customtext="msg"></h1>
</template>

<script>
export default {
  name: 'customeDirective',
  data() {
    return {
      msg: 'Hello Custom Directives!'
    };
  },
  directives: {
    customtext: {
      inserted(el, binding, vnode, oldVnode) {
        el.innerText = binding.value;
      }
    }
  }
};
</script>

其他

  • 组件间通信:

父传子:子组件通过props属性可接收父组件传递过来的变量:

// ParentComponent.vue
<template>
  <son-component :msg="message" />
</template>

<script>
import SonComponent from './SonComponent';

export default {
  name: 'ParentComponent',
  components: {
    SonComponent
  },
  data() {
    return {
      message: 'data from Parent Component'
    };
  }
};
</script>

// SonComponent.vue
<template>
  <h1>{{msg}}</h1>
</template>

<script>
export default {
  name: 'SonComponent',
  props: {
    msg: {
      type: String,
      required: true
    }
  }
};
</script>

子传父:子组件可以通过$emit发送自定义事件向父组件传值,父组件直接注册接收该事件即可:

// SonComponent.vue
<template>
  <button @click="sendEvent">点击发送事件</button>
</template>

<script>
export default {
  name: 'SonComponent',
  methods: {
    sendEvent() {
      // 发送自定义事件
      this.$emit('eventFromChild', 'data from Son Component!!');
    }
  }
};
</script>

// ParentComponent.vue
<template>
  <div>
    <!-- 接收事件 -->
    <son-component @eventFromChild="recvChildEvent" />
    <h1>{{data}}</h1>
  </div>
</template>

<script>
import SonComponent from './SonComponent';

export default {
  name: 'ParentComponent',
  components: {
    SonComponent
  },
  data() {
    return {
      data: 'hhhh'
    };
  },
  methods: {
    recvChildEvent(data) {
      this.data = data;
    }
  }
};
</script>

父传子孙:父组件/祖先组件通过provide提供变量,子孙组件通过inject来接收该变量:

// ParentComponent
import SonComponent from './SonComponent.vue'
export default {
    name: 'ParentComponent',
    components: {
        SonComponent
    },
    provide: {
        message: 'data from Parent Component'
    }
}
// SonComponent
<template>
    <h1>{{message}}</h1>
</template>>

<script>
export default {
    name: 'SonComponent',
    inject: ['message']
}
</script>>

更多组件间通信方式,请参考:Vue组件间通信6种方式

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容