相关代码:https://github.com/user0819/vue_test.git
一、Vue核心
1.1 Vue简介
1.1.1 官网
英文官网: Vue.js - The Progressive JavaScript Framework | Vue.js
中文官网: Vue.js - 渐进式 JavaScript 框架 | Vue.js
1.1.2 介绍与描述
动态构建用户界面的渐进式 JavaScript 框架
1.1.3 Vue 的特点
- 遵循 MVVM 模式
- 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
- 它本身只关注 UI, 也可以引入其它第三方库开发项目
1.1.4 与其它 JS 框架的关联
- 借鉴 Angular 的模板和数据绑定技术
- 借鉴 React 的组件化和虚拟 DOM 技术
1.1.5 Vue 周边库
- vue-cli: vue 脚手架
- vue-resource
- axios
- vue-router: 路由
- vuex: 状态管理
- element-ui: 基于 vue 的 UI 组件库(PC 端)
1.2 初始Vue
- 创建一个Vue实例,传入一个配置对象
- 代码依然符合html规范,只不过混入了一些特殊的Vue语法
- 容器里的代码被称为【Vue模板】
- Vue实例和容器是一一对应的
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用
- {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>初识Vue</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="demo">
<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>
<script type="text/javascript" >
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//创建Vue实例
new Vue({
el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'xiang',
address:'北京'
}
})
</script>
</body>
</html>
1.2.1 data与el的2种写法
el有2种写法
(1)new Vue时候配置el属性。
(2)先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
//el的两种写法
const v = new Vue({
//el:'#root', //第一种写法
data:{
name:'希昂'
}
})
v.$mount('#root') //第二种写法
data有2种写法
(1).对象式
(2).函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
//data的两种写法
new Vue({
el:'#root',
//data的第一种写法:对象式
/* data:{
name:'希昂'
} */
//data的第二种写法:函数式
data(){
return {
name:'希昂'
}
}
})
一个重要原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
1.3 模版语法
Vue模板语法有2大类:
- 插值语法
- 功能:用于解析标签体内容。
- 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
- 指令语法
- 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
- 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。
- 备;注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子。
示例效果:
<body>
<!-- 容器-->
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr/>
<h1>指令语法</h1>
<a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
<a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
name:'xiang',
school:{
name:'B站',
url:'http://www.bilibili.com',
}
}
})
</script>
1.4 数据绑定
Vue中有2种数据绑定的方式:
- 单向绑定(v-bind):数据只能从data流向页面。
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
备注:
- 双向绑定一般都应用在表单类元素上(如:input、select等)
- v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
示例效果:
<body>
<!-- 个容器-->
<div id="root">
<!-- 普通写法 -->
<!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/> -->
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>
<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<!-- <h2 v-model:x="name">你好啊</h2> -->
</div>
</body>
<script type="text/javascript">
new Vue({
name:'希昂',
el:'#root',
data:{
name:'希昂'
}
})
</script>
1.5 MVVM模型
M:模型(Model) :对应 data 中的数据
V:视图(View) :模板
VM:视图模型(ViewModel) : Vue 实例对象
1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
1.5.1 数据代理
何为数据代理?
通过一个对象代理对另一个对象中属性的操作(读/写)
<script type="text/javascript" >
let obj = {x:100}
let obj2 = {y:200}
Object.defineProperty(obj2, 'x', {
get(){
return obj.x
},
set(value){
obj.x = value
}
})
</script>
1.5.2 Vue数据代理
1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>姓名:{{name}}</h2>
<h2>地址:{{address}}</h2>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
name:'希昂',
address:'南京'
}
})
</script>
1.6 事件处理
1.6.1 事件基本使用
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数!否则this就不是vm了;
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
- @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参;默认事件形参: event。隐含属性对象: $event。
示例:
<body>
<!-- 容器-->
<div id="root">
<h2>欢迎{{name}}学习</h2>
<!-- <button v-on:click="showInfo">点我提示信息</button> -->
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
name:'希昂',
},
methods:{
showInfo1(event){
alert('同学你好!')
},
showInfo2(event,number){
console.log(event,number)
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!!')
}
}
})
</script>
1.6.2 事件修饰符
Vue中的事件修饰符:
- prevent:阻止默认事件(常用);
- stop:阻止事件冒泡(常用);
- once:事件只触发一次(常用);
- capture:使用事件的捕获模式;
- self:只有event.target是当前操作的元素时才触发事件;
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
<body>
<!-- 容器-->
<div id="root">
<h2>欢迎{{name}}学习</h2>
<!-- 阻止默认事件(常用) -->
<a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>
<!-- 阻止事件冒泡(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
<!-- 修饰符可以连续写 -->
<a href="http://www.baidu.com" @click.prevent.stop="showInfo">点我提示信息</a>
</div>
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时才触发事件; -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
name:'希昂'
},
methods:{
showInfo(e){
alert('同学你好!')
},
showMsg(msg){
console.log(msg)
},
demo(){
for (let i = 0; i < 100000; i++) {
console.log('#')
}
console.log('累坏了')
}
}
})
</script>
1.6.3 按键修饰符
Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete (捕获“删除”和“退格”键)
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
系统修饰键(用法特殊):ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合keydown使用:正常触发事件。
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<body>
<!-- 容器-->
<div id="root">
<h2>欢迎{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
Vue.config.keyCodes.huiche = 13 //定义了一个别名按键
new Vue({
el:'#root',
data:{
name:'希昂'
},
methods: {
showInfo(e){
console.log(e, e.key,e.keyCode)
}
},
})
</script>
1.7 计算属性与监视
1.7.1 计算属性-computed
- 要显示的数据不存在,要通过计算得来
- 在 computed 对象中定义计算属性
- 在页面中使用{{方法名}}来显示计算的结果
示例:
<body>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
测试:<input type="text" v-model="x"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
x:'你好'
},
computed:{
fullName:{
//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
//set什么时候调用? 当fullName被修改时。
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
简写:
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
//简写:只有get
fullName(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
})
1.7.2 监视属性-watch
通过 vm 对象的$watch()或 watch 配置来监视指定的属性
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视!!
- 监视的两种写法:
- new Vue时传入watch配置
- 通过vm.$watch监视
示例:
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
/* watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
} */
})
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
</script>
深度监测:
- Vue中的watch默认不监测对象内部值的改变(一层)
- 配置deep:true可以监测对象内部值改变(多层)
<body>
<div id="root">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
{{numbers.c.d.e}}
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
numbers:{
a:1,
b:1,
c:{
d:{
e:100
}
}
}
},
watch:{
//监视多级结构中某个属性的变化
'numbers.a':{
handler(){
console.log('a被改变了')
}
},
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
})
</script>
1.8 class与style绑定
在应用界面中, 某个(些)元素的样式是变化的。
class/style 绑定就是专门用来实现动态样式效果的技术。
1.8.1 class绑定
- :class="xxx"
xxx可以是字符串、对象、数组。
- 字符串写法适用于:类名不确定,要动态获取。 'classA'
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。{classA:isA, classB: isB}
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。['classA', 'classB']
1.8.2 style绑定
- :style="{fontSize: xxx}"其中xxx是动态值。
- :style="[a,b]"其中a、b是样式对象。
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>绑定样式</title>
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
border: 4px solid red;;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);
}
.sad{
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal{
background-color: skyblue;
}
.xiang1{
background-color: yellowgreen;
}
.xiang2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.xiang3{
border-radius: 20px;
}
</style>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/><br/>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name:'希昂',
mood:'normal',
classArr:['xiang1','xiang2','xiang3'],
classObj:{
xiang1:false,
xiang2:false,
},
styleObj:{
fontSize: '40px',
color:'red',
},
styleObj2:{
backgroundColor:'orange'
},
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
},
methods: {
changeMood(){
const arr = ['happy','sad','normal']
const index = Math.floor(Math.random()*3)
this.mood = arr[index]
}
},
})
</script>
</html>
1.9 条件渲染
1.9.1 v-if
写法:
- v-if="表达式"
- v-else-if="表达式"
- v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
1.9.2 v-show
写法:
- v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
1.9.3 示例
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 使用v-show做条件渲染 -->
<h2 v-show="false">欢迎{{name}}</h2>
<h2 v-show="n===1">欢迎{{name}}</h2>
<!-- v-else和v-else-if -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
<!-- v-if与template的配合使用 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>希昂</h2>
<h2>南京</h2>
</template>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name:'希昂',
n:0
}
})
</script>
1.10 列表渲染
1.10.1 基本使用
v-for指令:
- 用于展示列表数据
- 语法:v-for="(item, index) in xxx" :key="yyy"
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) in car" :key="k">
{{k}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) in str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) in 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
car:{
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello'
}
})
</script>
1.10.2 key的原理
react、vue中的key有什么作用?(key的内部原理)
- 虚拟DOM中key的作用:
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
- 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
- 对比规则:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM,随后渲染到到页面。
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
]
},
methods: {
add(){
const p = {id:'004',name:'老刘',age:40}
this.persons.unshift(p)
}
},
})
</script>
1.10.3 数据监测
Vue监视数据的原理:
- vue会监视data中所有层次的数据。
- 如何监测对象中的数据?通过setter实现监视,且要在new Vue时就传入要监测的数据。
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API:
- Vue.set(target,propertyName/index,value)
- vm.$set(target,propertyName/index,value)
- 如何监测数组中的数据? 通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
- 重新解析模板,进而更新页面。
- 在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
<body>
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button> <br/>
<button @click="addSex">添加性别属性,默认值:男</button> <br/>
<button @click="student.sex = '未知' ">修改性别</button> <br/>
<button @click="addFriend">在列表首位添加一个朋友</button> <br/>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
<button @click="addHobby">添加一个爱好</button> <br/>
<button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
age:18,
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:'jack',age:70})
},
updateFirstFriendName(){
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobby.push('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
// Vue.set(this.student.hobby,0,'开车')
this.$set(this.student.hobby,0,'开车')
},
removeSmoke(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '抽烟'
})
}
}
})
</script>
1.11 收集表单数据
收集表单数据,根据类型不同,v-model收集的不一定是value值。
- <input type="text"/>,,则v-model收集的是value值,用户输入的就是value值。
- <input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
- <input type="checkbox"/>
- 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- 配置input的value属性:
- v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
- lazy:失去焦点再收集数据
- number:输入字符串转为有效的数字
- trim:输入首尾空格过滤
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>收集表单数据</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
密码:<input type="password" v-model="userInfo.password"> <br/><br/>
年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
爱好:
学习<input type="checkbox" v-model="userInfo.hobby" value="study">
打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
<br/><br/>
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select>
<br/><br/>
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<button>提交</button>
</form>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
password:'',
age:18,
sex:'female',
hobby:[],
city:'beijing',
other:'',
agree:''
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
</html>
1.12 过滤器
1.12.1 介绍
功能: 对要显示的数据进行特定格式化后再显示
语法:
- 注册过滤器:
- 全局过滤器:Vue.filter(name,callback)
- 局部过滤器:new Vue{filters:{}}
- 使用过滤器:
- {{ xxx | 过滤器名}}
- v-bind:属性 = "xxx | 过滤器名"
注意: 并没有改变原本的数据, 是产生新的对应的数据
类型:局部过滤器、全局过滤器
1.12.2 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>过滤器</title>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 原时间戳 -->
<h3>时间戳:{{time}}</h3>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是:{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>现在是:{{time | timeFormatter}}</h3>
<!-- 过滤器实现(传参) -->
<h3>现在是:{{time | timeFormatter('YYYY_MM_DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">希昂</h3>
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,5)
})
new Vue({
el:'#root',
data:{
time:1621561377603, //时间戳
msg:'你好,希昂'
},
computed: {
fmtTime(){
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
methods: {
getFmtTime(){
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
//局部过滤器
filters:{
timeFormatter(value,str='YYYY年MM月DD日 HH:mm:ss'){
// console.log('@',value)
return dayjs(value).format(str)
}
}
})
new Vue({
el:'#root2',
data:{
msg:'hello,xiang!'
}
})
</script>
</html>
1.13 内置指令与自定义指令
1.13.1 常用内置指令
- v-text : 更新元素的 textContent
- v-html : 更新元素的 innerHTML
- v-if : 如果为 true, 当前标签才会输出到页面
- v-else: 如果为 false, 当前标签才会输出到页面
- v-show : 通过控制 display 样式来控制显示/隐藏
- v-for : 遍历数组/对象
- v-on : 绑定事件监听, 一般简写为@
- v-bind:xxx : 绑定解析表达式, 可以省略 v-bind
- v-model : 双向数据绑定
- v-cloak : 保证组件显示正常。vue创建完毕后删除此属性, 与 css 配合: [v-cloak] { display: none }
- v-once:所在节点在初次动态渲染后,就视为静态内容了
- v-pre指令:跳过其所在节点的编译过程
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>内置指令</title>
<style>
[v-cloak] {
display: none;
}
</style>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2 v-text="text"></h2>
<h2 v-html="html"></h2>
<hr>
<h2 v-if="isShow">{{chineseName}}</h2>
<h2 v-else>{{name}}</h2>
<h2 v-show="isShow">{{chineseName}}</h2>
<hr>
<h2>{{name}}</h2>
<hr>
<h2 v-for="(myName,index) in names" :key="index">{{index}}-{{ myName}}</h2>
<hr>
<h2 v-on:click="myAlert(name)">点击试试:{{name}}</h2>
<hr>
<a v-bind:href="baiduPre + baiduSuf">百度一下</a>
<hr>
<input type="text" v-model="inputValue">
<hr>
<h2 v-cloak>vue加载完成才显示</h2>
<h2 v-once>仅编译一次:{{name}}</h2>
<h2 v-pre>跳过编译,不信你看:{{name}}</h2>
<hr>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>
<script type="text/javascript">
console.log(1)
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el: '#root',
data: {
text: '你好,希昂',
html: '<strong style="color:red">你好,希昂</strong>',
isShow: true,
name: 'xiang',
chineseName: '希昂',
names: ['xiang', '希昂'],
baiduPre:"https://www.bai",
baiduSuf:"du.com",
inputValue:"你好"
},
methods:{
myAlert(a){
alert(a);
}
}
})
</script>
</html>
1.13.2 自定义指令
注册全局指令:
//方式一
Vue.directive(指令名,配置对象)
//方式二
Vue.directive(指令名,回调函数)
注册局部指令:
//方式一
new Vue({
directives:{指令名:配置对象}
})
//方式二
new Vue({
directives{指令名:回调函数}
})
//比如
directives: {
'my-directive' : {
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
}
配置对象中常用的3个回调:
- bind:指令与元素成功绑定时调用。
- inserted:指令所在元素被插入页面时调用。
- update:指令所在模板结构被重新解析时调用。
备注:
- 指令定义时不加v-,但使用时要加v-;
- 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>自定义指令</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>当前的n值是:<span v-text="n"></span> </h2>
<!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
<button @click="n++">点我n+1</button>
<hr/>
<input type="text" v-my-focus:value="n">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义全局指令
/* Vue.directive('my-focus',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
}) */
new Vue({
el:'#root',
data:{
n:10
},
directives:{
big(element,binding){
console.log('big',this) //注意此处的this是window
// console.log('big')
element.innerText = binding.value * 10
},
'my-focus':{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
}
}
})
</script>
</html>
1.14 Vue实例生命周期
1.14.1 生命周期分析
1、初始化显示
- beforeCreate()
- created()
- beforeMount()
- mounted()
2、更新状态: this.xxx = value
- beforeUpdate()
- updated()
3、销毁 vue 实例: vm.$destory()
- beforeDestory()
- destoryed()
常用的生命周期钩子:
- mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
- beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>分析生命周期</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root" :x="n">
<h2 v-text="n"></h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
// template:`
// <div>
// <h2>当前的n值是:{{n}}</h2>
// <button @click="add">点我n+1</button>
// </div>
// `,
data:{
n:1
},
methods: {
add(){
console.log('add')
this.n++
},
bye(){
console.log('bye')
this.$destroy()
}
},
watch:{
n(){
console.log('n变了')
}
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeDestroy() {
console.log('beforeDestroy')
},
destroyed() {
console.log('destroyed')
},
})
</script>
</html>
二、Vue组件化编程
2.1 模块与组件
2.1.1 概念
模块:向外提供特定功能的 js 程序, 一般就是一个 js 文件。
组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)
模块化:当应用中的 js 以模块来编写的, 那这个应用就是一个模块化的应用。
组件化:当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,
2.1.2 组件使用步骤
步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
定义组件
- 使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
- 区别如下:
- el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
- data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
- 备注:使用template可以配置组件结构。
注册组件
- 局部注册:靠new Vue的时候传入components选项
- 全局注册:靠Vue.component('组件名',组件)
使用组件
- <school></school>
2.1.3 组件说明
组件名:
- 一个单词组成:
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
- 多个单词组成:
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
- 备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
组件标签:
- 第一种写法:<school></school>
- 第二种写法:<school/>
- 备注:不使用脚手架时,会导致后续组件不能渲染。
简写方式:
const school = Vue.extend(options)
可简写为:
const school = options
VueComponent:
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
- 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent
关于this指向:
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
- new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
重要内置关系:
- VueComponent.prototype.__proto__ === Vue.prototype
- 让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
2.2 非单文件组件
组件不是定义在单独的vue文件中。
但也一样遵循:定义、注册、使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>{{name}}</h1>
<school></school>
<student></student>
</div>
</body>
<script>
const school = Vue.extend({
name: 'school',
template:
`
<div>
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>>
`,
data() {
return {
name: '哈哈哈',
address: '南京'
}
}
})
const student = Vue.extend({
name: 'student',
template:
`
<div>
<h2>学生名称:{{ name }}</h2>
</div>>
`,
data() {
return {
name: '希昂',
}
}
})
new Vue({
el: "#root",
data: {
name: 'xiang'
},
components: {
student: student,
school: school
}
})
</script>
</html>
2.3 单文件组件
在单独的vue文件中定义。
遵循:定义、注册、使用。
标准文件格式:
<template>
...
</template>
<script>
...
</script>
<style>
...
</style>
简单示例(需要配合脚手架才能运行)
- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
</body>
</html>
- main.js
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})
- App.vue
<template>
<div>
<Student></Student>
</div>
</template>
<script>
//引入组件
import Student from './Student.vue'
export default {
name:'App',
components:{
Student
}
}
</script>
- Student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
三、Vue脚手架
3.1 初始化脚手架
3.1.1 说明
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
最新的版本是 4.x。
文档: Vue CLI。
3.1.2 具体步骤
1、仅第一次执行时:全局安装@vue/cli:
npm install -g @vue/cli
2、切换到要创建项目的目录,使用命令创建项目
vue create xxxx
3、启动项目
npm run serve
备注:
1、如出现下载缓慢请配置 npm 淘宝镜像:
npm config set registry http://registry.npm.taobao.org
2、Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的webpakc 配置,请执行
vue inspect > output.js
3.1.3 模版项目的结构
.
├── node_modules
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
├── README.md
├── babel.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
└── vue.config.js
main.js中render的说明:
- vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能+模板解析器。
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
3.2 ref与props
3.2.1 ref
作用:用于给节点打标识,便于vue直接通过标识获取该对象。
对象:可以直接加载DOM上,也可以加载Vue Component上
读取方式:this.$refs.xxxxxx
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>
<script>
//引入School组件
import School from './components/School'
export default {
name:'App',
components:{School},
data() {
return {
msg:'欢迎学习Vue!'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象(vc)
console.log('原来内容:' + this.$refs.title.textContent)
this.$refs.title.textContent = "变了"
}
},
}
</script>
3.2.2 props
作用:用于父组件给子组件传递数据
传值方式:在使用子组件时传入对应属性值即可。
字符值直接传递、表达式需要使用v-bind:
<Student name="希昂" sex="男" :age=myAge />
读取方式:
读取方式一: 只指定名称
props: ['name', 'age', 'setName']
读取方式二: 指定名称和类型
props: {
name: String,
age: Number,
setNmae: Function
}
读取方式三: 指定名称/类型/必要性/默认值
props: {
name: {type: String, required: true, default:xxx},
}
App.vue
<template>
<div>
<Student name="希昂" sex="男" v-bind:age=itemAge />
</div>
</template>
<script>
import Student from './components/Student'
export default {
name:'App',
data(){
return {
itemAge: 25
}
},
components:{Student}
}
</script>
Student.vue
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge+1}}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
console.log(this)
return {
msg:'我是一个学生',
myAge:this.age
}
},
methods: {
updateAge(){
this.myAge++
}
},
//简单声明接收
props:['name','age','sex']
//接收的同时对数据进行类型限制
/* props:{
name:String,
age:Number,
sex:String
} */
//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
// props:{
// name:{
// type:String, //name的类型是字符串
// required:true, //name是必要的
// },
// age:{
// type:Number,
// default:99 //默认值
// },
// sex:{
// type:String,
// required:true
// }
// }
}
</script>
3.3 混入
作用:定义通用的方法,通过install方法引入,简化重复代码。
可以理解为一个公共对象。根据引入的地方,决定它的作用域:组件内或全局。
Vue 插件是一个包含 install 方法的对象
通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等
引入方法:
- 方式一:通过import引入
- 方式二:在vue中使用mixins
示例:
在mixin.js中定义:数据、方法、事件。在main.js中引入,作为全局方法和事件。
mixin.js
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
// 混入:注册全局对象
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
App.vue
<template>
<div>
<School/>
<hr>
<Student/>
</div>
</template>
<script>
import School from './components/School'
import Student from './components/Student'
export default {
name:'App',
components:{School,Student}
}
</script>
School.vue
<template>
<div>
<h2 @click="showName">学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
// import {hunhe,hunhe2} from '../mixin'
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
},
// mixins:[hunhe,hunhe2]
}
</script>
3.4 插件
作用类似混入,实现方式稍有不同。
Vue 插件是一个包含 install 方法的对象
通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等。
示例:在plugins.js中定义插件,并在main.js中引入使用,作为全局插件。
plugins.js
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
App.vue
<template>
<div>
<School/>
<hr>
<Student/>
</div>
</template>
<script>
import School from './components/School'
import Student from './components/Student'
export default {
name:'App',
components:{School,Student}
}
</script>
School.vue
<template>
<div>
<!-- 使用插件中的方法 -->
<h2>学校名称:{{name | mySlice}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'AAAA',
address:'北京',
}
},
methods: {
test(){
// 使用插件中的方法
this.hello()
}
},
}
</script>
3.5 Todo-list案例
具体实现,详见代码:https://github.com/user0819/vue_test.git
浏览器本地缓存:
- localStorage:会一直在浏览器中,页面关闭(会话结束)后,缓存内容也不会消失,
- sessionStorage:仅存在浏览器当前会话中,页面关闭(会话结束),缓存内容消失
localStorage.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))
// console.log(localStorage.getItem('msg3'))
}
function deleteData(){
localStorage.removeItem('msg2')
}
function deleteAllData(){
localStorage.clear()
}
</script>
</body>
</html>
sessionStorage.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
sessionStorage.setItem('msg','hello!!!')
sessionStorage.setItem('msg2',666)
sessionStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
const result = sessionStorage.getItem('person')
console.log(JSON.parse(result))
// console.log(sessionStorage.getItem('msg3'))
}
function deleteData(){
sessionStorage.removeItem('msg2')
}
function deleteAllData(){
sessionStorage.clear()
}
</script>
</body>
</html>
3.6 Vue中的自定义事件
组件之间需要互相之间的一些调用,可以用props传递属性或方法,但层级有限制,如果间隔多层需要一层层传递,很麻烦。可以考虑通过自定义事件进行方法调用或传递数据。
作用:在组件中自定义事件,通常用于组件间的方法调用。
使用方式:在组件A中定义事件,在组件B中触发事件,从而实现B调用A中方法。
注册事件:
<Header @addTodo="addTodo"/>
或者
<Header ref="header"/>
this.$refs.header.$on('addTodo', this.addTodo)
触发事件:
this.$emit('addTodo', todo)
销毁事件
this.$off('xiang') //解绑一个自定义事件
// this.$off(['xiang','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件
示例:
App.vue
<template>
<div class="app">
<h1>{{msg}},学校名称:{{schoolName}},学生姓名是:{{studentName}}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show"/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊',
studentName:'',
schoolName: ''
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
this.schoolName = name;
},
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
m1(){
console.log('demo事件被触发了!')
},
show(){
alert(123)
}
},
mounted() {
this.$refs.student.$on('xiang',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('xiang',this.getStudentName) //绑定自定义事件(一次性)
},
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>当前求和为:{{number}}</h2>
<button @click="add">点我number++</button>
<button @click="sendStudentName">把学生名给App</button>
<button @click="unbind">解绑xiang事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
add(){
console.log('add回调被调用了')
this.number++
},
sendStudentName(){
//触发Student组件实例身上的xiang事件
this.$emit('xiang',this.name,666,888,900)
// this.$emit('demo')
// this.$emit('click')
},
unbind(){
this.$off('xiang') //解绑一个自定义事件
// this.$off(['xiang','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件
},
death(){
this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
}
},
}
</script>
<style lang="css" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:'希昂',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
3.7 全局事件总线
上述自定义事件中,组件B若想触发事件时,仅能触发它自身上的事件,可以触发的事件优先,或者说可以传递信息的场景有限。
需要一种事件,是全局的事件,任何组件都可以直接注册事件,任何组件都可以调用事件,从而实现信息的全局传递。
全局事件总线:将应用的vm对象,设置为全局可见并定义为$bus。任何注册在vm组件上的事件,都是全局可见的,称为全局事件总线。
Vue 原型对象上包含事件处理的方法:
$on(eventName, listener) 绑定自定义事件监听
$emit(eventName, data) 分发自定义事件
$off(eventName) 解绑自定义事件监听
$once(eventName, listener) 绑定事件监听, 但只能处理一次
关键点:在创建vm的时候安装全局事件总线
new Vue({
beforeCreate () {
// 尽量早的执行挂载全局事件总线对象的操作
Vue.prototype.$globalEventBus = this
},
}).$mount('#root')
注册、触发、销毁
//注册
this.$globalEventBus.$on('deleteTodo', this.deleteTodo)
//触发
this.$globalEventBus.$emit('deleteTodo', this.index)
//销毁
this.$globalEventBus.$off('deleteTodo')
3.8 消息订阅与发布
除了全局事件总线外,也可以使用消息订阅与发布,进行全局的信息传递。
类似后台应用中消息队列的生产与消费。
包含以下操作:
(1) 订阅消息 --对应绑定事件监听
(2) 发布消息 --分发事件
(3) 取消消息订阅 --解绑事件监听
可以通过现有的一些插件库实现,比如:pubsub-js
安装插件:
npm i pubsub-js
引入插件:
import pubsub from 'pubsub-js'
发布消息:
pubsub.publish('hello',666)
订阅消息
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log(this)
// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
取消订阅
pubsub.unsubscribe(this.pubId)
3.9 过度与动画
3.9.1 vue 动画的理解
- 操作 css 的 trasition 或 animation
- vue 会给目标元素添加/移除特定的 class
- 过渡的相关类名:
- xxx-enter-active: 指定显示的 transition
- xxx-leave-active: 指定隐藏的 transition
- xxx-enter/xxx-leave-to: 指定隐藏时的样式
3.9.2 基本过渡动画的编码
- 在目标元素外包裹
- 定义 class 样式
- 指定过渡样式: transition
- 指定隐藏时的样式: opacity/其它
四、Vue中的ajax
4.1 开发环境Ajax跨域问题
前端项目中如果请求其他服务的接口,如果后端没有放开跨域限制,则浏览器会限制请求,报跨域错误。
比如:前端项目运行在http://localhost:8080/,去访问http://localhost:5002/students时就会出现跨域问题。
export default {
name:'App',
methods: {
getStudents(){
axios.get('http://localhost:5002/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
},
getCars(){
axios.get('http://localhost:5001/cars').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
}
},
}
要想解决此问题,则需要配置代理服务器。vue-cli脚手架帮构建一个代理服务器,它的请求路径同前端项目运行环境一致。由它去访问后台接口,从而避免跨域问题。
- 前端项目部署在http://localhost:8080/
- 代理服务器也是http://localhost:8080/
- 由代理服务器去向http://localhost:5002/students发送请求获取数据。
具体方式:
- vue.config.js中开启代理服务器
- 调用处向代理服务器发送请求
- vue-cli会自动帮我们进行代理请求发送
vue.config.js
devServer: {
proxy: {
'/xiang': {
target: 'http://localhost:5000',
pathRewrite: {'^/xiang': ''},
// ws: true, //用于支持websocket
// changeOrigin: true //用于控制请求头中的host值
},
'/demo': {
target: 'http://localhost:5001',
pathRewrite: {'^/demo': ''},
// ws: true, //用于支持websocket
// changeOrigin: true //用于控制请求头中的host值
}
}
}
App.vue
export default {
name:'App',
methods: {
getStudents(){
//此处将请求发送给代理服务器
axios.get('http://localhost:8080/xiang/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
},
getCars(){
axios.get('http://localhost:8080/demo/cars').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
}
},
}
4.2 vue项目中常用的两个ajax库
4.2.1 axios
通用的 Ajax 请求库, 官方推荐,使用广泛。需要安装引入。
<script>
import axios from 'axios'
export default {
name:'Search',
data() {
return {
keyWord:''
}
},
methods: {
searchUsers(){
//请求前更新List的数据
this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
console.log('请求成功了')
//请求成功后更新List的数据
this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
},
error => {
//请求后更新List的数据
this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
}
)
}
},
}
</script>
4.2.2 vue-resource
vue自带插件,需要引入。但已不再维护,了解即可。
//引入插件
import vueResource from 'vue-resource'
//使用插件
Vue.use(vueResource)
<script>
export default {
name:'Search',
data() {
return {
keyWord:''
}
},
methods: {
searchUsers(){
//请求前更新List的数据
this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
console.log('请求成功了')
//请求成功后更新List的数据
this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
},
error => {
//请求后更新List的数据
this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
}
)
}
},
}
</script>
4.4 slot插槽
父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的。
分类:
- 默认插槽
- 命名插槽
- 作用域插槽
示例:父组件下有三个子组件。
效果一:默认插槽
<template>
<div class="container">
<Category title="美食" >
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
<ul>
<li v-for="(f,index) in foods" :key="index">{{f}}</li>
</ul>
</Category>
<Category title="游戏" >
<ul>
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<ul>
<li v-for="(film,index) in films" :key="index">{{film}}</li>
</ul>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《希昂》']
}
},
}
</script>
<style scoped>
.container{
display: flex;
justify-content: space-around;
}
</style>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
效果二:具名插槽
<template>
<div class="container">
<Category title="美食" >
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
<a slot="footer" href="http://www.baidu.com">更多美食</a>
</Category>
<Category title="游戏" >
<ul slot="center">
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.baidu.com">单机游戏</a>
<a href="http://www.baidu.com">网络游戏</a>
</div>
</Category>
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<template v-slot:footer>
<div class="foot">
<a href="http://www.baidu.com">经典</a>
<a href="http://www.baidu.com">热门</a>
<a href="http://www.baidu.com">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《希昂》']
}
},
}
</script>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
效果三:作用域插槽
<template>
<div class="container">
<Category title="游戏">
<template scope="xiang">
<ul>
<li v-for="(g,index) in xiang.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<template scope="{games}">
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
}
</script>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
}
},
}
</script>
五、vuex
5.1 理解vuex
概念:专门在 Vue 中实现集中式状态(数据)管理的一个Vue 插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
Github : GitHub - vuejs/vuex: 🗃️ Centralized State Management for Vue.js.
使用场景:
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
工作原理图:
5.2 vuex核心概念
5.2.1 state
存储vuex 管理的状态对象。它应该是唯一的。
示例代码:
const state = { xxx:initValue }
5.2.2 actions
它的值为一个对象,包含多个响应用户动作的回调函数。通过 commit( )来触发 mutation 中函数的调用, 间接更新state
如何触发 actions 中的回调?在组件中使用: $store.dispatch('对应的 action 回调名') 触发
可以包含异步代码(定时器, ajax 等等)。
示例代码:
const actions = {
zzz(context,value){
context.commit('yyy',value)
}
}
5.2.3 mutations
它的值是一个对象,包含多个直接更新 state 的方法。
谁能调用 mutations 中的方法?如何调用?在 action 中使用:commit('对应的 mutations 方法名') 触发。
mutations 中方法的特点:不能写异步代码、只能单纯的操作state。(约定)
示例代码:
const mutations = {
yyy(state,value){
state.xxx = value
}
}
5.2.4 getters
值为一个对象,包含多个用于返回数据的函数。可以理解为是state的一些计算属性。
如何使用:$store.getters.xxx
示例代码:
const getters = {
bigSum(state){
return state.sum * 10
}
}
5.2.5 简单总结
stat:存储数据
actions:定义支持的一些方法,可以包含一些处理逻辑。 $store.dispatch('zzz') 触发
mutations:定义最原子的方法,里面不要包含复杂逻辑。$store.commit('yyy') 触发
5.3示例
5.3.1 定义store内容
在src目录下创建store目录,并创建index.js.
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
}
//准备mutations——用于操作数据(state)
const mutations = {
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
//准备state——用于存储数据
const state = {
sum:0 //当前的和
}
//准备getters——用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
5.3.2 引入store
main.js引入store
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
5.3.3 使用store
Count.vue中可以通过$store对象,获取状态对象,或调用方法。
可以通过this.$store.dispatch调用actions,也可以直接通过this.$store.commit调用mutations。
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<h3>当前求和放大10倍为:{{$store.getters.bigSum}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
}
},
methods: {
increment(){
this.$store.commit('JIA',this.n)
},
decrement(){
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait',this.n)
},
},
mounted() {
console.log('Count',this)
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
5.4 map简写
上述示例中,调用$store方法时,总是要加一些this.$store.xxx前缀才能获取值或者调用方法,不够简洁。
可以通过如下几个对象进行简化,直接将$store中的属性值或方法映射成vue对象中的属性或方法。
mapState,mapGetters,mapMutations,mapActions
示例:
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h3>当前求和放大10倍为:{{bigSum}}</h3>
<h3>我是{{name}},学习{{subject}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
}
},
computed:{
//借助mapState生成计算属性,从state中读取数据。(对象写法)
// ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),
//借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState(['sum','name','subject']),
//借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
// ...mapGetters({bigSum:'bigSum'})
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters(['bigSum'])
},
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
// ...mapMutations(['JIA','JIAN']),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
// ...mapActions(['jiaOdd','jiaWait'])
},
mounted() {
const x = mapState({he:'sum',xuexiao:'school',xueke:'subject'})
console.log(x)
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
5.5 vuex模块化
vuex支持按业务模块定义state。
5.5.1 定义store
定义模块一:count.js
//求和相关的配置
export default {
namespaced:true,
actions:{
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
},
mutations:{
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
},
},
state:{
sum:0, //当前的和
school:'希昂',
subject:'前端',
},
getters:{
bigSum(state){
return state.sum*10
}
},
}
定义模块二:person.js
//人员管理相关的配置
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
namespaced:true,
actions:{
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert('添加的人必须姓王!')
}
},
addPersonServer(context){
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
response => {
context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
},
error => {
alert(error.message)
}
)
}
},
mutations:{
ADD_PERSON(state,value){
console.log('mutations中的ADD_PERSON被调用了')
state.personList.unshift(value)
}
},
state:{
personList:[
{id:'001',name:'张三'}
]
},
getters:{
firstPersonName(state){
return state.personList[0].name
}
},
}
定义核心store.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
modules:{
countAbout:countOptions,
personAbout:personOptions
}
})
5.5.2 引入store
与之前相同
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store'
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
5.5.3 使用store
使用时注意指定模块。
方式一:使用$store方式
<script>
import {nanoid} from 'nanoid'
export default {
name:'Person',
data() {
return {
name:''
}
},
computed:{
personList(){
return this.$store.state.personAbout.personList
},
sum(){
return this.$store.state.countAbout.sum
},
firstPersonName(){
return this.$store.getters['personAbout/firstPersonName']
}
},
methods: {
add(){
const personObj = {id:nanoid(),name:this.name}
this.$store.commit('personAbout/ADD_PERSON',personObj)
this.name = ''
},
addWang(){
const personObj = {id:nanoid(),name:this.name}
this.$store.dispatch('personAbout/addPersonWang',personObj)
this.name = ''
},
addPersonServer(){
this.$store.dispatch('personAbout/addPersonServer')
}
},
}
</script>
方式二:使用map方式
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
}
},
computed:{
//借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState('countAbout',['sum','school','subject']),
...mapState('personAbout',['personList']),
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('countAbout',['bigSum'])
},
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
},
mounted() {
console.log(this.$store)
},
}
</script>
六、vue-router
6.1 相关理解
vue-router:是vue 的一个插件库,专门用来实现 SPA 应用。
SPA 应用:
是单页 Web 应用(single page web application,SPA)。整个应用只有一个完整的页面,点击页面中的导航链接不会刷新页面,只会做页面的局部更新。数据需要通过 ajax 请求获取。
路由:
一个路由就是一组映射关系(key - value)。key 为路径, value 可能是 function 或 component。
路由分类
- 后端路由
- 理解:value 是 function, 用于处理客户端提交的请求。
- 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
- 前端路由
- 理解:value 是 component,用于展示页面内容。
- 工作过程:当浏览器的路径改变时, 对应的组件就会显示
6.2 基本路由
6.2.1 编写路由步骤
- 定义路由组件
- 注册路由
- 使用路由
6.2.2 定义路由组件
引入VueRouter,创建路由器: router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
6.2.3 注册路由
在main.js中引入路由器,注册路由
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由器
import router from './router'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)
//创建vm
new Vue({
el:'#app',
render: h => h(App),
router:router
})
6.2.4 使用路由
- 借助router-link标签实现路由的切换
- 使用router-view指定组件的呈现位置
App.vue
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- Vue中借助router-link标签实现路由的切换 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'App',
}
</script>
About.vue
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About'
}
</script>
6.2.5 示例效果
默认进入主页,注意路径中包含/#/
点击About路由跳转,注意路径中包含/#/about。About组件被激活。
6.3 嵌套路由
路由支持嵌套(多级)。注意定义和使用方式。
6.3.1 定义多级路由
router/index.js。注意path仅在第一层加了/
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
}
]
}
]
})
6.3.2 使用多级路由
App.vue 同上
<template>
<div>
<div class="row">
<Banner/>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- Vue中借助router-link标签实现路由的切换 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Banner from './components/Banner'
export default {
name:'App',
components:{Banner}
}
</script>
Home.vue 它内部有一层路由,注意跳转路径是父+子:/home/news
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name:'Home',
}
</script>
message.vue
<template>
<div>
<ul>
<li>
<a href="/message1">message001</a>
</li>
<li>
<a href="/message2">message002</a>
</li>
<li>
<a href="/message/3">message003</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name:'Message'
}
</script>
6.3.3 效果
url中路由路径是多级。
6.4 路由传参
路由跳转时,可以传递参数。通过标签的to属性。
6.4.1 使用方式
传参方式:
//简单用法,直接跳转,没有参数
<router-link to="/home/message/detail">{{m.title}}</router-link>
//简单写法:跳转+参数传递
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
//对象写法:跳转+参数传递
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
参数接收:
接收处使用$router对象接收
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
6.4.2 示例
Message路由到Detail中,并传递参数
Message.vue 传递参数
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data() {
return {
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
},
}
</script>
Detail.vue 接收参数
<template>
<ul>
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
mounted() {
console.log(this.$route)
},
}
</script>
效果:
6.5 路由重要属性
6.5.1 路由名称
定义路由时,除了path外,还有一个名称属性name。也可以用于跳转。
使用方式:
// 定义name,注意name不得重复
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
}
]
}
//使用to的对象写法
<router-link :to="{
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
6.5.2 路由的params参数
路由跳转时,除了上述的参数传递方式外,还可以通过params参数的方式传递。概念同java的pathVariable。
使用方式:
//定义路由
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title',
component:Detail,
}
]
}
//接收使用param参数
<template>
<ul>
<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>
</ul>
</template>
6.5.3 路由的props参数
路由跳转时,如果有参数传递,可以将传递的参数转换成to对象的props参数,从而简化使用。
//定义
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
// props:{a:1,b:'hello'}
//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
// props:true
//props的第三种写法,值为函数
props($route){
return {
id:$route.query.id,
title:$route.query.title,
a:1,
b:'hello'
}
}
}
]
}
//使用
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
props:['id','title']
}
</script>
6.5.4 编程式路由
相关 API:
- this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
- this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
- this.$router.back(): 请求(返回)上一个记录路由
- this.$router.go(-1): 请求(返回)上一个记录路由
- this.$router.go(1): 请求下一个记录路由
6.6 两个新的生命周期钩子
router路由切换时,默认是创建和销毁。对应的生命周期是:mounted()和Destory().
但可以通过keep-alive的方式保存组件,切换时不再进行销毁和新建。仅限于在父组件里切换时,如果父组件被切换销毁了,这里还是会被销毁。
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
此时对应的生命周期,就是两个新的生命周期:
- activated : 激活
- deactivated:失活
<script>
export default {
name:'News',
data() {
return {
opacity:1
}
},
beforeDestroy() {
console.log('News组件即将被销毁了')
},
mounted(){
console.log('News组件创建了')
},
activated() {
console.log('News组件被激活了')
this.timer = setInterval(() => {
console.log('@')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated() {
console.log('News组件失活了')
clearInterval(this.timer)
},
}
</script>
6.7 路由守卫
所谓的路由守卫,就是在进入路由前进行校验拦截或放行,以及进入路由后进行一些补充事件。
6.7.1 全局路由守卫
在router/index.js中设置全局路由守卫。
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('name')==='xiang'){
next()
}else{
alert('名称不对,无权限查看!')
}
}else{
next()
}
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.title || '哈哈哈'
})
6.7.2 独享路由守卫
在router/index.js的每个组件上,可以设置独享路由守卫。只有前置,没有后置。
{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
console.log('独享路由守卫',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('name')==='xiang'){
next()
}else{
alert('名称不对,无权限查看!')
}
}else{
next()
}
}
},
6.7.3 组件内路由守卫
可以在组件里,设置beforeRouteEnter和beforeRouteLeave。
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About',
//通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
console.log('About--beforeRouteEnter',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='xiang'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
console.log('About--beforeRouteLeave',to,from)
next()
}
}
</script>
七、Vue UI组件库
7.1 移动端常用UI组件库
- Vant Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.
- Cube UI cube-ui Document
- Mint UI Mint UI