A、vue核心
一、vue基本使用
1、引入vue.js
2、创建vue对象
el:指定根element(选择器)
data:初始化数据(页面可以访问)
3、双向数据绑定:v-model
4、显示数据:{{}}
5、理解vue的mvvm实现
mvvm:
model:模型,数据对象(data)
view:视图,模板页面
viewModel:视图模型(vue的实例)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
<p>Hello {{message}}</p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
//创建 vue 实例
var app = new Vue({
el: "#app", //element 选择器
data: { //数据(model)
message: 'Hello Vue!'
}
})
</script>
</body>
</html>
二、模板语法
1、模板的理解:动态的html页面,包含了一些JS语法代码、双大括号表达式、指令(以v-开头的自定义标签属性)
2、双大括号表达式
● 语法:{{exp}}
● 功能:向页面输出数据
● 可以调用对象的方法
3、指令
● 强制数据绑定
功能:指定变化的属性
完整写法:v-bind:xxx=‘yyy’ //yyy会作为表达式解析执行
简洁写法: :xxx=‘yyy’
● 绑定事件监听
功能:绑定指定事件名的回调函数
完整写法:v-on:click = ‘xxx’
简洁写法:@click=‘xxx’
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>1、双大括号表达式</h2>
<p>{{msg}}</p>
<p>{{msg.toUpperCase()}}</p>
<p v-html="msg"></p> <!--textContent-->
<p v-text="msg"></p> <!--innerHTML-->
<h2>2、指令一:强制数据绑定</h2>
<img src="imgUrl" alt=""/>
<img v-bind:src="imgUrl" alt=""/>
<img :src="imgUrl" alt=""/>
<h2>3、指令二:绑定事件监听</h2>
<button v-on:click="test">test1</button>
<button @click="test2('abc')">test2</button>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
msg: '<a href="http://www.atguigu.com">I Will Back!</a>',
imgUrl: "https://cn.vuejs.org/images/logo.png"
},
methods: {
test(){
alert('hehe!')
},
test2(content){
alert(content)
}
}
})
</script>
</body>
</html>
三、计算属性与监视
● 计算属性
○ 在 computed 属性对象中定义计算属性的方法
○ 在页面中使用{{方法名}}来显示计算的结果
● 监视
○ 通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性
○ 当属性变化时, 回调函数自动调用, 在函数内部进行计算
● 计算属性高级
○ 通过 getter/setter 实现对属性数据的显示和监视
○ 计算属性存在缓存, 多次读取只执行一次 getter 计算
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="demo">
姓: <input type="text" placeholder="First Name" v-model="firstName"><br>
名: <input type="text" placeholder="Last Name" v-model="lastName"><br>
姓名 1(单向): <input type="text" placeholder="Full Name" v-model="fullName1"><br>
姓名 2(单向): <input type="text" placeholder="Full Name" v-model="fullName2"><br>
姓名 3(双向): <input type="text" placeholder="Full Name2" v-model="fullName3"><br>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Kobe',
lastName: 'bryant',
fullName2: 'Kobe bryant'
},
computed: {
fullName1: function () {
return this.firstName + " " + this.lastName
},
fullName3: {
get: function () {
return this.firstName + " " + this.lastName
},
set: function (value) {
var names = value.split(' ')
this.firstName = names[0]
this.lastName = names[1]
}
}
},
watch: {
lastName: function (newVal, oldVal) {
this.fullName2 = this.firstName + ' ' + newVal
}
}
})
vm.$watch('firstName', function (val) {
this.fullName2 = val + ' ' + this.lastName
})
</script>
</body>
</html>
四、class 与 style 绑定
1、理解
● 在应用界面中, 某个(些)元素的样式是变化的
● class/style 绑定就是专门用来实现动态样式效果的技术
2、class绑定
● :class=‘xxx’
● 表达式是字符串: ‘classA’
● 表达式是对象: {classA:isA, classB: isB}
● 表达式是对象: {classA:isA, classB: isB}
3、style绑定
● :style="{ color: activeColor, fontSize: fontSize + ‘px’ }"
● 其中 activeColor/fontSize 是 data 属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.classA {
color: red;
}
.classB {
background: blue;
}
.classC {
font-size: 50px;
}
</style>
</head>
<div id="demo">
<h2>1. class 绑定: :class='xxx'</h2>
<p class="classB" :class="a">表达式是字符串: 'classA'</p>
<p :class="{classA: isA, classB: isB}">表达式是对象: {classA:isA, classB: isB}</p>
<p :class="['classA', 'classC']"> 表达式是数组: ['classA', 'classB']</p>
<h2>2. style 绑定</h2>
<p :style="{color, fontSize}">style="{ color: activeColor, fontSize: fontSize +
'px' }"
</p>
<button @click="update">更新</button>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el : '#demo',
data : {
a: 'classA',
isA: true,
isB: false,
color: 'red',
fontSize: '20px'
},
methods : {
update () {
this.a = 'classC'
this.isA = false
this.isB = true
this.color = 'blue'
this.fontSize = '30px'
}
}
})
</script>
</body>
</html>
五、条件渲染
1、条件渲染指令
● v-if 与 v-else
● v-show
2、比较 v-if 与 v-show
● 如果需要频繁切换 v-show 较好
● 当条件不成立时, v-if 的所有子节点不会解析(项目中使用)
● v-if 的条件如果为 false,那么元素不会出现,并且也不会占用位置,而 v-show 为false时,元素不会出现,
但是会占用位置,相当于 display:none
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="demo">
<h2 v-if="ok">表白成功</h2>
<h2 v-else>表白失败</h2>
<h2 v-show="ok">求婚成功</h2>
<h2 v-show="!ok">求婚失败</h2>
<br>
<button @click="ok=!ok">切换</button>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#demo',
data: {
ok: false
}
})
</script>
</body>
</html>
六、列表渲染
1、列表显示指令
● 数组: v-for / index
● 对象: v-for / key
2、列表的更新显示
● 删除 item
● 替换 item
3、 列表的高级处理
● 列表过滤
● 列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="demo">
<h2>测试: v-for 遍历数组</h2>
<ul>
<li v-for="(p, index) in persons" :key="index">
{{index}}--{{p.name}}--{{p.age}}
--
<button @click="deleteItem(index)">删除</button>
--
<button @click="updateItem(index, {name:'Jok',age:15})">更新</button>
</li>
</ul>
<h2>测试: v-for 遍历对象</h2>
<ul>
<li v-for="(value, key) in persons[0]">
{{ key }} : {{ value }}
</li>
</ul>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
persons: [
{id: 1, name: 'Tom', age: 13},
{id: 2, name: 'Jack', age: 12},
{id: 3, name: 'Bob', age: 14}
]
},
methods: {
deleteItem(index) {
this.persons.splice(index, 1)
},
updateItem(index, p) {
// this.persons[index] = p // 页面不会更新
this.persons.splice(index, 1, p)
}
}
})
</script>
</body>
</html>
列表的搜索与排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="demo">
<input type="text" name="searchName" placeholder="搜索指定用户名"
v-model="searchName">
<ul>
<li v-for="(p, index) in filterPerson" :key="index">
{{index}}--{{p.name}}--{{p.age}}
</li>
</ul>
<button @click="setOrderType(1)">年龄升序</button>
<button @click="setOrderType(2)">年龄降序</button>
<button @click="setOrderType(0)">原本顺序</button>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
orderType: 0, //0 代表不排序, 1 为升序, 2 为降序
searchName: '',
persons: [
{id: 1, name: 'Tom', age: 13},
{id: 2, name: 'Jack', age: 12},
{id: 3, name: 'Bob', age: 17},
{id: 4, name: 'Cat', age: 14},
{id: 4, name: 'Mike', age: 14},
{id: 4, name: 'Monica', age: 16}
]
},
methods: {
setOrderType (orderType) {
this.orderType = orderType
}
},
computed: {
filterPerson() {
let {orderType, searchName, persons} = this
// 过滤
persons = persons.filter(p => p.name.indexOf(searchName)!=-1)
// 排序
if(orderType!==0) {
persons = persons.sort(function (p1, p2) {
if(orderType===1) {
return p1.age-p2.age
} else {
return p2.age-p1.age
}
})
}
return persons
}
}
})
</script>
</body>
</html>
七、事件处理
1、绑定监听
● v-on:xxx=“fun”
● @xxx=“fun”
● @xxx=“fun(参数)”
● 默认事件形参: event
● 隐含属性对象: $event
2、事件修饰符
● prevent : 阻止事件的默认行为 event.preventDefault()
● stop : 停止事件冒泡 event.stopPropagation()
3、按键修饰符
● keycode : 操作的是某个 keycode 值的键
● keyName : 操作的某个按键名的键(少部分)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="example">
<h2>1. 绑定监听</h2>
<button v-on:click="test1">Greet</button>
<button @click="test1">Greet2</button>
<button @click="test2($event, 'hello')">Greet3</button>
<h2>2. 事件修饰符</h2>
<!-- 阻止事件默认行为 -->
<a href="http://www.baidu.com" @click.prevent="test3">百度一下</a>
<br/>
<br/>
<!-- 停止事件冒泡 -->
<div style="width: 200px;height: 200px;background: red" @click="test4">
<div style="width: 100px;height: 100px;background: green"
@click.stop="test5"></div>
</div>
<h2>3. 按键修饰符</h2>
<input @keyup.8="test6">
<input @keyup.enter="test6">
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#example',
data: {
name: 'Vue.js'
},
methods: {
test1 (event) {
// 方法内 `this` 指向 vm
// alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
alert(event.target.innerHTML)
},
test2 (event, msg) {
alert(event.target.innerHTML + '---' + msg)
},
test3() {
alert('阻止事件的默认行为')
},
test4() {
alert('out')
},
test5() {
alert('inner')
},
test6(event) {
alert(event.keyCode + '---' + event.target.value)
}
}
})
</script>
</body>
</html>
八、表单输入绑定
1、使用 v-model 对表单数据自动收集
● text/textarea
● checkbox
● radio
● select
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="demo">
<form @submit.prevent="handleSubmit">
<span>用户名: </span>
<input type="text" v-model="user.username"><br>
<span>密码: </span>
<input type="password" v-model="user.pwd"><br>
<span>性别: </span>
<input type="radio" id="female" value="female" v-model="user.sex">
<label for="female">女</label>
<input type="radio" id="male" value="male" v-model="user.sex">
<label for="male">男</label><br>
<span>爱好: </span>
<input type="checkbox" id="basket" value="basketball"
v-model="user.likes">
<label for="basket">篮球</label>
<input type="checkbox" id="foot" value="football"
v-model="user.likes">
<label for="foot">足球</label>
<input type="checkbox" id="pingpang" value="pingpang"
v-model="user.likes">
<label for="pingpang">乒乓</label><br>
<span>城市: </span>
<select v-model="user.cityId">
<option value="">未选择</option>
<option v-for="city in allCitys" :value="city.id">
{{ city.name }}
</option>
</select><br>
<span>介绍: </span>
<textarea v-model="user.desc" rows="10"></textarea><br><br>
<input type="submit" value="注册">
</form>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#demo',
data: {
user: {
username: '',
pwd: '',
sex: 'female',
likes: [],
cityId: '',
desc: '',
},
allCitys: [{id: 1, name: 'BJ'}, {id: 2, name: 'SZ'},{id: 4, name:
'SH'}],
},
methods: {
handleSubmit (event) {
alert(JSON.stringify(this.user))
}
}
})
</script>
</body>
</html>
九、vue的生命周期
vue的生命周期有三个阶段:初始化、更新、死亡,每个阶段都有一些生命周期回调函数(钩子函数)
生命周期方法:
beforeCreate:初始化之前调用,不是创建之前,调用这个方式时vm对象已经创建了
created:初始化之后调用
beforeMount:挂载之前调用
挂载的意思:假如页面中有多个相同的标签需要将同一个数据显示,vm并不会一个一个进行赋值更新,而
是将页面的模型先不发生改变,将相同的标签中的数据全部先读取到内存中,然后再从内存中将数据
全部更新完,更新完以后再一次性批量显示到页面,这个过程就叫做挂载
mounted:挂载之后调用
以上方法调用完成之后,生命周期的第一个阶段(初始化)就完成了
beforeUpdate:更新之前调用
updated:更新之后调用
以上两个方法就是vue的更新阶段的生命周期方法
beforeDastroy:更新阶段之后,销毁vm之前调用
destroyed:销毁vm之后调用
这两个方法是vue的死亡阶段的生命周期方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="test">
<button @click="destroyVM">destroy vm</button>
<p v-show="isShow">尚硅谷IT教育</p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#test',
data: {
isShow: true
},
methods: {
destroyVM(){
//干掉vm
this.$destroy()
}
},
//初始化阶段
beforeCreate(){
console.log('beforeCreate')
},
created(){
console.log('created')
},
beforeMount(){
console.log('beforeMounted')
},
mounted() { //初始化显示之后立即调用(调用1次)
this.intervalId = setInterval(() => {
this.isShow = !this.isShow
}, 1000)
},
//初始化阶段
beforeUpdate(){
console.log('beforeUpdate')
},
updated(){
console.log('updated')
},
//销毁阶段
//销毁vm之前调用(调用1次)
beforeDestroy(){
//清除定时器
clearInterval(this.intervalId)
},
destroyed(){
console.log("destroyed")
}
})
</script>
</body>
</html>
十、vue动画
1、vue动画的理解
操作css的trasition或animation;vue会给目标元素添加/移除特定的class
2、基本过渡动画的编码
● 在目标元素外包裹<transition name=“xxx”>
● 定义class样式
○ 指定过渡样式:transition
○ 指定隐藏时的样式:opacity/其他
3、过渡的类名
● xxx-enter-active:指定显示的transition
● xxx-leave-active:指定隐藏的transition
● xxx-enter:指定显示之前隐藏时的样式
● .xxx-leave-to:指定显示之后隐藏时的样式
注意:xxx和上面<transition name=“xxx”>中的xxx一样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
/*显示隐藏的过渡效果*/
.xxx-enter-active, .xxx-leave-active{
transition: opacity 1s;
}
/*隐藏时的样式*/
.xxx-enter, .xxx-leave-to{
opacity: 0;
}
/*显示的过渡效果, 显示时间是1秒*/
.yyy-enter-active {
transition: all;
}
/*隐藏的过渡效果, 隐藏时间是3秒*/
.yyy-leave-active {
transition: all 3s;
}
/*隐藏时的样式*/
.yyy-enter, .yyy-leave-to{
opacity: 0;
transform: translateX(20px);
}
</style>
</head>
<body>
<div id="test">
<button @click="isShow=!isShow">toggle</button>
<transition name="xxx">
<p v-show="isShow">hello</p>
</transition>
</div>
<div id="test2">
<button @click="isShow=!isShow">toggle</button>
<transition name="yyy">
<p v-show="isShow">hello</p>
</transition>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#test',
data() {
return {
isShow: true
}
}
})
new Vue({
el: '#test2',
data() {
return {
isShow: true
}
}
})
</script>
</body>
</html>
十一、过滤器
1、理解
● 功能:对要显示的数据进行特定格式化后再显示
● 注意:并没有改变原本的数据,可是产生新的对应的数据
2、定义和使用过滤器
● 定义过滤器
Vue.filter(filterName, function(value[,arg1, arg2, …]){
//进行一定的数据处理
return newValue;
})
● 使用过滤器
<div>{{myData | filerName}}</div>
<div>{{myData | filterName(arg)}}</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--需求:对当前时间进行指定格式显示-->
<div id="test">
<h2>显示格式化的日期时间</h2>
<p>{{date}}</p>
<!--date表示要显示的数据,一般为vue实例中data中定义的数据,dateStr表示过滤器的名字-->
<p>完整版:{{date | dateStr}}</p>
<!--年月日-->
<p>{{date | dateStr('YYYY-MM-DD')}}</p>
<!--时分秒-->
<p>{{date | dateStr('HH:mm:ss')}}</p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/moment.js/2.24.0/moment.js"></script>
<script type="text/javascript">
//自定义过滤器, value是将要进行格式化的数据
Vue.filter('dateStr', function (value, format) {
return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss')
})
new Vue({
el: '#test',
data: {
date: new Date()
}
})
</script>
</body>
</html>
十二、vue指令
1、常用内置指令
● v:text : 更新元素的 textContent
● v-html : 更新元素的 innerHTML
● v-if : 如果为 true, 当前标签才会输出到页面
● v-else: 如果为 false, 当前标签才会输出到页面
● v-show : 通过控制 display 样式来控制显示/隐藏
● v-for : 遍历数组/对象
● v-on : 绑定事件监听, 一般简写为@
● v-bind : 强制绑定解析表达式, 可以省略 v-bind
● v-model : 双向数据绑定
● ref : 指定唯一标识, vue 对象通过$refs 属性访问这个元素对象
● v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none } 。解决问题:如果请求的数据很慢,那么在请求数
据还没有到达页面时,页面会显示{{msg}}这样的符号,为了解决这个问题,给标签添加一个 v-cloak属性,
然后利用属性选择器添加一个display: none 的样式,让数据没回来时不显示{{msg}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div id="example">
<p ref="content">atguigu.com</p>
<button @click="hint">提示</button>
<p v-cloak>{{msg}}</p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#example',
data: {
msg: '李鹏'
},
methods: {
hint(){
alert(this.$refs.content.textContent)
}
}
})
</script>
</body>
</html>
2、自定义指令
● 注册全局指令
Vue.directive(‘my-directive’, function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
● 注册局部指令
directives : {
‘my-directive’ : {
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
}
● 使用指令
v-my-directive='xxx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
需求:自定义2个指令
1、功能类似于v-text,但转换为全大写 v-upper-text
2、功能类似于v-text,但转换为全小写 v-lower-text
-->
<div id="test1">
<p v-upper-text="msg1"></p>
<p v-lower-text="msg1"></p>
</div>
<div id="test2">
<p v-upper-text="msg2"></p>
<p v-lower-text="msg2"></p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
//定义全局指令
//el: 指令属性所在的标签对象
//binding: 包含指令相关信息数据的对象
Vue.directive('upper-text', function (el, binding) {
el.textContent = binding.value.toUpperCase()
})
new Vue({
el: '#test1',
data: {
msg1: 'NBA I Love This Game!'
},
directives: { //注册局部指令, 只在当前vm管理的范围内有效
'lower-text': function (el, binding) {
el.textContent = binding.value.toLowerCase()
}
}
})
new Vue({
el: '#test2',
data: {
msg2: 'Just Do It!'
}
})
</script>
</body>
</html>
十三、vue插件
1、首先参照官网定义插件,下面是一个简单的插件
/**
* Created by 86130 on 2019/11/14.
*/
(function () {
const MyPlugin = {};
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
console.log('Vue函数对象的方法myGlobalMethod()')
}
// 2. 添加全局资源
Vue.directive('my-directive', function (el, binding) {
el.textContent = binding.value.toUpperCase()
})
// 3. 注入组件选项
// Vue.mixin({
// created: function () {
// // 逻辑...
// }
//
// })
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
console.log('Vue实例对象的方法$myMethod()')
}
}
//向外暴露
window.MyPlugin = MyPlugin
})()
2、使用上上面定义的插件,使用插件之前,需要使用Vue.use(‘插件名’)来声明使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="test">
<p v-my-directive="msg"></p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./vue-myPlugin.js"></script>
<script type="text/javascript">
//声明使用插件
Vue.use(MyPlugin) //内部会执行MyPlugin.install(Vue)
Vue.myGlobalMethod()
const vm = new Vue({
el: '#test',
data: {
msg: 'I Like You!'
}
})
vm.$myMethod()
</script>
</body>
</html>
B、vue组件化编码
一、使用vue-cli创建模板项目
1、说明
● vue-cli 是 vue 官方提供的脚手架工具
● 地址:github:https://github.com/vuejs/vue-cli 是一个js库
● 作用:从 https://github.com/vuejs-templates 下载模板项目
2、创建vue项目
● 全局安装脚手架:npm install -g vue-cli
安装完成以后会多一个 vue 的命令,后面步骤会用上这个命令
● 下载模板项:vue init webpack vue_demo
○ webpack:模板中的一种,一共有6种模板,webpack只是其中的一种,意思相当于可以使用webpack打
包;
○ vue_demo:表示创建的项目的名字,可以自己定义
○ 运行这条命令会有一些选项
■ 第一个:因每个项目中都有一个package.json,这个选项是确认package.json中的name叫什么,注
意这个name不能使用大写,可以使用下划线
■ 第二个:确认描述,可以使用默认值
■ 第三个:确认作者,可以使用默认值
■ 第四个:是否使用vue-router(vue路由),先可以暂时不用,输入n即可
■ 第五个:是否使用ESLint,可以选择y
■ 第六个:是否使用单元测试的库,选择n
■ 第七个:直接选择n
■ 第八个:选择下载方式,可以是npm下载、yarn下载或手工下载,选择手工下载,后面的步骤参考
下面的笔记
● 进入自己创建的项目的目录里面:cd vue_demo
● 安装:npm install
● 运行:npm run dev
● 访问:http://localhost:8080/
二、模板项目的结构
|-- build : webpack 相关的配置文件夹(基本不需要修改)
|-- dev-server.js : 通过 express 启动后台服务器
|-- config: webpack 相关的配置文件夹(基本不需要修改)
|-- index.js: 指定的后台服务的端口号和静态资源文件夹
|-- node_modules
|-- src : 源码文件夹
|-- components: vue 组件及其相关资源文件夹
|-- App.vue: 应用根主组件
|-- main.js: 应用入口 js,在build目录下webpack.base.conf.js中的module.exports→entry→app配置名称
|-- static: 静态资源文件夹
|-- .babelrc: babel 的配置文件,ES6转ES5的配置文件,rc:runtime control
|-- .eslintignore: eslint 检查忽略的配置,配置忽略eslint检查的文件
|-- .eslintrc.js: eslint 检查的配置
|-- .gitignore: git 版本管制忽略的配置
|-- index.html: 主页面文件
|-- package.json: 应用包配置文件
|-- README.md: 应用描述说明的 readme 文件
三、HelloWorld
1、在src下创建main.js作为主入口js文件
2、在src下创建App.vue作为根组件,它的结构基本固定。写完代码后可能会报错,可以把eslint、jslint、jshint都
关闭,打开settings,搜索上面的三个关键字,把前面的enable勾去掉即可
3、在src下创建components目录,用于存放其他组件。由于组件的结构基本固定,我们可以创建一个模板,创建
模板的步骤:打开settings→Editor→File and Code Templates→名字为vue,或者也是vue,不要加点,模
板如下
<template>
<div>
</div>
</template>
<script>
export default{
}
</script>
<style>
</style>
4、在src/components目录下创建一个HelloWorld.vue,暴露的data必须是函数
<template>
<div>
<p class="msg">{{msg}}</p>
</div>
</template>
<script>
export default{ //配置对象(与vue一致)
data() { //必须写函数
return {
msg: 'Hello Vue Component'
}
}
}
</script>
<style>
.msg{
color: red;
font-size: 30px;
}
</style>
5、在App.vue中引入组件、映射组件标签、使用组件标签,以及也可以将自己组件中的标签添加样式
<template>
<div>
<img class="logo" src="./assets/logo.png" alt="logo"/>
<!--3、使用组件标签-->
<HelloWorld/>
</div>
</template>
<script>
//1、引入组件
import HelloWorld from './components/HelloWorld.vue'
export default{
//2、映射组件标签
components: {
HelloWorld
}
}
</script>
<style>
.logo{
width: 200px;
height: 200px;
}
</style>
6、在main.js主入口js文件中创建vue实例,首先要引入vue,但是格式只能是 import Vue from ‘vue’ 这种格式。
其次引入根组件,使用components映射成标签。然后用template使用根组件标签
/*
* 创建Vue实例
* */
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app', //跟主页面中的模块id一致
components: {
App
},
template: '<App/>'
})
7、使用 npm run dev 运行,然后访问
四、项目的打包与发布
1、打包
上面的方式没有生成打包文件,使用 npm run build 打包
2、发布
● 使用静态服务器工具包
○ npm install -g serve
○ serve dist ----dist是第1步中打包生成的目录
○ 访问:使用serve dist 命令后会打印出一个地址,我们通过这个地址访问。http://localhost:5000
● 使用动态web服务器(tomcat)
○ 修改 webpack.prod.conf.js
output: {
publicPath: ‘/xxx/’ //打包文件夹的名称,如:vue_demo
}
○ 重新打包:npm run build
○ 修改 dist 文件夹为项目名称: xxx
○ 将 xxx 拷贝到运行的 tomcat 的 webapps 目录下
○ 访问: http://localhost:8080/xxx
五、eslint编码规范检查
1、说明
● ESList是一个代码规范检查工具
● 它定义了很多特定的规则,一旦你的代码违背了某一规则,eslist会作出非常有用的提示
● 官网:http://eslint.org/
● 基本已替代以前的 JSList
2、ESLint提供以下支持
● ES
● JSX
● style 检查
● 自定义错误和提示
3、ESLint 提供以下几种校验
● 语法错误校验
● 不重要或丢失的标点符号, 如分号
● 没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
● 未被使用的参数提醒
● 确保样式的统一规则, 如 sass 或者 less
● 检查变量的命名
4、规则的错误等级有三种
● 0 或 off: 关闭规则
● 1: 打开规则, 并且作为一个警告(信息打印黄色字体)
● 2: 打开规则, 并且作为一个错误(信息打印红色字体)
5、相关配置文件
● .eslintrc.js : 全局规则配置文件
‘rules’: {
‘no-new’: 1
}
● 在 js/vue 文件中修改局部规则
● .eslintignore: 指令检查忽略的文件
*.js
*.vue
六、组件定义与使用
1、vue 文件的组成(3 个部分)
● 模板页面
<template>
页面模板
</template>
● JS 模块对象
<script>
export default {
data() {return {}},
methods: {},
computed: {},
components: {}
}
</script>
● 样式
<style>
样式定义
</style>
2、基本使用
● 引入组件
● 映射成标签
● 使用组件标签
<template>
//使用组件标记
<HelloWorld></HelloWorld>
<hello-world></hello-world>
</template>
<script>
//引入组件
import HelloWorld from './components/HelloWorld'
//映射成标签
export default {
components: {
HelloWorld
}
}
</script>
3、关于标签名与标签属性名书写问题
● 写法一: 一模一样
● 写法二:大写变小写, 并用-连接
七、组件间通信
1、组件间通信基本原则
● 不要在子组件中直接修改父组件的状态数据
● 数据在哪, 更新数据的行为(函数)就应该定义在哪
2、vue 组件间通信方式
A、props
● 使用组件标签时
<my-component name='tom' :age='3' :set-name='setName'></my-component>
● 定义 MyComponent 时
○ 在组件内声明所有的 props
■ 方式一: 只指定名称
props: ['name', 'age', 'setName']
■ 方式二: 指定名称和类型
props: {
name: String,
age: Number,
setNmae: Function
}
■ 方式三: 指定名称/类型/必要性/默认值
props: {
name: {type: String, required: true, default:xxx},
}
● 注意
○ 此方式用于父组件向子组件传递数据
○ 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
○ 问题:
■ 如果需要向非子后代传递数据必须多层逐层传递
■ 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以
B、vue 的自定义事件
● 绑定事件监听
// 方式一: 通过 v-on 绑定, 在父组件中使用子组件的标签上添加下面的属性
@delete_todo="deleteTodo"
// 方式二: 通过$on()
this.$refs.xxx.$on('delete_todo', function (todo) {
this.deleteTodo(todo)
})
● 触发事件
// 触发事件(只能在父组件中接收), 在子组件中触发上面绑定的监听函数
this.$emit(eventName, data)
● 注意:
○ 此方式只用于子组件向父组件发送消息(数据)
○ 问题: 隔代组件或兄弟组件间通信此种方式不合适
C、消息订阅与发布(PubSubJS 库)
● 下载安装 pubsubjs库
npm install --save pubsub-js
● 订阅消息
import PubSub from 'pubsub-js' //哪个组件使用就从哪个组件导入
PubSub.subscribe('msg111', function(msg, data){})
● 发布消息
PubSub.publish('msg111', data)
● 注意
优点: 此方式可实现任意关系组件间通信(数据)
● 事件的 2 个重要操作(总结)
○ 绑定事件监听 (订阅消息)
目标: 标签元素
事件名(类型): click/focus
回调函数: function(event){}
○ 触发事件 (发布消息)
DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发
D、slot
● 理解
此方式用于父组件向子组件传递`标签数据`
● 子组件: Child.vue
<template>
<div>
<slot name="xxx">不确定的标签结构 1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构 2</slot>
</div>
</template>
● 父组件: Parent.vue
注意:这种方式所有关于子组件中的代码都需要放入到父组件中编写
<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>
E、vuex(后面单独讲)
八、鼠标移入移出事件区别
鼠标移入元素有两个事件:onmouseenter、onmouseover
鼠标移出元素有两个事件:onmouseleave、onmouseout
他们的区别:当有一个大div中包含一个小div时,当鼠标移入大div时都会触发 onmouseenter、onmouseover
,当从大div中移入小div时,会触发大div的 onmouseout;当从小div中移出到大div时会触发onmouseover,
移出大div时都会触发onmouseleave、onmouseout
九、存储数据
数据存储,以便浏览器关闭后,下次再打开时还能看到之前的数据,使用 window.localStorage 进行数据存储
获取数据:
window.localStorage.getItem('tasks_key')
设置数据:
window.localStorage.setItem('tasks_key', value)
C、vue_ajax
一、vue 项目中常用的 2 个 ajax 库
1、vue-resource
vue 插件, 非官方库, vue1.x 使用广泛
2、axios
通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛
二、vue-resource 的使用
1、在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
2、下载
npm install vue-resource --save
3、下载后,在main.js(主入口js文件)中引入vue-resource,然后在main.js中声明使用插件。声明以后,内部会给
vm对象和组件对象添加一个属性:$http,它里面有两个方法:get()和post()
import VueResource from 'vue-resource'
//声明使用插件
Vue.use(VueResource)
4、编码,请求一般放在 mounted() 声明周期方法中
// 引入模块
import VueResource from 'vue-resource'
// 使用插件
Vue.use(VueResource)
// 通过 vue/组件对象发送 ajax 请求
this.$http.get(url).then((response) => {
// success callback
console.log(response.data) //返回结果数据
}, (response) => {
// error callback
console.log(response.statusText) //错误信息
})
三、axios 的使用
1、axios 的使用
2、在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
3、下载
npm install axios --save
4、编码
导入 axios 时,哪个组件使用就从哪个组件中导入
// 引入模块
import axios from 'axios'
// 发送 ajax 请求
axios.get(url)
.then(response => {
console.log(response.data) // 得到返回结果数据
})
.catch(error => {
console.log(error.message)
})
D、VUE UI组件库
一、常用
● Mint UI
○ 主页:http://mint-ui.github.io/#!/zh-cn
○ 说明:饿了么开源的基于 vue 的移动端 UI 组件库
● Elment
○ 主页:http://element-cn.eleme.io/#/zh-CN
○ 说明:饿了么开源的基于 vue 的 PC 端 UI 组件库
二、使用Mint UI
1、安装
npm install --save mint-ui
2、实现按需打包
1. 下载
npm install --save-dev babel-plugin-component
2. 修改 babel 配置
"plugins": ["transform-runtime",["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]]
3、mint-ui 组件分类
● 标签组件
● 非标签组件
4、使用 mint-ui 的组件
● index.html
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,
minimum-scale=1, user-scalable=no" />
<!-- 引入实现快速点击的库 -->
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
if(!window.Promise) {
document.writeln('<script
src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"
'+'>'+'<'+'/'+'script>');
}
</script>
● main.js
import {Button} from 'mint-ui'
Vue.component(Button.name, Button)
● App.vue
<template>
<mt-button @click="handleClick" type="primary" style="width: 100%">Test</mt-button>
</template>
<script>
import {Toast} from 'mint-ui'
export default {
methods: {
handleClick () {
Toast('点击了测试');
}
}
}
</script>
E、vue-router
一、理解
1、说明
● 官方提供的用来实现 SPA 的 vue 插件
● github: https://github.com/vuejs/vue-router
● 中文文档: http://router.vuejs.org/zh-cn/
● 下载: npm install vue-router --save
2、相关 API 说明
● VueRouter(): 用于创建路由器的构建函数
new VueRouter({
// 多个配置项
})
● 路由配置
routes: [
{ // 一般路由
path: '/about',
component: About
},
{ // 自动跳转路由
path: '/',
redirect: '/about'
}
]
● 注册路由器
import router from './router'
new Vue({
router
})
● 使用路由组件标签
○ : 用来生成路由链接
Go to XXX
○ : 用来显示当前路由组件界面
二、基本路由
1、在src下面创建App.vue、main.js以及两个文件夹,一个文件夹放子组件,一般我们命名为 views或pages,一
个文件夹用于存放路由器的配置文件,命名为 router,路由器配置文件命名为 index.js
2、在主入口main.js中引入router
import Vue from 'vue'
import App from './App.vue'
import router from './router'
new Vue({ //配置对象的属性名都是一些确定的名称,不能随便修改
el: '#app',
components: {App},
template: '<App/>',
router
})
3、在router目录下的index.js配置路由,需要引入vue-router,然后声明使用,暴露需要用到的路由,注意:这里
有一个默认的路由,使用redirect,当鼠标没有点击时会出现默认的路由,
/*
* 路由器模块
* */
import Vue from 'vue'
import VueRouter from 'vue-router'
import About from '../views/About.vue'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
export default new VueRouter({
//n个路由
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
},
{
path: '/',
redirect: '/about'
}
]
})
4、在主组件App.vue中使用路由连接以及渲染当前路由组件
<!--路由链接-->
<router-link to="/about" class="routerLink">About</router-link>
<!--用于渲染当前路由组件-->
<router-view></router-view>
5、可以使用 .router-link-active 这个样式改变当前选择的路由,这个样式在选择路由的时候会默认加进去。
除了 .router-link-active 这个样式以外,还可以使用 linkActiveClass: ‘active’,//指定选择的路由连接的class
.router-link-active{
color: red !important;
}
三、嵌套路由
嵌套路由和前面的基本路由差不多,只是要在配置路由的index.js中配置子路由
/**
* Created by 86130 on 2019/11/17.
*/
/*
* 路由器模块
* */
import Vue from 'vue'
import VueRouter from 'vue-router'
import About from '../views/About.vue'
import Home from '../views/Home.vue'
import News from '../views/News.vue'
import Messages from '../views/Messages.vue'
import Title from '../views/Title.vue'
Vue.use(VueRouter)
export default new VueRouter({
//n个路由
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
//配置嵌套路由
children: [
{
path: '/home/news', //path前面的斜杠永远代表根路径
component: News
},
{
path: 'messages',
component: Messages,
children: [
{
path: 'title',
component: Title
}
]
},
{
path: '',
component: News
}
]
},
{
path: '/',
redirect: '/about'
}
]
})
四、缓存路由组件对象
1、理解
● 默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的
● 如果可以缓存路由组件对象, 可以提高用户体验
2、编码
<keep-alive>
<router-view></router-view>
</keep-alive>
五、向路由组件传递数据
方式 1: 路由路径携带参数(param/query)
1、配置路由
children: [
{
path: 'mdetail/:id',
component: MessageDetail
}
]
2、路由路径
<router-link :to="'/home/message/mdetail/'+m.id">{{m.title}}</router-link>
或者
<router-link :to="`/home/message/mdetail/${m.id}`">{{m.title}}</router-link>
如果使用query的方式,那么使用路由路径如下
<router-link :to="`/home/message/mdetail?id=${m.id}`">{{m.title}}</router-link>
3、路由组件中读取请求参数
this. r o u t e . p a r a m s . i d 或 者 t h i s . route.params.id 或者 this. route.params.id或者this.route.query.id
方式 2: 属性携带数据
使用组件标签上面传递数据,方式和vue的props组件传递数据一致
<router-view :msg="msg"></router-view>
在路由组件使用props接收数据
六、编程式路由导航
1、相关 API
● this.$router.push(path): 相当于点击路由链接**(可以返回到当前路由界面)**
● this.$router.replace(path): 用新路由替换当前路由**(不可以返回到当前路由界面)**
● this.$router.back(): 请求(返回)上一个记录路由
● this.$router.go(-1): 请求(返回)上一个记录路由
● this.$router.go(1): 请求下一个记录路由
2、编码
<template>
<div>
<ul>
<li v-for="message in messages" :key="message.id">
<router-link :to="`/home/messages/title/${message.id}`">{{message.title}}</router-link>
<!-- 点击下面两个按钮调用$router的方法 -->
<button @click="pushShow(message.id)">push查看</button>
<button @click="replaceShow(message.id)">replace查看</button>
</li>
</ul>
<button @click="$router.back()">回退</button>
<div>
<router-view :messages="messages"></router-view>
</div>
</div>
</template>
<script>
export default{
data() {
return {
messages: []
}
},
methods: {
pushShow(id){
// this.$router.push(`/home/messages/title/${id}`)
this.$router.push(`/login`)
},
replaceShow(id){
this.$router.replace(`/home/messages/title/${id}`)
}
},
mounted(){
setTimeout(() => {
const messages = [
{
id: 1,
title: 'message001'
},
{
id: 2,
title: 'message002'
},
{
id: 3,
title: 'message003'
}
]
this.messages = messages
}, 2000)
}
}
</script>
<style>
</style>
F、vue 源码分析
一、说明
1、分析 vue 作为一个 MVVM 框架的基本实现原理包含
● 数据代理
● 模板解析
● 数据绑定
2、不直接看 vue.js 的源码
3、剖析 github 上某基友仿 vue 实现的 mvvm 库
4、地址: https://github.com/DMQ/mvvm
二、准备知识
● [].slice.call(lis): 将伪数组转换为真数组
● node.nodeType: 得到节点类型
● Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符)
○ configurable: true/false 是否可以重新 define
○ enumerable: true/false 是否可以枚举(for…in / keys())
○ value: 指定初始值
○ writable: true/false value 是否可以修改
○ get: 回调函数, 用来得到当前属性值
○ set: 回调函数, 用来监视当前属性值的变化
● Object.keys(obj): 得到对象自身可枚举的属性名的数组
● DocumentFragment: 文档碎片(高效批量更新多个节点)
● obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="test">尚硅谷IT教育</div>
<ul id="fragment_test">
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
<!--
1. [].slice.call(lis): 将伪数组转换为真数组
2. node.nodeType: 得到节点类型
3. Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符)
4. Object.keys(obj): 得到对象自身可枚举属性组成的数组
5. obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性
6. DocumentFragment: 文档碎片(高效批量更新多个节点)
-->
<script type="text/javascript">
//1. [].slice.call(lis): 根据伪数组生成对应的真数组
const lis = document.getElementsByTagName('li') // lis是伪数组(是一个特别的对象, length和数值下标属性)
console.log(lis instanceof Object, lis instanceof Array)
// 数组的slice()截取数组中指定部分的元素, 生成一个新的数组 [1, 3, 5, 7, 9], slice(0, 3)
// slice2()
Array.prototype.slice2 = function (start, end) {
start = start || 0
end = start || this.length
const arr = []
for (var i = start; i < end; i++) {
arr.push(this[i])
}
return arr
}
const lis2 = Array.prototype.slice.call(lis) // lis.slice()
console.log(lis2 instanceof Object, lis2 instanceof Array)
// lis2.forEach()
//2. node.nodeType: 得到节点类型
const elementNode = document.getElementById('test')
const attrNode = elementNode.getAttributeNode('id')
const textNode = elementNode.firstChild
console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
//3. Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符)
const obj = {
firstName: 'A',
lastName: 'B'
}
//obj.fullName = 'A-B'
Object.defineProperty(obj, 'fullName', {
// 属性描述符:
// 数据描述符
//访问描述符
// 当读取对象此属性值时自动调用, 将函数返回的值作为属性值, this为obj
get () {
return this.firstName + "-" + this.lastName
},
// 当修改了对象的当前属性值时自动调用, 监视当前属性值的变化, 修改相关的属性, this为obj
set (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
})
console.log(obj.fullName) // A-B
obj.fullName = 'C-D'
console.log(obj.firstName, obj.lastName) // C D
Object.defineProperty(obj, 'fullName2', {
configurable: false, //是否可以重新define
enumerable: true, // 是否可以枚举(for..in / keys())
value: 'A-B', // 指定初始值
writable: false // value是否可以修改
})
console.log(obj.fullName2) // A-B
obj.fullName2 = 'E-F'
console.log(obj.fullName2) // A-B
/*Object.defineProperty(obj, 'fullName2', {
configurable: true,
enumerable: true,
value: 'G-H',
writable: true
})*/
//4. Object.keys(obj): 得到对象自身可枚举属性组成的数组
const names = Object.keys(obj)
console.log(names)
//5. obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性
console.log(obj.hasOwnProperty('fullName'), obj.hasOwnProperty('toString')) // true false
//6. DocumentFragment: 文档碎片(高效批量更新多个节点)
// document: 对应显示的页面, 包含n个elment 一旦更新document内部的某个元素界面更新
// documentFragment: 内存中保存n个element的容器对象(不与界面关联), 如果更新framgnet中的某个element, 界面不变
/*
<ul id="fragment_test">
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
*/
const ul = document.getElementById('fragment_test')
// 1. 创建fragment
const fragment = document.createDocumentFragment()
// 2. 取出ul中所有子节点取出保存到fragment
let child
while(child=ul.firstChild) { // 一个节点只能有一个父亲, 如果节点添加到另外一个父节点,那么子节点会从前一个父节点中删除
fragment.appendChild(child) // 先将child从ul中移除, 添加为fragment子节点
}
// 3. 更新fragment中所有li的文本
Array.prototype.slice.call(fragment.childNodes).forEach(node => {
if (node.nodeType===1) { // 元素节点 <li>
node.textContent = 'atguigu'
}
})
// 4. 将fragment插入ul
ul.appendChild(fragment)
</script>
</body>
</html>
三、数据代理
● 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)
● vue 数据代理: 通过 vm 对象来代理 data 对象中所有属性的操作
● 好处: 更方便的操作 data 中的数据
● 基本实现流程
○ 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
○ 所有添加的属性都包含 getter/setter
○ getter/setter 内部去操作 data 中对应的属性数据
四、模板解析
1、模板解析的基本流程
● 将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中
● 对 fragment 中的所有层次子节点递归进行编译解析处理
○ 对大括号表达式文本节点进行解析
○ 对元素节点的指令属性进行解析
■ 事件指令解析
■ 一般指令解析
● 将解析后的 fragment 添加到 el 中显示
2、模板解析(1): 大括号表达式解析
● 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
● 从 data 中取出表达式对应的属性值
● 将属性值设置为文本节点的 textContent
3、模板解析(2): 事件指令解析
● 从指令名中取出事件名
● 根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
● 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
● 指令解析完后, 移除此指令属性
4、模板解析(3): 一般指令解析
● 得到指令名和指令值(表达式) text/html/class msg/myClass
● 从 data 中根据表达式得到对应的值
● 根据指令名确定需要操作元素节点的什么属性
○ v-text—textContent 属性
○ v-html—innerHTML 属性
○ v-class–className 属性
● 将得到的表达式的值设置到对应的属性上
●移除元素的指令属性
G、vuex
一、vuex理解
1、vuex 是什么
● github 站点: https://github.com/vuejs/vuex
● 在线文档: https://vuex.vuejs.org/zh-cn/
● 简单来说: 对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
● 安装 vuex:npm install --save vuex
2、状态自管理应用
● state: 驱动应用的数据源
● view: 以声明方式将 state 映射到视图
● actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
3、多组件共享状态的问题
● 多个视图依赖于同一状态
● 来自不同视图的行为需要变更同一状态
● 以前的解决办法
○ 将数据以及操作数据的行为都定义在父组件
○ 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
● vuex 就是用来解决这个问题的
二、vuex 核心概念和 API
1、state
● vuex 管理的状态对象
● 它应该是唯一的
const state = {
xxx: initValue
}
2、mutations
● 包含多个直接更新 state 的方法(回调函数)的对象
● 谁来触发: action 中的 commit(‘mutation 名称’)
● 只能包含同步的代码, 不能写异步代码
● const mutations = {
yyy (state, {data1}) {
// 更新 state 的某个属性
}
}
3、actions
● 包含多个事件回调函数的对象
● 通过执行: commit()来触发 mutation 的调用, 间接更新 state
● 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’
● 可以包含异步代码(定时器, ajax)
● const actions = {
zzz ({commit, state}, data1) {
commit(‘yyy’, {data1})
}
}
4、getters
● 包含多个计算属性(get)的对象
● 谁来读取: 组件中: $store.getters.xxx
● const getters = {
mmm (state) {
return …
}
}
5、modules
● 包含多个 module
● 一个 module 是一个 store 的配置对象
● 与一个组件(包含有共享数据)对应
6、向外暴露 store 对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
7、组件中
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['xxx']),
...mapGetters(['mmm']),
}
methods: mapActions(['zzz'])
}
{{xxx}} {{mmm}} @click="zzz(data)"
8、映射 store
import store from './store'
new Vue({
store
})
9、store 对象
● 所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
● 属性:
○ state: 注册的 state 对象
○ getters: 注册的 getters 对象
● 方法:
dispatch(actionName, data): 分发调用 action
Vuex详解:
vuex 结构图:
使用步骤:
方式1:
1、安装 vuex,在控制台输入:
npm install --save vuex
2、创建 vuex 的核心管理对象模块:store。创建一个 store.js文件,存放在 src 目录下面,如:
3、store.js 文件中内容如下,在暴露的对象中引入4个具体的对象,state、mutations、actions、getters:
/*
vuex最核心的管理对象store
*/
import Vue from 'vue' //vuex是vue的插件,vue必须引入
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({ //Store是构造函数,存在Vuex对象里面的对象
state, //状态对象,用来存放具体数据
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
4、创建上面4个具体的对象,同样也是创建在 store.js 文件中,如下:
/*
vuex最核心的管理对象store
*/
import Vue from 'vue' //vuex是vue的插件,vue必须引入
import Vuex from 'vuex'
Vue.use(Vuex)
//状态对象,用来存放具体数据
const state = {
}
//包含多个更新state函数的对象
const mutations = {
}
//包含多个对应事件回调函数的对象
const actions = {
}
//包含多个getter计算属性函数的对象
const getters = {
}
export default new Vuex.Store({ //Store是构造函数,存在Vuex对象里面的对象
state, //状态对象,用来存放具体数据
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
5、将 store 对象引入到入口 main.js 文件,如下:
/*
入口js
*/
import Vue from 'vue'
import Counter from './Counter.vue'
//引入 store 对象
import store from './store'
new Vue({
el: '#app',
components: { App },
template: '<App/>',
store // 注册上vuex的store: 所有组件对象都多一个属性$store
})
6、将组件的 data 里面的数据放到 state 对象里面进行初始化管理,并且移出组件中 data 里面的数据,但是
移出组件中 data 里面的数据后,怎样访问初始化的数据呢?
由于所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象,这个对象中有两个属性:
state:注册的 state 对象
getters:注册的 getters 对象
我们可以用 store 中的这两个属性访问数据,除了两个属性以外,还有一个方法:
dispatch(actionName, data):分发调用 action
这个方法可以调用 actions 中的方法
/*
vuex最核心的管理对象store
*/
import Vue from 'vue' //vuex是vue的插件,vue必须引入
import Vuex from 'vuex'
Vue.use(Vuex)
//状态对象,用来存放具体数据
const state = { //初始化组件中的数据
counter: 0
}
//包含多个更新state函数的对象
const mutations = {
}
//包含多个对应事件回调函数的对象
const actions = {
}
//包含多个getter计算属性函数的对象
const getters = {
}
export default new Vuex.Store({ //Store是构造函数,存在Vuex对象里面的对象
state, //状态对象,用来存放具体数据
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
7、在组件中引用数据,在标签中直接使用 $store.state.属性即可获取数据,但是用 js 编写的时候需要在前面
加 this
<template>
<div>
<p>
click {{$store.state.counter}}
</p>
</div>
</template>
8、在组件中使用计算属性
1)、定义计算属性。
在 store.js 的 getters 中定义一个计算属性,注意,计算属性中引用 data 中的数据时,可以通过
state 这个对象访问数据,获取 state 时不需要其他操作,只要在计算属性方法的入参传入 state 即可
/*
vuex最核心的管理对象store
*/
import Vue from 'vue' //vuex是vue的插件,vue必须引入
import Vuex from 'vuex'
Vue.use(Vuex)
//状态对象,用来存放具体数据
const state = { //初始化组件中的数据
counter: 0
}
//包含多个更新state函数的对象
const mutations = {
}
//包含多个对应事件回调函数的对象
const actions = {
}
//包含多个getter计算属性函数的对象
const getters = {
evenOrOdd (state) { //直接传入 state 即可访问数据
return state.counter % 2 === 0 ? '偶数' : '奇数'
}
}
export default new Vuex.Store({ //Store是构造函数,存在Vuex对象里面的对象
state, //状态对象,用来存放具体数据
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
2)、使用定义好的计算属性。如下,使用上一步定义的计算属性 evenOrOdd
在标签中用两个大括号将定义好的计算属性包含,这个方法和在 getters 中定义的名字可以不一样;
在 computed 属性中定义上面大括号中的方法,然后使用 this.$store.getters.计算属性名 调用,注意,
不要在后面加括号,因为计算属性获取时直接调用里面的 getter 和 setter 方法
<template>
<div>
<p>
click {{$store.state.counter}} times, counter is {{evenOrOdd}}
</p>
</div>
</template>
<script>
export default {
computed: {
evenOrOdd() {
// 这里加括号是错误的,如 $store.getters.evenOrOdd() 是错误的
// 不需要调用,只需要读取属性值
return this.$store.getters.evenOrOdd
}
}
}
</script>
9、在组件中修改 state 管理的数据
使用 $store 对象中有 dispatch(actionName, data) 方法去修改数据,点击增加或减少按钮修改数据
1)、在组件中的代码如下:
当点击增加或者减少的按钮修改数据时,会执行 methods 中相应的函数,函数中使用
this. s t o r e . d i s p a t c h ( ) 调 用 s t o r e 中 a c t i o n s 中 的 具 体 方 法 , t h i s . store.dispatch() 调用 store 中 actions 中的具体方法,this. store.dispatch()调用store中actions中的具体方法,this.store.dispatch() 中传入的入参第一个
是 store 中 actions 中定义的函数名称,第二个以后是参数
<template>
<div>
<!-- 点击下面按钮增加或减少 counter -->
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script>
export default {
methods: {
increment() {
//通知 vuex 去增加
this.$store.dispatch('increment') //触发 store 中 action 调用
},
decrement() {
this.$store.dispatch('decrement')
}
}
}
</script>
2)、在 store.js 中的 actions 定义对应上面 dispatch 方法的第一个入参的方法,如 increment 方法,注
意:方法中第一个参数是 {commit, state},commit 是一个方法,它提交给 mutations,最终导致
mutations 中的某一个方法调用,即执行 commit 第一个参数对应的方法
执行异步操作都是在 actions 中执行的
/*
vuex最核心的管理对象store
*/
import Vue from 'vue' //vuex是vue的插件,vue必须引入
import Vuex from 'vuex'
Vue.use(Vuex)
//状态对象,用来存放具体数据
const state = { //初始化组件中的数据
counter: 0
}
//包含多个更新state函数的对象
const mutations = {
//增加的 mutation
INCREMENT (state) {
state.counter++
},
//减少的 mutation
DECREMENT (state) {
state.counter--
}
}
//包含多个对应事件回调函数的对象
const actions = {
increment({commit, state}) {
commit('INCREMENT')
},
decrement({commit, state}) {
commit('DECREMENT')
}
}
//包含多个getter计算属性函数的对象
const getters = {
evenOrOdd (state) { //直接传入 state 即可访问数据
return state.counter % 2 === 0 ? '偶数' : '奇数'
}
}
export default new Vuex.Store({ //Store是构造函数,存在Vuex对象里面的对象
state, //状态对象,用来存放具体数据
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
优化:
上面的步骤中,有很多 this.$store,我们可以进行优化。
在 script 标签中引入:import {mapState, mapGetters, mapActions} from ‘vuex’,引入后,在 computed
计算属性中使用 …mapState([‘counter’]),…mapGetters([‘evenOrOdd’]),这样以后,直接可以在标签的
大括号中使用 counter 或 evenOrOdd
同样的,在 methods 中也可以使用 …mapActions,只需将方法名传入进去即可
如果组件中定义的名称与 store 中定义的名字不一样,可以使用对象映射 {组件中的名字:store中的名字}
<template>
<div>
<p>
click {{counter}} times,counter is {{evenOrOdd}}
</p>
<button @click='increment'>+</button>
<button @click='decrement'>-</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['counter']),
...mapGetters(['evenOrOdd'])
//如果映射关系不一致,store.js 中getters中的方法名为 evenOrOdd2,我们可以用
//...mapGetters({evenOrOdd: 'evenOrOdd2'}) 映射
/*
以前的写法:
evenOrOdd() {
return this.$store.getters.evenOrOdd
},
counter() {
return this.$store.state.counter
}
其实,
...mapState(['counter']) 的返回值是一个对象,对象中使用的是一个函数,如:
{counter() {return this.$store.state['counter']}}
...mapGetters(['evenOrOdd']) 的返回值是一个对象,对象中使用的是一个函数,如:
{evenOrOdd() {return this.$store.state['evenOrOdd']}}
所以,其实是一样的,只是 vuex 给我们提供了一个简单的写法
*/
},
methods: {
...mapActions(['increment', 'decrement'])
}
}
</script>
Vuex结构图:
方式2:
一般的,在项目中使用 vuex 时,会将 state、actions、mutations、getters 4个对象分别创建4个不同的文件
步骤:
1、在 src 下创建一个 store 文件夹,在文件夹中分别创建 state.js、getters.js、actions.js、mutations.js 以
及入口 index.js 文件,如:
2、index.js 文件中内容如下:
/*
vuex核心管理模块store对象
*/
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
3、在入口 main.js 文件中引入 store
/*
入口JS
*/
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
el: '#app',
components: {App}, // 映射组件标签
template: '<App/>', // 指定需要渲染到页面的模板
store // 所有的组件对象都多了一个属性: $store(值就是store对象)
})
4、state.js、getters.js、actions.js、mutations.js 文件的写法如下:
里面的具体内容还是和方式1一样,在组件中也可以写前面的优化方式
export default {
}
需要注意的是,当有参数传入的时候,在 actions 文件中的入参还是跟普通传法一样,但是当调用 commit
方法时,需要将入参包含在 {} 中,同样,在 mutations 中接收的时候也要使用{}包含,如:
actions.js中传参时调用 commit 时,需将参数用大括号括起来:
addTodo ({commit}, todo) {
// 提交一个comutation请求
commit('add_todo', {todo}) // 传递给mutation的是一个包含数据的对象
},
mutations.js 中同样要用大括号括起来:
add_todo (state, {todo}) { // 方法名不是ADD_TODO, 而是add_todo
state.todos.unshift(todo)
},
H、项目