Vue 2.x#
Vue:一个渐进式的 JavaScript 框架,它的核心库只关注视图层。
1、安装#
安装方法有三个:
- 通过 cdn 引入
- 直接下载后,在
<script>
标签中引入 - NPM 安装
1.1 cdn 引入#
直接在 <script>
标签中引入 cdn 即可使用
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
1.2 直接下载#
在官网安装界面点击开发版本,即可下载 vue.js ,之后在项目中通过 <script>
标签引入
<!-- src="../vue.js" 表示的是:我将下载的 vue.js 保存在了当前文件上级目录的同级目录(resources)下 -->
<script src="../resources/vue.js"></script>
1.3 NPM 安装#
https://www.runoob.com/vue2/vue-install.html
2、初步使用(官网例子)#
这里通过几个简单的小例子了解一下 Vue
这里采用的是第二种安装方法 (直接下载) 引入
案列全部以下面这种形式展现在 HTML 页面中,之后的例子全都是 body 标签中内代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!-- 页面渲染 -->
<!-- 引入 vue.js -->
<script src="../resources/vue.js"></script>
<!-- 使用 vue.js -->
</body>
</html>
2.1 声明式渲染#
这是最简单的一个例子,将数据显示在页面上
Vue 会将数据和 DOM 建立关联,将数据声明式的渲染进 DOM 的系统,并且它里面所有东西都是响应式的
<!-- 将数据绑定到 DOM 文本(H1) -->
<div id = "app">
<h1>{{message}}</h1>
</div>
<!-- 引入 vue 的路径 -->
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello,Vue.'
}
})
// 在 console 中对 message 进行修改:app.message = '你好';
// 此时页面中的数据会同时发生变化,这就是响应式
</script>
2.2 几个小例子#
通过 v-if 来判断在页面上是否显示某一行内容
<div id = "app">
<h1>这是第一行数据</h1>
<p v-if = "see">修改 see 可以隐藏本行内容</p>
<h1>这是第三行数据</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
see: true
}
})
// 在 console 中编写 app.see = false ,则页面上只会显示两行数据
</script>
通过 v-for 在页面上渲染一个列表类型的数据
运行后,在浏览器端可以看到一个列表
<div id = "app">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
list: ["Html","JavaScript","css"]
}
})
// 在 console 中输入:app.list.push('Vue'); 页面上的列表会同步更新
// push(),在数组最后面添加一个元素
</script>
通过 v-on 添加一个事件监听器,通过它调用 Vue 实例中定义的方法
反转页面显示的字符串
<div id = "app">
<p>{{message}}</p>
<button v-on:click="reverseMessage">反转消息</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello,Vue'
},
methods: {
reverseMessage: function(){
// split():把一个字符串分割为一个字符数组
// reverse():颠倒数组中的元素顺序,
// join():把数组中的所有元素放入一个字符串
this.message = this.message.split('').reverse().join('')
}
}
})
</script>
通过 v-model 实现表单输入和应用状态之间的双向绑定
即表单中内容发生变化时,p 标签显示的文本内容同时发生变化
<div id = "app">
<p>{{message}}</p>
<input type="text" v-model="message" />
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello,Vue'
}
})
</script>
2.3 数据和方法#
当一个 Vue 实例被创建时,它会将数据对象中的所有的属性加入到 Vue 的响应式系统中
当这些属性的值发生改变时,视图将会产生 "响应",即匹配更新为新的值
这里的注意点:当 vue 的实例被创建时,hello 中已经存在的属性才会加入到响应式系统中
如果之后可能需要添加某个属性,应该在最开始定义的时候添加并赋一个初始值 (初始值可以为空)
<script src="../resources/vue.js"></script>
<script>
// 定义一个数据对象
var hello = {
id: 1,
text: "hello",
remark: ''
}
// 将该对象添加到 vue 实例中
// 此时会将 hello 对象的所有属性添加到响应式系统中
var app = new Vue({
el: "#app",
data: hello
})
// 获得 hello 对象的 id
app.id == hello.id;
// 修改 id 的值, hello 对象中的 id 会随之变化
app.id = 2;
console.log("hello.id = ",hello.id);
// 反过来也一样
hello.id = 3;
console.log("app.id = ",app.id);
</script>
Object.freeze(xxx):阻止修改现有的数据对象,这个意思是 Vue 的实例和这个对象本身都不能修改了
具体表现在,通过 Vue 实例修改会报错,通过对象本身修改会无效
<script src="../resources/vue.js"></script>
<script>
var hello = {
id: 1,
text: "hello"
}
// 阻止 vue 的实例对 hello 的属性进行修改
// hello 本身还是可以修改的,但是修改无效,23333~
Object.freeze(hello);
var app = new Vue({
el: "#app",
data: hello
})
app.id == hello.id;
// 这里就会报错,因为不能对 hello 的属性修改了
// app.id = 2;
// console.log("hello.id = ",hello.id);
// 这里两个结果都是 1,已经无法修改
hello.id = 3;
console.log("app.id = ",app.id);
console.log("hello.id = ",hello.id);
</script>
3、生命周期#
每个 Vue 实例在被创建的时候都要经历一系列初始化的过程
如需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等
3.1 MVVM 模式#
Model -- View -- Controller
Model 层:模型层,存储数据
View 层:视图层,用户能看到的并进行交互的界面,对应的就是网页页面显示的内容
Controller 层:控制器,收集视图层用户输入的数据,将数据处理后发送到模型层,最后返回新的视图
Model -- View -- ViewModel
Model 层:模型层,主要是处理服务器从网络上请求的数据,对应的就是 data
View 层:视图层,前端展示的页面,对应的就是上面的页面
ViewModel 层:视图模型层,视图层和模型层交流的桥梁,一方面实现了数据绑定,数据的变化将实时反馈到视图层
另一方面实现了 DOM 监听,当 DOM 中发生一些事件的时候,可以监听并改变对应的数据,对应的就是 new Vue({...})
区别:MVVM 实现了 View 和 Model 的自动同步,当 Model 改变时,对应 View 会自动改变,不用操作 DOM 元素
MVC 是单向通信的,View 和 Model 需要通过 Controller 通信
3.2 生命周期钩子函数#
然后,Vue 在这些过程中提供了一些叫做 生命周期钩子 的函数,可以在不同阶段根据自己需求添加代码完成相关功能
<div id="app">
<h2>{{message}}</h2>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
// 数据还没有绑定到 Vue 对象,data,methods 中的数据还没有初始化,同时页面还没有挂载对象
beforeCreate() {
console.log("创建实例之前")
console.log(this.message) // undefined
console.log(this.$el) // undefined
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>{{message}}</h2>
},
// data,methods 中的数据已经初始化好了,但是页面还没有挂载对象
// 这个时候可以执行一些异步操作,因为 data 中已经有数据了
created() {
console.log("创建实例之后")
console.log(this.message) // Hello
console.log(this.$el) // undefined
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>{{message}}</h2>
},
// 此时页面挂载对象,但是还没有渲染页面
beforeMount() {
console.log("实例挂载前")
console.log(this.message) // Hello
console.log(this.$el) // <div id="app"> <h2>{{message}}</h2> </div>
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>{{message}}</h2>
},
// 页面已经渲染完成了
mounted() {
console.log("实例挂载后")
console.log(this.message) // Hello
console.log(this.$el) // <div id="app"> <h2>Hello</h2> </div>
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>Hello</h2>
},
// 在页面打开命令行,输入:app.message="World";
// 页面还没有更新,但是 data,methods 中的数据已经更新,还没有渲染到页面
beforeUpdate() {
console.log("数据更新前")
console.log(this.message) // World
console.log(this.$el) // <div id="app"> <h2>World</h2> </div>
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>Hello</h2>
},
// 页面和 vue 对象中的数据保持同步,此时更新渲染已经完成
updated() {
console.log("数据更新后")
console.log(this.message) // World
console.log(this.$el) // <div id="app"> <h2>World</h2> </div>
console.log("DOM结构:",document.getElementById('app').innerHTML) //<h2>World</h2>
}
})
</script>
4、一些指令#
4.1 v-once#
该指令修饰的标签或者组件只会渲染一次,之后修改不再生效
<div id="app">
<h2>{{message}}</h2>
<!-- 在页面对 message 进行修改时,第二个 <h2> 标签的内容不会发生变化 -->
<h2 v-once>{{message}}</h2>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message:'hello'
}
})
</script>
4.2 v-html#
针对获得数据是一段 HTML 代码的情况,可以通过 v-html 完成解析
<div id="app">
<h2>{{link}}</h2>
<!-- 这里可以对 link 中的 html 代码进行解析 -->
<h2 v-html="link"></h2>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
link:"<a href='http://www.baidu.com'>百度</a>"
}
})
</script>
4.3 v-text#
将数据显示在页面上,跟 {{}} 的作用差不多,但是 v-text 指定的内容会覆盖掉标签里面定义的内容,{{}} 则不会
<div id="app">
<!-- 这一行会显示 hello,你好 -->
<h2>{{message}},你好</h2>
<!-- 下面这一行在页面上只会显示 message 的内容 -->
<h2 v-text="message">,你好</h2>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message: 'hello'
}
})
</script>
4.4 v-pre#
指定的元素和它的子元素会跳过 vue 的渲染,直接显示标签定义的内容
<div id="app">
<h2>{{message}}</h2>
<!-- 这个 div 下的元素都不会经过 vue 的渲染,会直接显示 {{message}} -->
<div v-pre>
<h2>{{message}}</h2>
<h2>{{message}}</h2>
</div>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message:'hhh'
}
})
</script>
4.5 v-cloak#
有时候在网络不太好的情况下,页面加载完了,但是 vue 还没有渲染,导致页面会显示 {{message}} 的情况
vue 解析渲染之后会忽略掉 v-cloak 这个指令,所以可以对 v-cloak 的样式进行设计,使界面不会出现 {{message}}
<div id="app" v-cloak>
<h1>{{message}}</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
// 这个延时模拟网络卡顿
setTimeout(function(){
let app = new Vue({
el: '#app',
data: {
message:'hhh'
}
})
},1000)
</script>
<!-- 这个样式的意思是:是否显示这个 div -->
<style>
[v-cloak]{
display: none;
}
</style>
4.6 v-bind(重要)#
绑定标签元素内部的属性
它与 {{}} 的区别在于,v-bind 是用来绑定元素内部属性的,另一方面 {{}} 也无法绑定标签元素内部的属性
<div id="app">
<!--
{{message}} 绑定了标签外的值,将 <p> 标签中间的值绑定为 data 中的 message 的值
v-bind 则是绑定了元素内部的标签,将 align 的值绑定为 data 中的 address 的值
两者都可以通过 vue 实例进行动态替换
-->
<p v-bind:align="address">{{message}}</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello',
address: 'center'
}
})
</script>
v-bind 的简写模式
<!-- 完整写法 -->
<h2 v-bind:align="address"></h2>
<!-- 简写 -->
<h2 :align="address"></h2>
v-bind 动态绑定 class 有两种实现方式
- 通过对象绑定时
- 通过数组绑定时
通过对象方式绑定,会对对象中的 boolean 值进行判断,为 true 就添加进 class,否则不添加进去
<div id="app">
<!-- <h2 :class="{key1: boolean, key2: boolean}"></h2> -->
<!-- 当 boolean 为 true 时,class 会添加 key 进去
只有一个 key 为 true 时,class = "key1"
当有多个 key 为 true 时,class = "key1 key2"
-->
<!-- 所以这里就是 class="active" -->
<h2 :class="{active: isActive, b: isB}"></h2>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
isActive: true,
isB: false
}
})
</script>
通过数组方式绑定,如果数组中的元素有 引号 ,就显示它本身,否则就显示 data 中定义的,再否则就不显示那个元素
<div id="app">
<!-- 此处的 class="item1 b" -->
<h2 :class="['item1',item2,item4]"></h2>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
item1: 'a',
item2: 'b',
item3: 'c'
}
})
</script>
v-bind 动态绑定 style 也有两种实现方式
- 通过对象绑定
- 通过数组绑定
通过对象绑定时
<div id="app">
<p :style="{color: hc, fontSize: fs}">666</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
hc: 'red',
fs: '20px'
}
})
</script>
通过数组绑定时
<div id="app">
<p :style="[hc,fs]">666</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
hc: {color: 'red'},
fs: {fontSize: '20px'}
}
})
</script>
一个关于 v-bind 的小例子:遍历一个列表在页面,当点击某一个元素时,将其变成红色,其余的不变色
<div id="app">
<ul>
<li v-for="(item,index) in lists" @click="change(index)" :class="{active: num === index}">{{item}}</li>
</ul>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
lists:["a","b","c","d"],
num: 0
},
methods: {
change(index){
this.num = index;
}
}
})
</script>
<style>
.active{
color: red
}
</style>
4.7 v-on(重要)#
v-on 事件监听,常常跟事件处理方法联合使用,例如下面的点击事件,上面监听调用下面的方法
<div id="app">
<h1 v-on:click="change">{{message}}</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
methods: {
change() {
this.message = 'World'
}
}
})
</script>
v-on:简写模式
<!-- 完整写法 -->
<h1 v-on:click="change">{{message}}</h1>
<!-- 简单写法 -->
<h1 @click="change">{{message}}</h1>
事件调用的方法可能出现的三种情况
- 方法不需要参数
- 方法本身需要一个参数
- 方法需要一个参数,同时还需要一个 event 对象
<div id="app">
<!-- 不需要参数的时候,小括号加不加都可以 -->
<button @click="btn">按钮1</button>
<button @click="btn()">按钮2</button>
<!-- 需要一个参数的时候,有三种特别情况:
1.不加小括号,vue 会默认将浏览器生成的 event 事件对象作为参数传入,打印结果为 相应的事件对象
2.加了小括号,表示传参为空,打印结果为 undefined
3.传入参数,但是没有添加引号,如果是单词或字符,vue 会将其看作变量在 data 中寻找,找不到就报错
-->
<button @click="btn1">按钮3</button>
<button @click="btn1()">按钮4</button>
<button @click="btn1(msg)">按钮5</button>
<button @click="btn1(123)">按钮6</button>
<!-- 需要传入参数和 event 对象
1.不加小括号,vue 会将浏览器生成的 event 对象作为第一个参数传入,第二个参数就显示为 undefined
2.加了小括号,但是没有传参数,会显示两个 undefined
3.传入一个参数,如果这个参数不是事件对象,则事件对象会默认为 undefined,反之亦然
4.传入 event 对象的时候,需要使用 $event 作为参数
-->
<button @click="btn2">按钮7</button>
<button @click="btn2()">按钮8</button>
<button @click="btn2(1)">按钮9</button>
<button @click="btn2(1,$event)">按钮0</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
methods: {
// 不需要参数
btn(){
console.log('---');
},
// 需要一个参数
btn1(a){
console.log('+++',a)
},
// 需要一个参数和一个 event 对象
btn2(b,event){
console.log(b,'+++',event)
}
}
})
</script>
事件处理修饰符,这里简单写了几个
.stop 阻止事件冒泡
.prevent 阻止默认事件
.enter(也可以指定其他键位) 监听某一个键位
.once 该事件只会触发一次
.exact 精确控制事件
<div id="app">
<div @click="div">
<!--
不添加 .stop 时,点击按钮同时会触发 div 的点击事件(事件冒泡)
添加了 .stop 则不会触发 div 的点击事件
-->
<button @click.stop="btn">按钮</button>
</div>
<form action="http://www.baidu.com">
<!-- 默认的提交事件,会直接自动提交并跳转页面 -->
<input type="submit" value="提交" />
<!-- 阻止默认的提交事件,具体表现就是不跳转页面,而是在自己定义的方法中进行操作 -->
<input type="submit" value="提交" @click.prevent="sub" />
</form>
<!-- 监听键盘所有键位抬起的事件 -->
<!-- <input type="text" @keyup="key" /> -->
<!-- 只监听回车键 -->
<input type="text" @keyup.enter="key" />
<!-- 该事件只会触发一次 -->
<button @click.once="one">一次</button>
<!-- 只有 "只按下 ctrl 键并且鼠标点击" 才会触发事件,其他情况都不能触发事件 -->
<button @click.exact.ctrl="show">exa</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
methods: {
btn(){
console.log('btn');
},
div(){
console.log("div")
},
sub(){
console.log("sub")
},
key(){
console.log("key")
},
one(){
console.log("one")
},
exa(){
console.log("exc")
}
}
})
</script>
4.8 v-if#
判断页面某个内容是否显示,通常跟 v-else 联合使用
<div id="app">
<h1 v-if="isShow">页面显示IF</h1>
<h1 v-else>页面显示ELSE</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
isShow: false
}
})
</script>
v-show 与 v-if 的对比
v-show 让元素消失是给元素添加了一个行内样式:display:none,dom 中依旧可以查看
v-if 则是直接让元素在页面消失了,dom 查看不到该元素了
4.9 v-for#
循环遍历,通过 v-for 可以遍历数组和集合
<div id="app">
<ul>
<!-- 遍历数组 -->
<li v-for="(item,index) in lists">{{item}} -- {{index}}</li>
</ul>
<ul>
<!-- 遍历对象,获取的顺序依次为:值,键,下标 -->
<li v-for="(value,key,index) in obj">{{item}} - {{key}} - {{index}}</li>
</ul>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
lists:['a','b','c','d'],
obj:{
name: 'zz',
age: 18
}
}
})
</script>
4.10 v-model(重要)#
表单元素和数据的双向绑定
<div id="app">
<input type="text" v-model="message" />
<h1>{{message}}</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:''
}
})
</script>
<!--------------------------------------------------------------------------------------------------------->
<!-- 不用 v-model 实现双向绑定 -->
<div id="app">
<!-- 通过 v-bind 和 @input 实现
v-bind 绑定文本框输入的值
@input 文本框改变的时候触发,修改 message 的值
-->
<input type="text" :value="message" @input="cc" />
<h1>{{message}}</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:''
},
methods:{
cc(event){
this.message = event.target.value;
}
}
})
</script>
v-model 绑定一些标签
<div id="app">
<!-- radio 单选按钮 -->
<label>
<input type="radio" v-model="sex" value="男" />男
</label>
<label>
<input type="radio" v-model="sex" value="女" />女
</label>
<h2>性别是:{{sex}}</h2>
<hr />
<!-- checkbox 同意协议的那个小框框 -->
<label>
<input type="checkbox" v-model="isAgree" />同意协议
</label>
<button :disabled="!isAgree">下一步</button>
<hr />
<!-- checkbox 多选框 -->
<label>
<input type="checkbox" v-model="hobby" value="吃"/>吃
</label>
<label>
<input type="checkbox" v-model="hobby" value="喝"/>喝
</label>
<label>
<input type="checkbox" v-model="hobby" value="拉"/>拉
</label>
<label>
<input type="checkbox" v-model="hobby" value="撒"/>撒
</label>
<h2>爱好是:{{hobby}}</h2>
<hr />
<!-- 下拉框单选 -->
<select name="zz" v-model="fruit">
<option value="冬瓜">冬瓜</option>
<option value="南瓜">南瓜</option>
<option value="西瓜">西瓜</option>
<option value="北瓜">北瓜</option>
</select>
<h2>水果是:{{fruit}}</h2>
<hr />
<!-- 下拉框多选 -->
<select name="zz" v-model="fs" multiple="multiple">
<option value="冬瓜">冬瓜</option>
<option value="南瓜">南瓜</option>
<option value="西瓜">西瓜</option>
<option value="北瓜">北瓜</option>
</select>
<h2>水果是:{{fs}}</h2>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
sex:'',
isAgree: false,
hobby:[],
fruit:'冬瓜',
fs:[]
}
})
</script>
v-model 的一些修饰符
<div id="app">
<!-- lazy: 数据在失去焦点或者按 enter 时才会更新
v-model 本来是实时更新双向绑定的,用在这里,可以输入完了再在下方显示
-->
<input type="text" v-model.lazy="message" />
<h1>{{message}}</h1>
<!-- number: 将用户输入的数字转化成 number 类型(默认是字符串类型) -->
<input type="text" v-model.number="age" />
<h1>{{typeof age}}</h1>
<!-- trim: 去掉字符串前后的空格,中间的空格还在 -->
<input type="text" v-model.trim="name" />
<h1>{{name}}</h1>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:'',
age:'',
name:''
}
})
</script>
5、小插曲#
5.1 数组的一些方法(重要)#
<div id="app">
<ul>
<li v-for="(item,index) in lists">{{item}}</li>
</ul>
<button @click="change">改变</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
lists:['a','b','c','d']
},
methods:{
change(){
// 在数组末尾添加一个或多个元素
// this.lists.push('e');
// this.lists.push("e","f","g");
// 在数组开头添加一个或多个元素
// this.lists.unshift('aa')
// this.lists.unshift("aa","aaa");
// 删除数组最后一个元素
// this.lists.pop();
// 删除数组第一个元素
// this.lists.shift();
// splice(start,num,value...) 是集大成者,可以实现插入、替换、删除元素
// 删除元素,从下标为 x 的位置开始,删除两个元素
// this.lists.splice(1,2)
// 只传入一个参数的情况下,会删除这个元素以及之后的所有元素
// this.lists.splice(1)
// 替换元素,从下标为 x 的位置开始,替换 n 个元素,替换 m 个元素
// this.lists.splice(1,3,'xx','yy')
// 插入元素,从下标为 x 的位置开始,第二个参数为 0 ,插入 n 个元素
// this.lists.splice(1,0,'z','zz','zzz')
// Vue.set(对象,索引,修改值) 修改数组值
// Vue.set(this.lists,1,'bb')
// 反转数组内容
this.lists.reverse();
}
}
})
</script>
5.2 可变参数#
参数个数不确定的时候使用
<div id="app">
<h2>sum:{{result}}</h2>
<!-- 可以传入多个参数 -->
<button @click="sum(1,6,5,50)">求和</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
result: 0
},
methods:{
// 定义可变长度的参数
sum(...a){
for(let i = 0; i < a.length; i++){
this.result += a[i]
}
}
}
})
</script>
5.3 JS 相关(重要)#
这三个方法可以实现链式编程,就是 let newArr = arr.filter(xxx).map(xxx).reduce(xxx);
- filter: 过滤数组元素,参数是一个回调函数,返回值为 true 时会将元素存入新数组,否则就过滤掉
- map: 对数组所有元素进行一次变化,参数是一个回调函数
- reduce: 汇总数组的所有内容,参数是一个回调函数
<script>
let nums = [10,50,6,20,63,9,100,56,86,23]
// filter:过滤数组中的元素
// let filterNums = nums.filter(function(n){
// return n < 50;
// });
// 这是箭头函数的简便写法
let filterNums = nums.filter(n => n < 50);
console.log(filterNums)
// map:操作数组元素
// let mapNums = nums.map(function(n){
// return n * 2;
// });
// 这是箭头函数的简便写法
let mapNums = nums.map(n => n * 2);
console.log(mapNums)
// reduce:对数组中的元素汇总
// preValue 是初始值(计算结束之后的返回值),n 表示当前元素,0 表示传递给函数的初始值
// let sum = nums.reduce(function(preValue,n){
// return preValue + n;
// },0);
// 这是箭头函数的简便写法
let sum = nums.reduce(((preValue,n) => preValue + n),0);
console.log(sum)
</script>
5.4 动态参数#
用 [] 括起来的参数
可以将指令的参数或者标签的属性括起来,动态进行改变
我现在还没看到这个有什么作用,得以后看一看
<!--
这里需要注意的事情:vue 会把 [] 中定义的参数全部识别为小写,然后再从 data 中查找匹配项
为了防止出错,用小写就完事了,如果觉得不好看,那一定也得把 [] 在 data 中对应的属性名变成小写
-->
<div id="app">
<p v-bind:[ATTRIbuteName]="ck">{{message}}</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello',
attributename: 'align',
ck: 'right'
}
})
</script>
5.5 key#
key :管理可复用的元素,更高效的更新虚拟 DOM
Vue 本身就会尽可能的高效地渲染元素,通常都会复用已经存在的元素而不是从头渲染,这使得 Vue 很高效
例如下面的例子,改变了 login 的类型实现用不同的方式登录,但是输入框的内容不会清空,因为这两个模板使用了相同的元素
为了保持高效,input 标签不会被替换掉,这里只是改变了它的 placeholder 内容
什么意思呢,就是文本框的输入依然保存着,改变的只是默认显示的 placeholder 的内容
<div id="app">
<div v-if="login === 'username'">
<label>Username</label>
<input placeholder="输入名字">
</div>
<div v-else>
<label>Email</label>
<input placeholder="输入邮箱">
</div>
<button @click="change">切换</button>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
login:''
},
methods:{
change(){
// 如果 login===username,就赋值为空,否则就赋值为username
// 简单点说就是通过点击按钮,切换登录的方式
this.login = this.login === 'username' ? '' : 'username';
}
}
})
</script>
官网里面说,Vue 通过 key 来表示两个元素是相互独立的,不要复用它们
所以,相同的组件定义了不同的 key ,vue 就不会复用该标签了
用在我这里,就表示两个 input 是相互独立的,不要复用,具体表现就是点击按钮切换之后,文本框的内容会清空
<!-- 这里只需要在上面的代码中给 input 标签添加不同的 key 即可,key 相同的话就是告诉 vue 这俩货一样了,它就又会复用 -->
<input placeholder="输入名字" key="a">
<input placeholder="输入邮箱" key="b">
5.6 const#
-
const 定义的时候,必须赋值
-
const 定义的内容被赋值后不能被更改
-
const 定义对象的时候,对象不能更改,但是对象的属性可以更改
<script>
// 下面这行定义的时候没有赋值,会报错
// const name;
const age = 100;
const obj = {
name:'a',
age:18
}
console.log(obj)
// 不能直接修改对象,这样写是改变了对象的指向,是修改了 obj 保存的地址值,会报错
// obj = {
// name: 'b',
// age:19
// }
// 不过可以对对象的属性进行修改
obj.name = 'b'
obj.age = 19
console.log(obj)
</script>
6、计算属性#
对于任何复杂的逻辑,都应刚使用计算属性,以保证 vue 的标签部分只是为了显示结果,更加直观
可能使用了计算属性使得代码量变多了,但是页面其实更清晰了,有条理了,这算是另一种简洁吧
6.1 简单使用#
将一个字符串进行反转显示在页面上
<!-- 没有使用计算属性时候 -->
<div id="app">
<p>反转前:{{message}}</p>
<p>反转后:{{message.split('').reverse().join('')}}</p>
</div>
<!-- 使用了计算属性 -->
<div id="app">
<p>反转前:{{message}}</p>
<p>反转后:{{anotherMessage}}</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed:{
anotherMessage(){
return this.message.split('').reverse().join('');
}
}
})
</script>
6.2 计算属性和方法的区别#
通过下面的代码来看,好像本质上没有什么区别,实际上也是这样子的哈哈哈
实际上这俩的区别之处在于 性能
- 计算属性是会基于响应式依赖进行缓存的
- 方法则是调用一次就会执行一次
- 可以把计算属性当作一个属性直接使用,但是调用方法的时候需要加 (),虽然也可以不加,这一点参考 4.7 v-on
这意味着只要源头的 message 不发生变化,多次获得 anotherMessage 值,计算属性会立即返回之前的计算结果
而通过方法获得的 another 值,每次执行都会执行方法才能获得结果
<div id="app">
<p>反转前:{{message}}</p>
<p>使用计算属性反转后:{{anotherMessage}}</p>
<p>使用方法反转后:{{another()}}</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed:{
anotherMessage(){
return this.message.split('').reverse().join('');
}
},
methods:{
another(){
return this.message.split('').reverse().join('')
}
}
})
</script>
6.3 计算属性的 setter#
计算属性默认是 getter 方法,但是有些情况可能要用到 setter 进行赋值(开发人员想的挺周到)
<div id="app">
<p>English Name: {{fullName}}</p>
</div>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: 'Bob',
lastName: 'Sam'
},
computed:{
// 这个 fullName 相当于是一个属性
fullName: {
// 这里是 getter 方法,获取计算后的结果
get(){
return this.firstName + ' ' + this.lastName;
},
// 这里是 setter 方法,进行赋值操作
set(name){
let names = name.split(',');
this.firstName = names[0];
this.lastName = names[names.length-1];
}
}
}
})
// 在 console 输入:app.fullName="Ha,ha";
// 页面会发生变化,显示 Ha ha
</script>
6.4 侦听器#
vue 通过 watch 提供了一个通用的办法来响应数据的变化,具体表现在数据变化时候就触发 watch 中的代码
数据变化时执行异步或开销较大的操作,数据一旦发生了变化就 通知侦听器所绑定的方法。
监听单个变量的变化
<div id="app">
{{message}}
<button @click="change">改变</button>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message: "Hello"
},
methods:{
change(){
this.message = "haha";
}
},
// message 发生变化时会触发 watch
watch:{
message(newV,oldV){
console.log(newV,oldV)
}
}
})
</script>
监听对象的单个属性的变化
<div id="app">
{{obj.name}} + {{obj.age}}
<button @click="change">改变</button>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
obj:{
name:'aa',
age:18
}
},
methods:{
change(){
this.obj.name='hh'
this.obj.age=16
}
},
watch:{
'obj.name'(newValue,oldValue){
console.log(newValue,oldValue)
},
'obj.age'(newValue,oldValue){
console.log(newValue,oldValue)
}
}
})
</script>
监听一个对象整体的变化
<div id="app">
{{obj.name}} + {{obj.age}}
<button @click="change">改变</button>
</div>
<script src="../resources/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
obj:{
name:'aa',
age:18
}
},
methods:{
change(){
this.obj.name='hh'
this.obj.age=16
}
},
watch:{
obj:{
handler(newValue,oldValue){
console.log(newValue,oldValue)
}
}
}
})
</script>
// 直接更改对象的属性,不会发生变化
// 更改对象的引用后,调用了 watch
// 为了更改对象的属性时,也能触发 watch,可以使用 deep(深入观察),下面这么写就可以
/*
watch:{
obj:{
handler(newValue,oldValue){
console.log(newValue,oldValue)
},
deep:true
}
}
*/
初始化绑定的时候就触发监听事件,通过 immmediate
watch:{
obj:{
handler(newValue,oldValue){
console.log(newValue,oldValue)
},
// 表示初始化的时候就会执行 watch
immediate:true,
deep:true
}
}
7、组件化#
Vue 的组件化是它很重要的一个思想,它是通过一个个独立的可以复用的组件来构造一个应用
- 功能划分,将不同的功能细化为组件
- 可以复用,移植性高,之后如果有需要用到的地方,可以拿直接使用,不用再写一个功能相似的东西
7.1 基本使用#
Vue 在之前版本使用组件有三个步骤:
- 创建组件构造器:const name = Vue.extend({xxx})
- 注册组件:Vue.component("标签名",name),标签名推荐字母全小写且必须包含一个连字符,类似于:my-comp
- 使用组件:必须在 vue 实例中使用,就好比我下面的例子,写在 div 之外,vue 可不会搭理它
<div id="app">
<!-- 3.使用组件 -->
<my-comp></my-comp>
<my-comp></my-comp>
</div>
<script src="../resources/vue.js"></script>
<script>
// 1.创建一个组件构造器
const comp = Vue.extend({
// 这里用了一个 ` 这个符号,可以实现字符串跨行,很鸡肋,但看起来比较整齐
template: `
<div>
<h2>哈哈</h2>
<p>这是一句话</p>
<p>这也是一句话</p>
</div>
`
})
// 2.注册组件,这里是注册成了全局组件
Vue.component("my-comp",comp)
let app = new Vue({
el:'#app',
data: {
}
// 2.也可以在这注册成局部组件
// components:{
// 'my-comp':comp
// }
})
</script>
更新版本之后,组件的创建和注册合并了,我这里之后的组件的例子就使用更新之后的版本
<div id="app">
<!-- 2.使用组件 -->
<my-comp></my-comp>
</div>
<script src="../resources/vue.js"></script>
<script>
// 1.组件的创建和注册
// 这是全局组件
Vue.component("my-comp",{
data(){
return{
count: 0
}
},
template: "<button @click='add'>点击了 {{count}} 次</button>",
methods:{
add(){
this.count++;
}
}
})
let app = new Vue({
el: '#app',
data: {
}
})
</script>
7.2 全局和局部#
全局组件:多个 vue 实例都可以使用
局部组件:只有当前 vue 实例可以使用
通过 Vue.component("xxx",{...}) 注册的方式,注册的是全局组件
当存在多个 vue 实例时,它们都可以使用
<div id="app">
<my-comp></my-comp>
</div>
<div id="app1">
<my-comp></my-comp>
</div>
<script src="../resources/vue.js"></script>
<script>
// 这表示注册全局组件
Vue.component("my-comp",{
template: `
<div>
<h2>哈哈</h2>
<p>这是一句话</p>
<p>这也是一句话</p>
</div>
`
})
let app = new Vue({
el:'#app',
data: {
}
})
let app1 = new Vue({
el:'#app1',
data:{
}
})
</script>
在某个 vue 实例中将组件注册,那个组件就会变成局部组件
局部组件注册的方式:
- 可以先在 script 标签中定义组件,然后在实例中注册组件
- 也可以直接在实例中定义注册组件
对于局部组件,只有注册组件的 vue 实例可以使用它,在其他实例中使用该组件就会报错
<div id="app">
<my-comp></my-comp>
</div>
<!-- 会报错,提示 my-comp 没注册 -->
<div id="app1">
<my-comp></my-comp>
</div>
<script src="../resources/vue.js"></script>
<script>
const comp = {
template: `
<div>
<h2>哈哈</h2>
<p>这是一句话</p>
<p>这也是一句话</p>
</div>
`
}
let app = new Vue({
el:'#app',
data: {
},
// 在实例中注册局部组件
components:{
'my-comp': comp
}
})
let app1 = new Vue({
el:'#app1',
data:{
}
})
</script>
全局组件和局部组件的另一种写法 -> 简写 + 模板写法
- 简写:
- 全局简写:组件的创建和注册通过 Vue.component() 完成
-
局部简写:将组件的创建和注册都在 vue 实例中完成
-
模板:将组件中 template 的内容抽离出来写在上面
<div id="app">
<!-- 会报错,提示 my-all-comp 没有注册 -->
<my-all-comp></my-all-comp>
<my-local-comp></my-local-comp>
</div>
<!-- 定义模板,指定 id -->
<template id="allComp">
<div>
<h2>这是一句话</h2>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
// 如果注册全局组件,直接这样写就可以,模板内容是通过 id 获取上面定义的内容
// Vue.component("my-all-comp",{
// template: '#allComp'
// })
let app = new Vue({
el:'#app',
data:{
},
// 局部组件的写法
components:{
'my-local-comp':{
template:'#allComp'
}
}
})
</script>
7.3 父子组件#
定义两个组件,在一个组件中注册另一个组件,它们之间就形成了父子关系
然后在 vue 实例中注册父组件,就可以使用父子组件了
虽然在父组件中注册了子组件,但是由于 vue 实例中并没有注册子组件,所以子组件无法在页面使用
<div id="app">
<my-comp></my-comp>
<!-- 这里会报错,提示没注册 my-son -->
<my-son></my-son>
</div>
<template id="son">
<div>
<p>我是儿子</p>
</div>
</template>
<template id="father">
<div>
<p>我是爹爹</p>
<myson></myson>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
// 子组件 (明明敲得例子一样,但是我的没出来,QAQ~ 原来是下面的原因)
// 必须先定义子组件,否则父组件使用时会提示找不到
// 这里使用了模板的写法,即将 template 抽离到上面去
const son = {
template: '#son'
}
// 父组件中注册子组件
const father = {
template: '#father',
components:{
'my-son': son
}
}
let app = new Vue({
el:'#app',
data: {
},
// vue 实例中注册父组件
components:{
'my-comp': father
}
})
// 上面的代码通过 "模板+简写" 整合起来的究极代码就是下面这样子
// let app = new Vue({
// el: '#app',
// data:{
// },
// components:{
// 'my-comp': {
// // 这里也可以直接这么写:template: father(可以直接写 id 名)
// template: '#father',
// components:{
// 'my-son': {
// // 同上
// template: '#son'
// }
// }
// }
// }
// })
</script>
7.4 组件的 data 数据#
组件能否直接使用 vue 实例中的数据吗? 不能!!!
虽然是在 vue 的实例中注册的组件,但是组件无法直接使用 vue 实例中的 data
<div id="app">
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<p>我是一句话</p>
<!-- 这里页面的 console 会报错 -->
<p>{{message}}</p>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:'hello'
},
components:{
'my-comp': {
template: cc
}
}
})
</script>
首先在 7.3 父子组件 中,从父子组件的使用方法可以看出,其实对于父组件而言,它相当于是 vue 实例的子组件。
基于上面这句话,可以猜测两个结论:
-
既然 vue 也是一个组件,它有自己的 data,那么组件肯定也有自己的 data
-
既然组件无法直接使用 vue 实例中的 data,那么就表示子组件无法直接使用父组件的 data
针对第一个结论的说明
组件中的数据也在 data 中,但是它不像 vue 实例那用使用,它有自己的方式
- 组件的 data 必须是一个函数,这个函数的返回值是一个对象
- 返回值的对象中保存着数据
<div id="app">
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<p>我是一句话</p>
<p>{{message}}</p>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:'hello'
},
components:{
'my-comp': {
template: cc,
data(){
return {
message: '组件的数据'
}
}
}
}
})
</script>
为什么组件的 data 需要通过函数使用?
因为如果组件的 data 不是函数,那么多个地方都使用该组件的时候,会公用这个 data,这可不是我们希望看到的;
而当组件的 data 是一个函数的时候,每次使用返回的都是一个新的对象,互不干扰。
<!-- 这里定义了三个计数器 -->
<div id="app">
<my-comp></my-comp>
<my-comp></my-comp>
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<p>当前计数:{{count}}</p>
<button @click="add">+</button>
<button @click="del">-</button>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
},
components:{
'my-comp': {
template: cc,
data(){
return{
count: 0
}
},
methods:{
add(){
this.count++;
},
del(){
this.count--;
}
}
}
}
})
</script>
针对第二个结论
子组件无法直接使用父组件定义的 data 中的内容,会提示 message 没有定义
<div id="app">
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<p>我是一句话</p>
<p>{{message}}</p>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:"vue"
},
components:{
'my-comp': {
template: cc
}
}
})
</script>
7.5 父子组件通讯#
父子组件无法直接访问对方的 data 中的数据,但是间接就可以了,这个就是父子组件之间的通讯
-
父组件向子组件传值:子组件通过 props 接收
-
子组件向父组件传值:通过自定义事件
父组件向子组件传值
子组件通过 props 用数组方式接收
<div id="app">
<!-- 2.将父组件的 lists 传给子组件,子组件通过 v-bind:arr 与 props 中定义的内容绑定,接收父组件的传值 -->
<my-comp :arr="lists"></my-comp>
</div>
<template id="cc">
<div>
<ul>
<!-- 3.子组件使用父组件传来的值 -->
<li v-for="(item,index) in arr">{{item}} - {{index}}</li>
</ul>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
lists:['a','b','c','d']
},
components:{
'my-comp': {
// 1.最简单的写法,子组件通过该 props 接受父组件传来的值
props:['arr'],
template: cc
}
}
})
</script>
props 通过对象接收参数
<div id="app">
<!-- 父组件没有给它传值,就显示默认值 -->
<my-comp></my-comp>
<!-- 父组件传值之后,就显示传来的值 -->
<my-comp :arr="lists"></my-comp>
</div>
<template id="cc">
<div>
<ul>
<li v-for="(item,index) in arr">{{item}} - {{index}}</li>
</ul>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
lists:['a','b','c','d']
},
components:{
'my-comp': {
template: cc,
// 高级一点的内容,指定必须传入什么类型的值,且如果没有传值,就用指定的默认值
props:{
arr:{
type: Array,
default(){
return ['x','y']
}
}
}
}
}
})
</script>
props 注意:驼峰问题
props 定义接受参数名的时候如果用了驼峰命名,那么在页面接收父组件的参数的那个地方需要将驼峰命名更改
具体更改方式:myInfoList -> my-info-list
<div id="app">
<my-comp :my-info-list="lists"></my-comp>
</div>
<template id="cc">
<div>
<ul>
<li v-for="item in myInfoList">{{item}}</li>
</ul>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
lists:['a','b','c','d'],
message:'666'
},
components:{
'my-comp': {
template: cc,
props:{
myInfoList:{
type: Array,
default(){
return ['a','b']
}
}
}
}
}
})
</script>
子组件向父组件传值
子组件通过 this.$emit("xxx",参数) 触发父组件的事件,并传递参数,父组件通过 v-on(@) 监听事件
<div id="app">
<!-- 3.父函数监听 mclick 事件,调用本身的 hello 方法,并把 item 传入 -->
<my-comp :arr="lists" @mclick="hello"></my-comp>
</div>
<template id="cc">
<div>
<!-- 1.点击子组件的按钮触发子组件的 ccclick 方法 -->
<button v-for="item in arr" @click="ccclick(item)">{{item.name}}</button>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
lists:[
{id:'1',name:'aa'},
{id:'2',name:'bb'},
{id:'3',name:'cc'},
{id:'4',name:'dd'},
{id:'5',name:'ee'}
]
},
components:{
'my-comp':{
props:['arr'],
template:cc,
methods:{
ccclick(item){
// 2.这个函数中,触发父组件的 mclick 方法,并传递当前 item 项
this.$emit("mclick",item);
}
}
}
},
methods:{
hello(item){
// 4.完成 hello 方法
console.log(item.name)
}
}
})
</script>
小例子:父子组件互相修改文本框的值
<div id="app">
<!-- 父组件显示的值 -->
<input v-model="message" />
<h3>{{message}}</h3>
<!-- 子组件显示的值 -->
<my-comp
:msg="message"
@input="fc"
>
</my-comp>
</div>
</div>
<template id="cc">
<div>
<!-- 监听输入框改变 -->
<input :value="msg" @input="change" />
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
message:'hello'
},
components:{
'my-comp':{
template: cc,
props:['msg'],
methods:{
change(e){
this.$emit("input",e)
}
}
}
},
methods:{
fc(e){
this.message = e.target.value;
}
}
})
</script>
7.6 父子组件的访问方式#
父组件直接访问子组件:通过 $children 或者 $refs
- 通过 $children 访问时,需要通过 [] 指定第几个子组件,不是很方便
- 通过 $refs 访问时,需要在自定义子组件标签上定义 ref="xxx" 的属性,通过 $refs.xx.属性/方法 访问
子组件直接访问父组件:通过 $parent,直接通过 this.$parent.属性/方法 访问
<!-- 下面是 $children 和 $refs 的使用 -->
<div id="app">
<my-comp></my-comp>
<my-comp ref="aa"></my-comp>
<button @click="btnclick">按钮</button>
</div>
<template id="cc">
<div>
<h1>我是子组件</h1>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
},
components:{
'my-comp':{
template: cc,
data(){
return {
name:'子组件名字'
}
},
methods:{
show(){
console.log("子Method")
}
}
}
},
methods:{
btnclick(){
// 通过 $children 获得子组件的属性和方法
// this.$children[0].show();
// console.log(this.$children[0].name)
this.$refs.aa.show();
console.log(this.$refs.aa.name)
}
}
})
</script>
子组件访问父组件,跟上面的 $children 类似,应该用的不多
<div id="app">
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<h1>我是子组件</h1>
<button @click="btnclick">按钮</button>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
name:'我是父组件'
},
components:{
'my-comp':{
template: cc,
methods:{
btnclick(){
this.$parent.show();
console.log(this.$parent.name)
}
}
}
},
methods:{
show(){
console.log("父Method")
}
}
})
</script>
7.7 动态组件#
动态组件,实现不同组件的动态切换,可以通过 <component>
添加一个 is attribute 来实现
下面是官网的一个例子,我把那些名字改了改,改成我一眼能看懂的
<div id="app">
<!-- 1.点击按钮,defaultTab 变化 -->
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['tab-button', { active: defaultTab === tab }]"
v-on:click="defaultTab = tab"
>
{{ tab }}
</button>
<!-- 3.动态切换,当 chooseWho 改变的时候,显示的组件也会跟着变化 -->
<component v-bind:is="chooseWho" class="tab"></component>
</div>
<script src="../resources/vue.js"></script>
<script>
Vue.component("tab-a",{
template:"<div>这是A组件</div>"
});
Vue.component("tab-b",{
template:"<div>这是B组件</div>"
});
Vue.component("tab-c",{
template:"<div>这是C组件</div>"
});
let app = new Vue({
el: "#app",
data: {
defaultTab: "a",
tabs: ["a","b","c"]
},
// 2.defaultTab 变化引起 chooseWho 变化
computed: {
chooseWho() {
return "tab-" + this.defaultTab.toLowerCase();
}
}
});
</script>
<style>
.tab-button {
padding: 6px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid #ccc;
cursor: pointer;
background: #f0f0f0;
margin-bottom: -1px;
margin-right: -1px;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-button.active {
background: #e0e0e0;
}
.tab {
border: 1px solid #ccc;
padding: 10px;
}
</style>
动态切换组件虽然很好用,但是设想一个场景
在网上看书,首页左边是目录列表,右面是内容,顶部有两个按键,对应首页和其他页,你点了一个目录开始看
然后不小心点到其它页,然后你点击首页继续看的时候,发现显示的是默认的内容,不是你之前读的内容
如果是动态组件实现这个场景,就会遇到这种问题
因为动态组件虽然用到了计算属性,但是每次切换的时候,defaultTab 会发生变化,所以创建了新的实例
为了让我们返回首页继续看之前的内容,就需要用到 keep-alive
标签了,字面意思:保持活性
<div id="app">
<!-- 3.点击按钮之后,引起 defaultTab 的变化 -->
<button
v-for="tab in tabs"
:key="tab"
@click="defaultTab = tab"
>{{tab}}
</button>
<!-- 4.保持活性的代码片段,chooseWho 改变的时候,切换按钮对应的页面
此时如果返回页面,会保持显示上一次浏览的目录对应的主体内容
-->
<keep-alive>
<component :is="chooseWho"></component>
</keep-alive>
</div>
<template id="cc">
<div>
<div>
<ul>
<!-- 1.点击列表项(目录)时,将 item 赋给 selectWho -->
<li
v-for="item in lists"
:key="item.id"
@click="selectWho = item"
>
{{item.title}}
</li>
</ul>
</div>
<div>
<!-- 2.根据 selectWho 来确定显示的内容 -->
<div v-if="selectWho">
<h3>{{selectWho.context}}</h3>
</div>
<h3 v-else>点击标题开始阅读</h3>
</div>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
Vue.component("A",{
template: cc,
data(){
return{
lists:[
{id:1,title:'第一个',context:'第一页主体内容第一页主体内容第一页主体内容第一页主体内容'},
{id:2,title:'第二个',context:'第二页主体内容第二页主体内容第二页主体内容第二页主体内容'},
{id:3,title:'第三个',context:'第三页主体内容第三页主体内容第三页主体内容第三页主体内容'}
],
selectWho: null
}
}
});
Vue.component("B",{
template:"<div>其他页面</div>"
})
let app = new Vue({
el:'#app',
data:{
defaultTab: 'A',
tabs:['A','B']
},
// 4.defaultTab 的变化引起 chooseWho 的变化
computed:{
chooseWho(){
return this.defaultTab;
}
}
})
</script>
8、插槽 slot#
主要是为了提高组件的扩展性
8.1 基本使用#
参考手机 app 顶部的导航栏,它们长得相似但显示内容可能不同,对于它们不同的那一部分,就可以通过 slot 实现
这个玩意,就是把一样的留下,把不同的暴露为插槽,插槽的内容调用的时候再决定,有点像 java 的抽象类了
- 在子组件中定义一个标签:
<slot>
- 插槽可以有默认值,例如:
<slot><button>按钮</button></slot>
- 多个值同时放入子组件进行替换时,全部都会显示
<div id="app">
<!-- 在使用子组件的时候,中间那一部分可以替换插槽的内容 -->
<my-comp></my-comp>
<my-comp><p>这里插入p标签</p></my-comp>
<my-comp></my-comp>
<my-comp>
<p>这里插入p标签</p>
<h2>这里插入h2标签</h2>
</my-comp>
<my-comp></my-comp>
</div>
<template id="cc">
<div>
<h3>我是子组件,我下面是插槽</h3>
<!-- 如果没有填写插槽,就默认显示按钮 -->
<slot><button>按钮</button></slot>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
},
components:{
'my-comp':{
template: cc
}
}
})
</script>
自 vue 2.6.0 起,slot 和 slot-scope 已经废弃,取代它们的是 v-slot
8.2 具名插槽#
有些导航栏或者其他东西可能不只是一个东西不一样,它的左边右边中间都可能随机不一样,于是就用到了具名插槽
这里的使用方法
- 在子组件中定义插槽,指定 name 属性
- 在父组件中使用子组件的时候,
<template v-slot:name>
标签内的内容会替换掉对应 name 的插槽
<div id="app">
<!-- 这里第一行显示默认的三个插槽 -->
<my-comp></my-comp>
<!-- 根据插槽名,对第二行和第三行的插槽进行替换 -->
<my-comp>
<template v-slot:center>
<span>标题</span>
</template>
<!-- 简写方式:template #center -->
</my-comp>
<my-comp>
<template v-slot:left>
<button>返回</button>
</template>
<template v-slot:center>
<input placeholder="请输入搜索内容" />
</template>
<template v-slot:right>
<button>详情</button>
</template>
</my-comp>
</div>
<template id="cc">
<div>
<slot name="left">左边</slot>
<slot name="center">中间</slot>
<slot name="right">右边</slot>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
},
components:{
'my-comp':{
template: cc
}
}
})
</script>
8.3 作用域插槽#
具体理解,正常情况下是父组件替换插槽的标签,内容是父组件决定的
但是现在想把内容交给子组件决定,就需要用到作用域插槽了
什么意思呢,这首先需要了解一下,在页面使用组件之后,父组件是无法直接拿到子组件中定义的数据的
如果想对子组件中的数据进行不同形式的展现,就可以通过作用域插槽来解决
作用域插槽
- 在子组件中定义插槽模板,同时与子组件中的数据绑定,我这里是 :arr="lists" (这个 arr 可以随意起名)
- 在页面中使用子组件的时,通过
<template v-slot:default="slot">
来引用子组件的插槽 - 然后通过绑定的属性就可以获取子组件中的数据了,意思就是可以通过 slot.arr 获取到子组件的数据了
<div id="app">
<my-comp></my-comp>
<my-comp>
<!-- 2.引用子组件插槽 -->
<template v-slot:default="slot">
<!-- 3.获取子元素的数据,按需求显示 -->
<!-- 这里是通过 join('-') 将数组中的元素按 - 分割开来 -->
<span>{{slot.arr.join('-')}}</span>
</template>
</my-comp>
<my-comp>
<template v-slot:default="slot">
<!-- 同上 -->
<span>{{slot.arr.join('+')}}</span>
</template>
</my-comp>
</div>
<template id="cc">
<div>
<!-- 1.绑定子元素的数据,这个玩意叫做 插槽prop -->
<slot :arr="lists">
<ul>
<li v-for="item in lists">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../resources/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
},
components:{
'my-comp':{
template: cc,
data(){
return{
lists:['a','b','c','d','e']
}
}
}
}
})
</script>
模块化#
为什么要模块化一个项目
因为一个项目如果不是一个人开发,那么会引入不同的 js 文件,如果这些文件中使用了相同的变量,加上不同 js 文件导入的先后顺序不同,就会导致不必要的错误
为此,可以使用立即执行函数解决这个问题,在函数内部定义的变量外部无法访问,但这样又会出现一个问题,就是如果我想在函数外面访问这个 js 的方法和变量,就不行了,就需要重写,代码复用性不行,也不好看,甚至可能依旧会出现上面的问题
之后,又出现了一个写法,将自己开发的模块中定义一个变量,在立即执行函数中定义一个对象,在对象中添加变量和方法,将对象返回,之后在其他模块使用的时候,直接通过 变量名.xxx 即可,这种情况下,只需要避免模块名相同即可
最后,前人已经制定了一些模块化的写法,我们只需要按照相应规范即可实现模块化,模块化最主要的就是 导入 和 导出,over~
9、Webpack#
Webpack 模块化打包工具,依赖于 node.js 环境
node.js 环境为了可以正常执行代码,又会依赖很多个包,为此,node.js 自带了软件包管理工具 npm
9.1 安装#
首先在电脑上查看 node.js 版本,cmd 输入:node -v
如果没有就需要先安装 node.js,之后通过命令行安装 webpack
我在 B站 看的视频,安装的是 3.6.0. 版本的,全局安装(在命令行任何地方可以使用)
npm install webpack@3.6.0 -g
9.2 基本使用#
首先建立文件夹
- dist:存放打包之后的代码
- src:这在里编写模块代码
- index.html:暂时放在这里运行
这里写一个简单的打包测试一下能否实现模块的导入导出
1.编写 commonJS.js 使用 commonJS 的模块导出
// 这个是 commonJS.js
function add(a,b){
return a + b
}
function sub(a,b){
return a - b
}
module.exports = {
add,
sub
}
2.编写 ES6.js 使用 ES6 的模块导出
let name = "伍六七";
let age = 18;
function show(){
console.log("ES6 show")
}
export {
name,
age,
show
}
3.编写 main.js 导入模块
// 这个是 main.js
// commonJS 的模块导入
const {add, sub} = require('./commonJS.js');
// ES6 的模块导入
import {name,age,show} from "./ES6.js"
// 导入后直接使用
console.log(add(100,23));
console.log(sub(123,23));
show();
编写完毕之后,在 cmd 进入当前目录,输入以下代码进行打包,之后会在 dist 目录下生成 bundle.js 文件
webpack ./src/main.js ./dist/bundle.js
之后直接在 index.html 中引入 bundle.js 即可,运行 index.html,查看 console,能看到 main.js 中打印的结果
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>
9.3 配置#
每次修改 js 文件之后都需要重新打包,输入那么一长串
为了少些一点后面的路径,可以配置 webpack.config.js 文件
但要知道 这个配置文件的作用不止于此,不然就太鸡肋了
配置 webpack.config.js
// 导入 node 的 path 模块,下面会通过 path 动态获取当前路径
const path = require("path")
module.exports = {
// 配置入口(js 文件)和出口(打包的地方)
entry:'./src/main.js',
output:{
// path 中的方法,这里是对两个路径进行拼接
// __dirname:当前文件所在路径,(可以理解为上级目录)也就是 E:\Demo\VueDemo\webpack
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js'
},
}
因为我们在 webpack.config.js 中可能会需要使用到 node 的相关内容,所以首先初始化项目
在页面输入下面的代码,之后会生成 package.json 文件,这个文件会保存当前项目的一些信息,是 npm 包管理的文件
npm init -y
之后就可以只用写 webpack 完成打包了,同时目录下会多一个 package.json 的文件
配置 package.json
npm run xxx 会对应 package.json 中的 scripts 中定义的键值对
所以可以通过配置下面这样,之后就通过 npm run build 完成打包
而通过 npm run build 运行时,package.json 首先会在本地的 node_mudules 中寻找 webpack,找不到就找全局的
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "webpack"
},
本地安装 webpack,本地安装之后,通过 npm run build 就会优先在本地寻找 webpack,找不到就去全局找
本地安装主要解决项目不使用全局的 webpack,在本地安装之后配合 package.json 实现本地 webpack 版本的打包
npm install webpack@3.6.0 --save-dev
这里遇到错误的话,应该是 package.json 里面的 name 属性对应的是 webpack ,修改掉这个名字就好了
loader
之前说过,只是通过 webpack 打包很鸡肋,它其实还有更高级的操作
例如将 ES6 和 TypeScript 转化成 ES5,将 less scss 转化成 css,将 .vue 文件转化成 .js 文件等
那要实现这些功能,就需要用到 loader 了
loader 使用步骤:
- 通过 npm 安装
- 在 webpack.config.js 配置
例如这里使用 css-loader 和 style-loader
安装:npm install --save-dev css-loader
npm install --save-dev style-loader
配置:
module:{
rules:[
{
test:/\.css$/,
// css-loader 只负责加载 css 文件
// style-loader 负责将样式添加到 DOM 中生效
use:['style-loader','css-loader']
}
]
}
处理图片
-
安装:npm install --save-dev url-loader@1.1.2
-
配置:
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
// 图片小于 limit ,会被编译成 base64 字符串形式
// 图片大于 linit ,会采用 file-loader 模块加载,此时图片会被打包进 dist 目录中并重新命名
//
limit: 20000
}
}
]
}
- 添加背景图片
body{
background: url("../img/123.jpg");
}
- 通过 npm run build 运行,在页面即可看到图片
针对图片大小大于 limit 指定的大小时,直接通过上面方式就会报错
此时在上一步的基础上,需要继续安装 file-loader,并进行相关配置
安装:npm install --save-dev file-loader@3.0.1
配置:
- 这里为什么配置?因为图片大于 limit 时,不再使用 url-loader,而是会使用 file-loader,此时图片会被打包到 dist 目录
中,然后浏览器访问图片的那个样式会变成 background: url(xxxx.png),相当于在项目路径下找这个图片,那肯定是找不到的呀
为了让浏览器能找到,所以需要配置
- 在 webpack.config.js 中配置:
output:{
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js',
// 这里是在 output 中配置 publicPath,之后涉及到 url 的内容,都会在前面拼接上一个 dist
// 这样,大一些的图片也可以加载了
publicPath: 'dist/'
}
修改图片名字:
在 webpack.config.js 中进行配置,修改大图片打包之后的图片名字
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 20000,
// 在这里配置,生成的图片会在 dist/img 目录下,名字是图片名字.hash值前八位.图片后缀名
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
配置 vue
安装:npm install --save vue
配置:
- 在 main.js 中引入 vue
import Vue from 'vue'
const app = new Vue({
el: '#app',
data: {
message: 'Hello,世界'
}
})
- 在 webpack.config.js 配置 vue
resolve:{
alias:{
// vue 从这个文件中加载
'vue$':'vue/dist/vue.esm.js'
}
}
- 在 index.html 中使用,不需要引入 vue.js 了
<div id="app">
<h2>{{message}}</h2>
</div>
那之后总不能一直在 index.html 和 main.js 中写代码吧,不方便而且代码多了会很臃肿
于是就开始抽离,怎么抽离呢?
- 1.刚开始是把 index.html 中的内容全部写在 main.js 中
实现方法就是:在 main.js 中定义一个组件,然后在 vue 实例中直接注册使用组件就好了
import Vue from 'vue'
// 定义一个组件,完成之前 vue 实例中的代码
const App = {
template: `
<div>
<h2>{{message}}</h2>
<button @click='btn'>按钮</button>
</div>
`,
data(){
return{
message: 'Hello,世界'
}
},
methods:{
btn(){
console.log(this.message);
}
}
}
// 在 vue 实例中使用上面定义的组件
new Vue({
el: '#app',
// 在页面中使用子组件
template: '<App/>',
// 注册子组件
components:{
App
}
})
-
- index.html 是简洁了,但是 main.js 又变复杂了,下一步就是把 main.js 再变的简洁一些
实现方法就是:将子组件再抽离出去,然后将子组件导出,在 main.js 中导入子组件
子组件使用 .vue 文件,将页面展现(模板中的 html 代码)和数据(写在 data 那部分的 js 代码)分离
<template> <div> <h2>{{message}}</h2> <button @click='btn'>按钮</button> </div> </template> <script> export default{ name: 'App', data(){ return{ message: 'Hello,世界' } }, methods:{ btn(){ console.log(this.message); } } } </script> <style> </style>
main.js 导入并修改之后
import Vue from 'vue' import App from '../vue/App.vue' new Vue({ el: '#app', template: '<App/>', components:{ App } })
此时还需要导入两个 loader 才行
注意 vue 和 vue-template-compiler 版本保持一致(可以在 package.json 中查看版本信息)
npm install --save-dev vue-loader vue-template-compiler
在 webpack.config.js 中配置
// 引入这个模块 const VueLoaderPlugin = require('vue-loader/lib/plugin') // 在 module 的 rules 中配置 vue-loader { test:/\.vue$/, use:['vue-loader'] } // 在 module 同级引入这个插件,就是上面引入的模块 plugins: [ // 请确保引入这个插件! new VueLoaderPlugin() ]
运行项目,打开网页查看结果
npm run build
经历上面两个步骤,就可以在 vue 目录下创建 .vue 文件,然后在 App.vue 中导入使用
例如这里创建一个 MyView.vue
<template>
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div>
</template>
<script>
export default{
name: 'MyView',
data(){
return{
name:'刺客',
age:18
}
},
methods:{
btn(){
console.log(this.message);
}
}
}
</script>
<style>
</style>
在 App.vue 中导入
<template>
<div>
<h2>{{message}}</h2>
<button @click='btn'>按钮</button>
<!-- 使用,注意大写要变成 -小写 -->
<my-view></my-view>
</div>
</template>
<script>
// 导入
import MyView from './MyView.vue'
export default{
name: 'App',
// 注册
components:{
MyView
},
data(){
return{
message: 'Hello,世界'
}
},
methods:{
btn(){
console.log(this.message);
}
}
}
</script>
9.4 Plugin#
plugin 使用步骤:
- 安装插件
- 在 webpack.config.js 中配置
在文件头生成版权信息
在 webpack.config.js 中引入相关插件,并在其中的 plugins 中配置,然后打包发布之后
const webpack = require('webpack')
...中间省略...
new webpack.BannerPlugin('最终版权归xx所有')
htmlWebpackPlugin
发布项目的时候,发布的是打包在 dist 文件夹中的内容,很显然没有 index.html
而我们都是通过 index.html 引入的 js 文件,index.html 都没有,那那些 js 文件也就没用了
所以发布的时候也需要把 index.html 文件打包到 dist 文件夹中
安装
npm install --save-dev html-webpack-plugin@3.2.0
配置
const htmlwebpackplugin = require('html-webpack-plugin')
...中间省略...
new htmlwebpackplugin()
运行
npm run build
结果:dist 目录下会生成 index.html 文件,并自动引入 bundle.js
uglifyjs-webpack-plugin
压缩打包后的 js 代码
安装
npm install --save-dev uglifyjs-webpack-plugin@1.1.1
配置
const uglifyjswebpackplugin = require('uglifyjs-webpack-plugin')
...中间省略...
new uglifyjswebpackplugin()
运行
npm run build
结果:bundle.js 已经被压缩了,同时,BannerPlugin 注释的内容也会被删掉
搭建本地服务器#
实现"伪·热部署"
安装 npm install --save-dev webpack-dev-server@2.9.3
配置 在 webpack.config.js 中 module 的同级下配置
devServer:{
contentBase: './dist',
inline:true
}
10、混入#
Mixin:对于 data 和 methods 中定义的内容,如果混入和组件同名,优先选择组件中定义的内容
<div id="app">
<h2>{{message}}</h2>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<h2>{{author}}</h2>
</div>
<script src="../resources/vue.js"></script>
<script>
let mixin = {
data() {
return {
message: 'Hello,Mixin',
name: 'Mr.z',
age: '20'
}
},
methods:{
show(){
console.log(this.message,this.name,this.age)
},
showInfo(){
console.log("Haha~")
}
}
}
let app = new Vue({
el:'#app',
// 注册混入
mixins:[mixin],
data:{
message: 'Hello,Vue',
author: 'zxd'
},
created(){
this.show();
this.showInfo();
},
methods:{
show(){
console.log(this.message,this.author)
}
}
})
</script>
生命钩子函数:同名时,两者地生命周期钩子函数会被合并为数组,同时混入先调用,组件后调用
<div id="app">
</div>
<script src="../resources/vue.js"></script>
<script>
let mixin = {
created(){
console.log("Mixin,created~")
}
}
let app = new Vue({
el:'#app',
mixins:[mixin],
created(){
console.log("Vue,created~")
},
mounted() {
console.log("Vue,mounted~")
}
})
</script>