本章将继续和大家分享Vue的一些基础知识 。话不多说 ,下面我们直接上代码:
本文内容大部分摘自Vue的官网:https://v2.cn.vuejs.org/v2/guide/
首先我们先来看一下Demo的目录结构 ,如下所示:
一 、侦听器
二 、Vue组件基础
自定义组件<button-counter> 代码如下:
define([
axios
], function (axios) {
/*
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项 ,例如 data 、computed 、watch 、methods 以及生命周期钩子等 。
仅有的例外是像 el 这样根实例特有的选项 。
*/
return {
template: <button v-on:click="count++">You clicked me {{ count }} times.</button>,
props: [],
//一个组件的 data 选项必须是一个函数 ,因此每个实例可以维护一份被返回对象的独立的拷贝
data: function () {
return {
count: 0
}
},
mounted: function () {
},
methods: {
},
watch: {
}
};
});
自定义组件 <blog-post> 代码如下:
define([
axios
], function (axios) {
/*
因为组件是可复用的 Vue 实例 ,所以它们与 new Vue 接收相同的选项 ,例如 data 、computed 、watch 、methods 以及生命周期钩子等 。
仅有的例外是像 el 这样根实例特有的选项 。
*/
return {
//每个组件必须只有一个根元素
template: `
<div class="blog-post" desc="根元素">
<h3>{{ post.title }}</h3>
<p>子组件中的titleNew:<input type="text" v-model="titleNew"></p>
<slot></slot>
<button @click="handleEnlargeText">Enlarge text</button>
<div v-html="post.content"></div>
</div>
`,
/*
1 、通过 Prop 向子组件传递数据 。
2、一个组件默认可以拥有任意数量的 prop ,任何值都可以传递给任何 prop 。
在上述模板中 ,你会发现我们能够在组件实例中访问这个值 ,就像访问 data 中的值一样 。
3 、所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中 ,但是反过来则不行 。
这样会防止从子组件意外变更父级组件的状态 ,从而导致你的应用的数据流向难以理解。
*/
props: [post, title],
//一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
data: function () {
return {
enlargeFontSize: 0.1, //需要放大字体的大小
titleNew: this.title, //初始值为props中父组件传递过来的值
}
},
mounted: function () {
},
methods: {
//处理放大文本字体
handleEnlargeText: function () {
var _this = this;
//子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个父组件的事件
//第二个参数为调用父组件事件所需传的参数
//enlarge-text为自定义事件
_this.$emit(enlarge-text, _this.enlargeFontSize);
}
},
watch: {
//监听器完整写法
title: {
handler(newValue, oldValue) {
this.titleNew = newValue;
},
//deep: true, // 深度监听
immediate: true // 强制立即执行回调(一般用于父组件向子组件动态传值时)
},
//监听器简写 ,当需要设置 deep 或者 immediate 时需使用完整写法
titleNew: function (newValue, oldValue) {
/*
注意在 JavaScript 中对象和数组是通过引用传入的 ,所以对于一个数组或对象类型的 prop 来说,
在子组件中改变变更这个对象或数组本身将会影响到父组件的状态 。
这种情况下就不需要以 update:myPropName 的模式触发更新事件了 。
*/
this.$emit(update:title, newValue); //更新父组件title属性绑定的值
/*
自定义事件 .sync 修饰符:
在有些情况下 ,我们可能需要对一个 prop 进行“双向绑定 ”。
不幸的是 ,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件 ,且在父组件和子组件两侧都没有明显的变更来源 。
这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之 。
举个例子 ,在一个包含 title prop 的假设的组件中 ,我们可以用以下方法表达对其赋新值的意图:
this.$emit(update:title, newTitle)
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event">
</text-document>
为了方便起见 ,我们为这种模式提供一个缩写 ,即 .sync 修饰符:
<text-document :title.sync="doc.title"></text-document>
*/
}
}
};
});
Vue组件基础.html 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue组件基础</title>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<div :style="{ fontSize: postFontSize + em }">
<blog-post v-for="post in posts" :key="post.id" :title.sync="post.title" :post="post"
v-on:enlarge-text="onEnlargeText">
<!-- 默认插槽的内容 -->
<template v-slot:default>
<p>父组件中的post.title:<input type="text" v-model="post.title" /></p>
</template>
</blog-post>
</div>
</div>
<script src="/js/lib/require.js"></script>
<script src="/js/common/require_config.js"></script>
<script src="/js/ComponentsDemo.js"></script>
</body>
</html>
其中 ComponentsDemo.js 代码如下:
//Vue组件基础
require([../common/base, ../components/blogPost], function (base, blogPost) {
let axios = base.axios;
var vm = new base.vue({
el: #app, //挂载点
mixins: [base.mixin], //混入 ,类似基类的概念
components: {
blog-post: blogPost //局部注册组件 ,注意局部注册的组件在其子组件中不可用 。
},
data: {
posts: [
{ id: 1, title: My journey with Vue },
{ id: 2, title: Blogging with Vue },
{ id: 3, title: Why Vue is so fun }
],
postFontSize: 1
},
//created钩子函数
created: function () {
console.log(This is index created);
},
//mounted钩子函数
mounted: function () {
console.log(This is index mounted);
},
//方法
methods: {
//放大文本
onEnlargeText: function (enlargeFontSize) {
var _this = this;
_this.postFontSize += enlargeFontSize
}
}
});
});
其中require_config.js 代码如下:
//主要用来配置模块的加载位置(设置短模块名)
require.config({
baseUrl: /js/lib, //设置根目录
paths: { //如果没有设置根目录则需要填写完整路径
vue: vue,
axios: axios,
jquery: jquery-3.6.3,
//paths还有一个重要的功能 ,就是可以配置多个路径 ,如果远程cdn库没有加载成功,可以加载本地的库 ,如下:
//jquery: [http://libs.baidu.com/jquery/2.0.3/jquery, /js/lib/jquery-3.6.3],
}
});
其中base.js 代码如下:
//define用来自定义模块
//第一个参数:加载依赖模块 ,可以是require_config中定义的短模块名,也可以是完整的模块路径(去掉.js后缀名)
//第二个参数:执行加载完后的回调函数
define([vue, axios, ../components/buttonCounter], function (vue, axios, buttonCounter) {
//TODO 此处可以处理一些公共的逻辑
//vue.component(component-a, { /* ... */ }); //全局注册组件
//vue.mixin({...}); //全局混入
/*
定义组件名的方式有两种:
1 、使用 kebab-case (短横线分隔命名)
当使用 kebab-case (短横线分隔命名) 定义一个组件时 ,你也必须在引用这个自定义元素时使用 kebab-case ,例如 <my-component-name>
2、使用 PascalCase (首字母大写命名)
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用 。
也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的 。
注意 ,尽管如此 ,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的 。
*/
//Vue.component(...) 的第一个参数为组件名 。
vue.component(button-counter, buttonCounter); //全局注册
return {
vue: vue,
axios: axios,
//Vue混入
mixin: {
//数据
data: function () {
return {
domain: , //域名
}
},
//组件
components: {
},
//created钩子函数
created: function () {
console.log(This is base created);
},
//mounted钩子函数
mounted: function () {
console.log(This is base mounted);
},
//方法
methods: {
//测试
doTest: function () {
console.log(This is base doTest);
},
//获取域名
getDomain: function () {
var _this = this;
_this.domain = https://www.baidu.com;
},
}
},
};
});
运行结果如下:
三 、组件注册
1 、组件名大小写
定义组件名的方式有两种:
1)使用 kebab-case
Vue.component(my-component-name, { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时 ,你也必须在引用这个自定义元素时使用 kebab-case ,例如<my-component-name> 。
2)使用 PascalCase
Vue.component(MyComponentName, { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时 ,你在引用这个自定义元素时两种命名法都可以使用 。也就是说<my-component-name>和<MyComponentName>都是可接受的 。注意 ,尽管如此 ,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的 。
2、全局注册
到目前为止 ,我们用过Vue.component来创建组件:
Vue.component(my-component-name, {
// ... 选项 ...
})
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中 。
3 、局部注册
全局注册往往是不够理想的 。比如 ,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了 ,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加 。
在这些情况下 ,你可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在components选项中定义你想要使用的组件:
new Vue({
el: #app,
components: {
component-a: ComponentA,
component-b: ComponentB
}
})
对于components对象中的每个 property 来说,其 property 名就是自定义元素的名字 ,其 property 值就是这个组件的选项对象 。
注意局部注册的组件在其子组件中不可用。例如 ,如果你希望ComponentA在ComponentB中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
component-a: ComponentA
},
// ...
}
四 、组件中的Prop
1 、Prop 的大小写 (camelCase vs kebab-case)
HTML 中的 attribute 名是大小写不敏感的 ,所以浏览器会把所有大写字符解释为小写字符 。这意味着当你使用 DOM 中的模板时 ,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component(blog-post, {
// 在 JavaScript 中是 camelCase 的
props: [postTitle],
template: <h3>{{ postTitle }}</h3>
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
重申一次 ,如果你使用字符串模板 ,那么这个限制就不存在了 。
2 、Prop 类型
到这里 ,我们只看到了以字符串数组形式列出的 prop:
props: [title, likes, isPublished, commentIds, author]
但是 ,通常你希望每个 prop 都有指定的值类型 。这时 ,你可以以对象形式列出 prop ,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
这不仅为你的组件提供了文档 ,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户 。
3 、单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行 。这样会防止从子组件意外变更父级组件的状态 ,从而导致你的应用的数据流向难以理解 。
额外的 ,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值 。这意味着你不应该在一个子组件内部改变 prop 。如果你这样做了 ,Vue 会在浏览器的控制台中发出警告 。
这里有两种常见的试图变更一个 prop 的情形:
1)这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
在这种情况下 ,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: [initialCounter],
data: function () {
return {
counter: this.initialCounter
}
}
2)这个 prop 以一种原始的值传入且需要进行转换 。
在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: [size],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的 ,所以对于一个数组或对象类型的 prop 来说 ,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态 。
五 、自定义事件
1 、事件名
不同于组件和 prop ,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称 。举个例子 ,如果触发一个 camelCase 名字的事件:
this.$emit(myEvent)
则监听这个名字的 kebab-case 版本是不会有任何效果的:
<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>
不同于组件和 prop ,事件名不会被用作一个 JavaScript 变量名或 property 名 ,所以就没有理由使用 camelCase 或 PascalCase 了 。并且v-on事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的) ,所以v-on:myEvent将会变成v-on:myevent——导致myEvent不可能被监听到。
因此 ,我们推荐你始终使用 kebab-case 的事件名 。
2 、.sync 修饰符
在有些情况下 ,我们可能需要对一个 prop 进行“双向绑定 ” 。不幸的是,真正的双向绑定会带来维护上的问题 ,因为子组件可以变更父组件 ,且在父组件和子组件两侧都没有明显的变更来源 。
这也是为什么我们推荐以update:myPropName的模式触发事件取而代之 。举个例子,在一个包含titleprop 的假设的组件中 ,我们可以用以下方法表达对其赋新值的意图:
this.$emit(update:title, newTitle)
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property 。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
为了方便起见 ,我们为这种模式提供一个缩写,即.sync修饰符:
<text-document v-bind:title.sync="doc.title"></text-document>
六 、插槽
1、具名插槽
有时我们需要多个插槽 。例如对于一个带有如下模板的<base-layout>组件:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
对于这样的情况 ,<slot>元素有一个特殊的 attribute:name 。这个 attribute 可以用来定义额外的插槽:
<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的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Heres some contact info</p>
</template>
</base-layout>
现在<template>元素中的所有内容都将会被传入相应的插槽 。
最终渲染结果如下所示:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Heres some contact info</p>
</footer>
</div>
注意 v-slot 只能添加在 <template> 上 (只有一种例外情况) ,这一点和已经废弃的 slot attribute 不同。
2 、后备内容
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的 ,它只会在没有提供内容的时候被渲染 。例如在一个<submit-button>组件中:
<button type="submit">
<slot></slot>
</button>
我们可能希望这个<button>内绝大多数情况下都渲染文本“Submit ” 。为了将“Submit ”作为后备内容 ,我们可以将它放在<slot>标签内:
<button type="submit">
<slot>Submit</slot>
</button>
现在当我在一个父级组件中使用<submit-button>并且不提供任何插槽内容时:
<submit-button></submit-button>
后备内容“Submit ”将会被渲染:
<button type="submit">
Submit
</button>
但是如果我们提供内容:
<submit-button>
Save
</submit-button>
则这个提供的内容将会被渲染从而取代后备内容:
<button type="submit">
Save
</button>
3 、动态插槽名
动态指令参数也可以用在 v-slot 上 ,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
4、具名插槽的缩写
跟v-on和v-bind一样 ,v-slot也有缩写 ,即把参数之前的所有内容 (v-slot:) 替换为字符#。例如v-slot:header可以被重写为#header:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Heres some contact info</p>
</template>
</base-layout>
七 、混入
1 、基础
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能 。一个混入对象可以包含任意组件选项 。当组件使用混入对象时 ,所有混入对象的选项将被“混合 ”进入该组件本身的选项。
例子:
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log(hello from mixin!)
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
2、选项合并
当组件和混入对象含有同名选项时 ,这些选项将以恰当的方式进行“合并 ” 。
比如,数据对象在内部会进行递归合并 ,并在发生冲突时以组件数据优先 。
var mixin = {
data: function () {
return {
message: hello,
foo: abc
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: goodbye,
bar: def
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组 ,因此都将被调用 。另外,混入对象的钩子将在组件自身钩子之前调用 。
var mixin = {
created: function () {
console.log(混入对象的钩子被调用)
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log(组件钩子被调用)
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项 ,例如methods 、components和directives ,将被合并为同一个对象 。两个对象键名冲突时 ,取组件对象的键值对 。
var mixin = {
methods: {
foo: function () {
console.log(foo)
},
conflicting: function () {
console.log(from mixin)
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log(bar)
},
conflicting: function () {
console.log(from self)
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
注意:Vue.extend()也使用同样的策略进行合并 。
3 、全局混入
混入也可以进行全局注册 。使用时格外小心!一旦使用全局混入 ,它将影响每一个之后创建的 Vue 实例 。使用恰当时 ,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 myOption 注入一个处理器 。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: hello!
})
// => "hello!"
请谨慎使用全局混入 ,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件) 。大多数情况下 ,只应当应用于自定义选项 ,就像上面示例一样。推荐将其作为插件发布 ,以避免重复应用混入 。
Demo源码:
链接:https://pan.baidu.com/s/1jc5SGf3_8qb6pZT4T-5mxA
提取码:broa
此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/17139196.html
版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改 ,谢谢!!!
声明:本站所有文章 ,如无特殊说明或标注,均为本站原创发布 。任何个人或组织 ,在未征得本站同意时 ,禁止复制 、盗用 、采集 、发布本站内容到任何网站 、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 。