Vue 2.0
Vue.js 是一个用于构建用户界面的渐进式JavaScript框架,它采用组件化和响应式数据绑定的方式,简化了Web应用程序的开发。
一、Vue核心
1、Object.defineProperty
Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。这是Vue核心底层逻辑。
语法
Object.defineProperty(obj, prop, descriptor)
示例
let num = 18;
let person = {
name: 'zs',
sex: 'male'
};
Object.defineProperty(person, 'age', {
value: 18, //添加age属性的value值
enumerbale: true, //控制属性是否可以枚举,默认为false
writeable: true, //控制属性是否可以被修改,默认为false
configurable: true, //控制属性是否可以被删除,默认为false
//当读取person的age属性时,get函数(getter)就会被调用,且返回值 就是age的值
get(){
return num;
},
//当修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
num =value;
}
});
TIP
定义或修改多个属性:
Object.defineProperties(obj, props)
2、模板语法
插值语法
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):
- 功能:用过解析标签体内容
- 语法:,xxx是 js 表达式,且可以直接读取到 data 中的所有属性
<span>Message: {{ msg }}</span>
指令语法
v-bind:href = 'xxxx' 或 :href='xxxx' ,xxxx是 js 表达式被解析,且可以直接读取到 data 中的所有属性
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
3、数据绑定
单向数据绑定
- 语法:v-bind:href ="xxx" 或简写为 :href="xxx"
- 特点:数据只能从 data 流向页面
双向数据绑定
- 语法:v-mode:value="xxx" 或简写为 v-model="xxx"
- 特点:数据不仅能从 data 流向页面,还能从页面流向 data,且只能应用在表单类元素(即value值)
v-model修饰符
- .lazy:懒加载
- .trim:去除前后空格
- .number:将字符串数字转换为数值
4、el和data的两种写法
<div id = "root">
<h1>您好,{{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
//写法一
new Vue({
el: '#root',
data:{
name: 'kite'
}
});
//写法二:推荐
const vm = new Vue({
data(){ //组件内必须这种写法
return {
name:'kite'
}
}
});
vm.$mount('#root');
</script>
5、事件处理
事件基本使用
- v-on:xxx="函数名"
- @xxx="函数名"
- @xxx="函数名(参数)"
- 默认事件形参:event
- 隐含属性对象:$event
TIP
- xxx为事件名,如change、click、mouseover、mouseout、keydown、keyup、load、scorll、wheel等;
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数!否则this指向windows对象而不是vm或组件实例对象;
- @click="demo"和@click="demo($event)"效果是一样的,但后者可以传参;
<div id="root">
<h2>您好,{{name}}</h2>
<button v-on:click="showInfo1">点我(不传参)</button>
<button @click="showInfo2(88)">点我(传参)</button>
</div>
<script type="text/javascript">
new Vue({
el:'root',
data:{
name: 'kite'
},
methods:{
showInfo1(event){
console.log(event.target);
},
showInfo2(num){
console.log(num);
}
}
});
</script>
事件修饰符
- prevent:阻止默认事件
- stop:阻止事件冒泡
- once:事件只触发一次
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素时才触发事件
- passive:事件的默认行为为立即执行,无需等待事件回调执行完毕
<div id="root">
<!-- 第一种写法 -->
<a href="https://www.baidu.com" @click.prevent="showInfo">点我干嘛</a>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
methods: {
showInfo(e){
//e.preventDefault(); //第二种写法
console.log("滚开");
}
}
})
</script>
按键修饰符
默认按键别名
- enter:回车
- delete:删除
- esc:退出
- space:空格
- tab:换行
- up:上
- down:下
- left:左
- right:右
- caps-lock:上一页
<div id="root">
i <input type="text" @click.enter="showInfo">
</div>
<script type="text/javascript">
new Vue({
el:'root',
methods: {
showInfo(e){
conosle.log(e.keyCode, e.key)
}
}
})
</script>
TIP
ctrl/alt/shift/win特殊用法:
- 配合keyup使用:按键的同时,再按下其他键,随后释放其他键,事件方可触发;
- 配合keydown使用:正常触发事件;
自定义按键别名
<div id="root">
<input type="text" @click.huiche="showInfo">
</div>
<script type="text/javascript">
Vue.config.keyCodes.huiche = 13; //自定义按键别名
new Vue({
el:'root',
methods: {
showInfo(e){
conosle.log(e.keyCode, e.key)
}
}
})
</script>
6、computed计算属性
定义
要显示的数据不存在,要通过计算得来,且在 computed 对象中定义计算属性,在页面中使用来显示计算的结果。
原理
底层借助了Object.defineproperty方法提供的getter和setter。
getter和setter调用
- get函数:
- 初次读取时会调用一次
- 当依赖的数据发生改变时会被再次调用
- 初次读取时会调用一次
- set函数
- 计算属性被修改时会调用
与methods区别
- computed:内部有缓存机制(复用)
- methods:内部无缓存机制
<div id="root">
姓:<input type="text" v-model="fistName">
名:<input type="text" v-mode="lastName">
全名:{{fullName}}
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
fistName:'张',
lastName:'三'
},
methods:{
demo(){
//....
}
},
computed:{
//完整写法:可读可写
fullName:{
get(){
return this.firstName + '-' + this.lastName;
},
set(value){
const arr = value.split('-');
this.fisrtName = arr[0];
this.lastName = arr[1];
}
}
//简写:仅可读,不可写
fullName(){
return this.firstName + '-' + this.lastName;
}
}
})
</script>
7、watch监听属性
通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性,当属性变化时, 回调函数自动调用, 在函数内部进行计算。
<div id="root">
<h1>今天的天气很{{info}}</h2>
<button @click="change"></button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
isHot:true
},
methods:{
change(){
this.isHot=!this.isHot;
}
},
computed:{
info(){
return this.isHot?'凉爽':'严热';
}
},
//第一种写法
watch:{
//完整写法
isHot:{ //监听data属性
immediate:true, //初始化时让handler调用一下
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
},
info:{ //监听computed属性
deep:true, //开启深度监听,可以监听对象内部值的改变
handler(newValue,oldValue){
console.log('info被修改了',newValue,oldValue);
}
}
//简写:仅回调函数存在,没有其他配置项
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
}
})
//第二种写法
vm.$watch('isHot',{
immediate:true,
handler(newValue,oldValue){
console.log('info被修改了',newValue,oldValue);
}
})
</script>
TIP
watch与computed之间的区别
- computed能完成的功能,watch都可以完成;
- watch能完成的功能,computed不一定能完成;
8、class与style绑定
语法:v-bind:class='xxx'/v-bind:style='xxx' 或 :class='xxx'/:style='xxx'
- xxx是字符串: 'classA' ,适用于类名不确定且需要动态指定
- xxx是对象: {classA:isA, classB: isB},适用于类名确定、类名数量确定且动态指定
- xxx是数组: ['classA', 'classB'],适用于类名不确定、类名数量不确定
<style>
.base {...}
.mood {...}
</style>
<body>
<div id="root">
<div class="basic" :class="mood">{{name}}</div>
<div class="basic" :class="classArr" :style="styleArr">{{name}}</div>
<div class="basic" :class="classObj" :style="styleObj">{{name}}</div>
</div>
</body>
<scirpt type="text/javascirpt">
new Vue({
el:'#root',
data:{
name:'Vue学生',
mood:'happy',
classArr:['happy','phone','money'],
classObj:{
happy:true,
phone:false,
money:true
},
styleArr:[
{
fontSize:40px,
color:pink
},
{
backgroundColor: hotpink
}
],
styleObj:{
fontSize:30px,
color:red
}
}
})
</script>
9、条件渲染
<div id="app">
<h2 v-show="true">猜猜我是谁</h2>
<h2 v-if="false">猜猜你是谁</h2>
<h2 v-else-if="false">要你管</h2>
<h2 v-else>再见</h2>
</div>
10、列表渲染
<div id="root">
<ul>
//:key不指定,默认为:key="index",即索引号
<li v-for="p in persons" :key="p.id">
{{index}}:{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'小明',age:19},
{id:'002',name:'小红',age:20},
{id:'003',name:'小虎',age:17}
]
}
})
</script>
列表过滤与排序
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keywords">
<button @click="sortType = 0">原顺序</button>
<button @click="sortType = 1">年龄升序</button>
<button @click="sortType = 2">年龄降序</button>
<ul>
<li v-for="(p,index) of filterPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
keywords:'',
sortType:0,
persons:[
{id:'1',name:'马冬梅',age:30,sex: '女'},
{id:'2',name:'周冬雨',age:27,sex:'女'},
{id:'3',name:'周杰伦',age:36,sex:'男'},
{id:'4',name:'温兆伦',age:38,sex:'男'}
]
},
computed:{
filterPersons(){
const personsArr = this.persons.filter((p)=>{
return p.name.indexOf(this.keywords) !== -1;
})
if(this.sortType){
personsArr.sort((a,b)=>{
return this.sortType ===1 ? b.age - a.age : a.age - b.age;
})
}
return personsArr;
}
}
})
</script>
数据监听
- vue会监听data中所有层次的数据。
- vue如何监听对象中的数据:
- 通过setter实现监听,且要在new Vue时就传入要监听的数据 *对象中后追加的属性,Vue默认不做响应式处理 * 如需给后添加的属性做响应式处理,请使用如下API *Vue.set(target, propertyName/index, value) * vm.$set(target, propertyName/index, value)
- 如何监听数组中的数据
- 通过包裹组更新元素的方法实现,本质就是做了2件事 *调用原生对应的方法对数据进行更新 * 重新解析模板,进而更新页面
- 在Vue修改数组中的某个元素一定要用如下方法
- 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- Vue.set(target, propertyName/index, value)
- vm.$set(target, propertyName/index, value)
- 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
TIP
Vue.set()和vm$set()不能给vm或vm的根数据对象添加属性。
11、 收集表单数据
<div id="root">
<form @submit.prevent="demo">、
<!-- trim:删除前后空格 -->
用户名:<input type="text" v-model.trim="userInfo.account">
密码:<input type="password" v-model="userInfo.password">
<!-- number:将字符转换为数字 -->
年龄:<input type="number" v-model.number="userInfo.age">
性别:
<input type="radio" name="sex" v-model="userInfo.sex" value="male">男
<input type="radio" name="sex" v-model="userInfo.sex" value="female">女
爱好:
<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="music">音乐
所在地区:
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="nanjing">南京</option>
<option value="wuhan">武汉</option>
</select>
内容:
<!-- lazy:失去焦点时触发 -->
<textarea v-model.lazy="userInfo.other"></textarea>
<input type="checkbox" v-model="userInfo.isAgree">阅读并同意<a href="javascript:;"《用户协议》</a>
<input type="submit" value="提交">
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip=false;
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
password:'',
age:'',
sex:'female',
hobby:[],
city:'nanjing',
content:'',
isAgree:false
}
}
methods:{
demo(){
console.log(this.userInfo);
}
}
})
</script>
12、过滤器
<div id="root">
<h2>北京时间:{{time | timeFormater}}</h2>
<h2>北京时间:{{time | timeFormater('YYYY-MM-DD')}}</h2>
<h2>北京时间:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h2>
<h3 :hi="msg | mySlice">世界</h3>
</div>
<div id="app">
<h2>{{msg | mySlice}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip=false;
//全局过滤器
Vue.filter('mySlice', function(value){
return value.slice(0,4);
});
new Vue({
el:'#root',
data:{
time:1621561377603,
msg:'你们好呀'
},
filters:{
timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
//需要引入第三方库day.js
return dayjs(value).format(str);
}
}
});
new Vue({
el:'#app',
data:{
msg:'hello'
}
})
</script>
13、指令
内置指令
- v-text : 更新元素的 innerText
- v-html : 更新元素的 innerHTML
- v-if : 如果为 true, 当前标签才会输出到页面
- v-else: 如果为 false, 当前标签才会输出到页面
- v-show : 通过控制 display 样式来控制显示/隐藏
- v-for : 遍历数组/对象
- v-on : 绑定事件监听, 一般简写为@
- v-bind : 绑定解析表达式, 可以省略 v-bind
- v-model : 双向数据绑定
- v-cloak : 无赋值,防止闪现, 与 css 配合: [v-cloak]
- v-once:无赋值,执行一次后变为静态值
- v-pre:无赋值,跳过Vue编译过程
自定义指令
局部自定义指令
//使用指令
v-my-directive='xxx'
directives:{
//注册函数式指令:指令与元素成功绑定时执行,指令所在的模板被重新解析时执行
'my-directive'(el, binding){
el.innerHTML = binding.value.toupperCase();
},
//注册对象式指令
'my-directive':{
//指令与元素成功绑定时执行
bind(el, binding){
el.innerHTML = binding.value.toupperCase();
},
//指令所在元素被插入页面时执行
inserted(el, binding){
el.innerHTML = binding.value.toupperCase();
},
//指令所在的模板被重新解析时执行
update(el, binding){
el.innerHTML = binding.value.toupperCase();
}
}
}
全部自定义指令
//使用指令
v-my-directive='xxx'
//注册函数式指令
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase();
})
//注册对象式指令
Vue.directive('my-directive',{
bind(el, binding){
el.innerHTML = binding.value.toupperCase();
},
inserted(el, binding){
el.innerHTML = binding.value.toupperCase();
},
update(el, binding){
el.innerHTML = binding.value.toupperCase();
}
})
14、生命周期函数
创建阶段
- beforeCreate:数据创建完成之前
- created():数据创建完成之后
挂载阶段
- beforeMount:模板渲染完成之前
- mounted:模板渲染完成之后
更新阶段
- beforeUpdate:数据更新完成之前,模板未渲染
- updated:数据更新完成之后,模板已渲染
销毁阶段
- beforeDestory:模板卸载完成之前
- destoryed:模板卸载完成之后
TIP
- mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等;
- beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等;
- 销毁后借助Vue开发者工具看不到任何信息;
- 销毁后自定义事件支失效,但原生DOM事件依然有效;
- 一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程;
二、Vue组件
在 Vue 组件中,为了保证每个组件实例,维护独立的一份数据对象,data 必须是一个函数形式。每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
1、非单文件组件
注册局部组件
<div id="root">
<h1>{{msg}}</h1>
<!--
* 第三步:使用组件
* <school></school>:双标签可以编译多次
* <school/>:单标签且不使用脚手架时,会导致后续组件无法编译
* <my-school></my-school>:配合脚手架使用,否则无法编译
* <MySchool></MySchool>:配合脚手架使用,否则无法编译
-->
<school></school>
<school />
<my-school></my-school>
<MySchool></MySchool>
</div>
<div id="wrap">
<app></app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script type='text/javascript'>
Vue.config.productionTip = false;
//第一步:创建局部组件
const school = Vue.extend({
name: 'School',
data() {
return {
schoolName: '和仁小学',
address: '和仁付'
}
},
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h3>学校地址:{{address}}</h3>
</div>
`
})
const student = Vue.extend({
template: `
<div>
<h3>学生姓名:{{studentName}}</h3>
<h3>学生年龄:{{age}}</h3>
</div>
`,
data() {
return {
studentName: '李四',
age: 17
}
}
})
const sdt = Vue.extend({
name: 'Sdt',
data() {
return {
studentName: '张三',
age: 18
}
},
template: `
<div>
<h2>学生名称:{{studentName}}</h2>
<h3>学生年龄:{{age}}</h3>
</div>
`
})
const app = Vue.extend({
name: 'App',
template: `
<student></student >
`,
components: {
student: sdt
}
})
//第二步:注册局部组件
new Vue({
el: '#root',
data: {
msg: '欢迎学习Vue!'
},
components: {
school,
'my-school': school,
MySchool: school
}
})
new Vue({
el: '#wrap',
components: {
app
}
})
</script>
注册全局组件
<div id="main">
<!-- 第三步:使用全局组件 -->
<Hello></Hello>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script type='text/javascript'>
Vue.config.productionTip = false;
//第一步:创建全局组件
const Hello = Vue.extend({
name: 'coency', //Vue开发者工具呈现名称
data() { //组件必须使用函数式
return { slogan: 'Hello World!' }
},
template: `
<h2>slogan:{{slogan}}</h2>
`
})
// 第二步:注册全局组件
Vue.component('Hello', Hello)
// 挂载
new Vue({
el: '#main'
})
</script>
TIP
VueComponent
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写
<school/>
或<school></school>
,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的实例对象,以后简称vc(也可称之为:组件实例对象),Vue的实例对象,以后简称vm
- 一个重要的内置关系:VueComponent.prototype.proto == Vue.prototype
2、单文件组件
.vue 文件组成(3 个部分)
HTML模板页面
<template>
<!-- 页面模板 -->
</template>
JS 模块对象
<script>
export default {
name:'',
data() {
return {}
},
methods: {},
computed: {},
components: {}
}
</script>
Style样式
<style>
/* 样式定义 */
</style>
基本使用
- 引入组件
- 映射成标签
- 使用组件标签
3、动态组件
<template>
<div id="app">
<component :is="componentId"></component>
<button @click="bool = !bool">修改bool</button>
</div>
</template>
<script>
import Song from "@/components/song.vue";
import Singer from "@/components/singer.vue";
export default {
name: "App",
data() {
return {
bool: true,
};
},
computed: {
componentId() {
return this.bool ? "Singer" : "Song";
},
},
components: { Song, Singer },
};
</script>
4、缓存动态组件
<!-- 默认缓存所有的动态组件 -->
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
<!-- 只缓存 Song 组件 -->
<keep-alive include="Song">
<component :is="componentId"></component>
</keep-alive>
<!-- 不缓存Song的组件 -->
<keep-alive exclude="Song">
<component :is="componentId"></component>
</keep-alive>
三、Vue脚手架
1、安装脚手架
第一步(仅第一次执行):全局安装@vue/cli。
npm install -g @vue/cli
第二步:切换到你要创建项目的目录,然后使用命令创建项目
vue create 项目文件夹
第三步:启动项目
npm run serve
2、render函数
new Vue({
el:'#root',
//简写
render: h => h(App)
/**
* 完整写法
* render: render(createElement){
* return createElement('h1','你好')
* }
**/
})
TIP
vue.js与vue.runtime.xxx.js区别
- vue.js是完整版的vue,包含核心功能+模板解析器
- vue.runtime.xxx.js是运行版的vue,只包含核心功能,没有模板解析器,所有不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
3、配置文件
查看Vue脚手架默认配置文件
vue inspect > output.js
修改Vue脚手架默认配置文件
新建一个vue.config.js文件
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
}
}
路径写法
<!-- 相对路径 -->
<img src="./assets/logo.png" alt="">
<!-- 根路径 -->
<img src="@/assets/logo.png" alt="">
<!-- 动态绑定路径 -->
<img src="srcURL" alt="">
<script>
export default {
data() {
return {
srcURL: require('@/assets/logo.png')
}
}
}
</script>
4、ref属性
用于给节点打标识。
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<buttom ref="btn" @click="showDom">点击显示DOM元素</buttom>
<School ref="sch"/>
</div>
</template>
<script>
import School from './components/School'
export default {
name:'App',
components:{School},
data(){
return {msg:'欢迎学习vue'}
},
methods:{
showDom(){
console.log(this.$refs.title);
console.log(this.$refs.btn);
console.log(this.$refs.sch)
}
}
}
</script>
TIP
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 获取:this.$refs.xxx
- 标识:
5、mixin混入属性
可以把多个组件共用的配置提出成一个混入对象。
<!-- School.vue -->
<template>
<div>
<h2 @click="showName">学校名称:{{name}}</h2>
<h2>学校地址:{{city}}</h2>
</div>
</template>
<script>
import {mixin} from './mixin'
export default {
name:'School',
data(){
return {
name:'南京大学',
city:'南京'
}
},
mixins:[mixin] //局部混入
}
</script>
<!-- Student.vue -->
<template>
<div>
<h2 @click="showName">学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
import {mixin} from './mixin'
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
},
mixins:[mixin] //局部混入
}
</script>
<!-- mixin.js -->
<script>
export const mixin = {
methods:{
showName(){
console.log(this.name)
}
}
}
// main.js全局混入
import Vue from 'vue'
import App from './App'
import {mixin} from './mixin'
Vue.mixin(mixin)
new Vue({
render: h => h(App)
}).$mount('#root')
</script>
6、plugins插件属性
//plugins.js
export default {
install(Vue, params){
Vue.prototype.demo = () => {console.log('123')}
}
//main.js
import plugins from './plugins.js'
Vue.use(plugins, params) //使用插件
TIP
一般把通用型的方法写在 utils 文件夹内,如 密码验证函数、数组去重、日期格式化函数等。
7、scoped样式
让css样式只在局部作用域生效,防止类名冲突。
<style scoped>
</style>
8、props属性
用于父组件给子组件传递数据。
<!-- App.vue -->
<template>
<div>
<Student name="张三" sex="男" :age="18"/>
</div>
</template>
<script>
import Student from './component/Student'
export default {
name:'App',
components:{Student}
}
</script>
<!-- Student.vue -->
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{age+1}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
msg:'学生信息表',
},
//方式一:数组写法,仅指定名称
props:['name','sex','age']
//方式二:对象写法,指定名称 /类型
props:{
name:String,
sex:String,
age:Number
}
//方式二:对象写法,指定名称 /类型 /必要性 /默认值 /自定义
props:{
name:{
type:String, //字符串型
required:true //必填
},
sex:{
type:String, //字符型
default:20 //不填,默认为20
},
age:{
type:Number, //数值型
validator(value){ //自定义
if(value >= 18){
return true
}else {
return false
}
}
}
}
}
</script>
TIP
- 所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
- props是只读,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告;如果业务需要修改,则复制props的内容到data中,然后去修改data中的数据。
- props 没有接收的标签属性,会出现在 根节点和 vc 的 $attrs 上。
9、组件自定义事件
绑定自定义事件
<!-- 方式一 -->
<Student @addTodo="addTodo"></Student>
<script>
this.$emit('addTodo',this.addTodo)
</script>
<!-- 方式二 -->
<Student ref="list"></Student>
<script>
this.$refs.list.$on('addTodo',this.addTodo)
</script>
解绑自定义事件
//解绑单个自定义事件
this.$off('addTodo')
//解绑多个自定义事件
this.$off(['addTodo','updateTodo'])
TIP
- —种组件间通信的方式,适用于子组件向父组件传数据,子组件给父组件传数据,要在父组件中给子组件绑定自定义事件且事件的回调在父组件中;
- 要自定义事件只能触发一次,需要使用once修饰符或$once方法;
- 组件上要绑定原生DOM事件,需要使用native修饰符;
- this.$refs.xxx.$on('eventName', callback)绑定自定义事件时,回调要么配置在methods中,要么使用箭头函数,否则this指向有问题;
10、组件 v-mode
.sync 修饰符
11、全局事件总线
//第一 步:在main.js中把vm对象挂载到Vue原型上
new Vue({
.....
beforeCreate(){
Vue.prototype.$bus = this;
},
.....
})
//第二步:给vm注册上自定义事件(需要被改变的组件 需要被接收值的组件),如App.vue
new Vue({
......
methods(){
vcName(params){......}
}
......
mounted(){
this.$bus.$on('vcName',this.vcName);//接收数据
},
beforeDestroy(){
this.$bus.$off('vcName');
}
})
//第三步:触发vm上的自定义事件(需要传值给别人的组件),如Student.vue
new Vue({
.....
methods(){
handleName(params){
this.$globalEventBus.$emit('vcName',params); //提供数据
}
}
})
TIP
- 一种组件间通信的方式,适用于任意组件间通信
- 最好在beforeDestroy钩子中,用$off解绑当前组件所用到的事件
12、$nextTick
<div>
<button @click="showIpt">点击显示 input框</button>
<input type="text" v-if="bool" ref="ipt" />
</div>
<script>
new Vue({
.....
showIpt() {
this.bool = true;
console.log(this.$refs);
//使得input具有焦点 得等input渲染完毕之后 再执行
//dom更新完毕 调用的钩子函数
this.$nextTick(() => {
this.$refs.ipt.focus();
});
}
})
</script>
TIP
- 在下一次DOM更新结束后执行其指定的回调
- 当改变数据后,要基于更新后的新DOM进行操作时,要在nextTick所指定的回调函数中执行
13、动画
<template>
<!-- name不写时,动画默认为一样,appear不写时,默认进入无动画 -->
<transition name="todo" appear>
<div v-show="isShow">Hello World!</div>
</transition>
</tempalte>
<script>
new Vue({
el:'#root',
data:{
isShow:'true'
}
})
</script>
<style>
/* todo为template的name,若name不一样,可设置不同的动画*/
.todo-enter-active {
transition: atguigu 1s linear;
}
.todo-leave-active {
transition: atguigu 1s linear reverse
}
@keyframes atguigu {
from {
transform: translateX(100%)
}
to {
transform: translateX(0)
}
}
</style>
14、过渡
单个元素过渡
<template>
<transition name="todo" appear>
<div v-show="isShow">Hello World!</div>
</transition>
</tempalte>
<script>
new Vue({
el:'#root',
data:{
isShow:'true'
}
})
</script>
<style>
.todo-enter, .todo-leave-to {
transform: translateX(100%);
}
.todo-leave, .todo-enter-to{
transform: translateX(0);
}
.todo-enter-active, .todo-leave-active {
transition: 1s linear;
}
</style>
多个元素过渡
<template>
<transition-group name="todo" appear>
<!-- 必须要具有 key值 -->
<div v-show="!isShow" key="1">Hello World!</div>
<div v-show="isShow" key="2">你好,世界!</div>
</transition-group>
</template>
<script>
new Vue({
el:'#root',
data:{
isShow:'true'
}
})
</script>
<style>
.todo-enter, .todo-leave-to {
transform: translateX(100%);
}
.todo-leave, .todo-enter-to{
transform: translateX(0);
}
.todo-enter-active, .todo-leave-active {
transition: 1s linear;
}
</style>
四、Vue之ajax
1、解决跨域
方法一
在vue.config.js中添加如下配置
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
TIP
- 优点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,才会将请求会转发给服务器((优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
ws: true,
changeOrigin: true
},
'/foo': {
target: '<other_url>'
}
}
}
}
TIP
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
2、slot插槽
用于父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式。
默认插槽
<!-- 父组件 -->
<Category>
<div>html结构1</div>
</Category>
<!-- 子组件Category -->
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
具名插槽
父组件指明放入子组件的哪个插槽slot="footer",如果是template可以写成v-slot:footer
<!-- 父组件 -->
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
<!-- 子组件Category -->
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
作用域插槽
用于父组件往子组件插槽放的html 结构接收子组件的数据scope="scopeData"。
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
<!-- 父组件 -->
<Category>
<template scope="scopeData">
<ul>
<li v-for="g in scopeData.gamesDate" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<!-- 解构赋值方式 -->
<template scope="{gamesData}">
<ul>
<li v-for="g in gamesData" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<!-- 普通节点方式:不能直接用scope,得用slot-scope -->
<ul slot-scope="{gamesDate}">
<li v-for="g in gamesDate" :key="g">{{g}}</li>
</ul>
</Category>
<!-- 子组件Category -->
<template>
<div>
<slot :gamesDate="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
//数据在子组件自身
data() {
return {
games: [
{ id: 0, name: "zhuque" },
{ id: 1, name: "wanzi" },
{ id: 2, name: "yingtao" },
{ id: 3, name: "afei" },
]
}
}
}
</script>
五、Vuex状态管理
1、安装
npm install vuex@next --save
2、创建store
创建src/store/index.js该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
import Vuex from 'vuex' // 引入Vuex
Vue.use(Vuex) // 应用Vuex插件
// 创建并暴露store
export default new Vuex.Store({
actions:{ // 准备actions——用于响应组件中的动作
/** 绕过Action时,不需要再写
add(context,value){
context.commit('add',value)
},
cut(context,value){
context.commit('cut',value)
},
*/
addOdd(context,value){ // context 相当于精简版的 $store
if(context.state.sum % 2){
context.commit('add',value)
}
},
addWait(context,value){
setTimeout(()=>{
context.commit('add',value)
},500)
}
}
mutations:{ // 准备mutations——用于操作数据(state)
add(state,value){
state.sum += value
},
cut(state,value){
state.sum -= value
}
}
state:{ // 准备state——用于存储数据
sum:0 //当前的和
}
})
3、配置store
在src/main.js中创建vm时传入store配置项
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 引入store
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App),
store, // 配置项添加store
beforeCreate() {
Vue.prototype.$bus = this
}
})
4、调用store
在src/components/Count.vue中创建Count.vue子组件调用store
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="cut">-</button>
<button @click="addOdd">当前求和为奇数再加</button>
<button @click="addWait">等一等再加</button>
</div>
</template>
<script>
export default {
name: 'Count',
data() {
return {
num: 1, //用户选择的数字
}
},
methods: {
add() { //绕过Action,直接对接Commit
this.$store.commit('add', this.num)
},
cut() { //绕过Action,直接对接Commit
this.$store.commit('cut', this.num)
},
addOdd() {
this.$store.dispatch('addOdd', this.num)
},
addWait() {
this.$store.dispatch('addWait', this.num)
},
}
}
</script>
6、getters配置项
在store.js中追加getters配置
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
// 创建并暴露store
export default new Vuex.Store({
......
getters
})
组件中读取数据$store.getters.bigSum
6、四个map方法使用
mapState方法:用于帮助映射state中的数据为计算属性
computed: {
// 借助mapState生成计算属性:sum、school、subject(对象写法一)
...mapState({
sum:'sum',
school:'school',
subject:'subject'
}),
// 借助mapState生成计算属性:sum、school、subject(数组写法二)
...mapState(['sum','school','subject']),
}
mapGetters方法:用于帮助映射getters中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法一)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法二)
...mapGetters(['bigSum'])
}
mapActions方法:用于帮助生成与actions对话的方法,即包含$store.dispatch(xxx)的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
mapMutations方法:用于帮助生成与mutations对话的方法,即包含$store.commit(xxx)的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
7、模块化+命名空间
修改store.js
const countAbout = {
namespaced: true, // 开启命名空间
actions: { ... },
mutations: { ... },
state: {x:1},
getters: {
bigSum(state){ return state.sum * 10 }
}
}
const personAbout = {
namespaced: true, // 开启命名空间
actions: { ... }
mutations: { ... },
state: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
开启命名空间后,组件中读取state数据
// 方式一:自己直接读取
this.$store.state.personAbout.list
// 方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject'])
开启命名空间后,组件中读取getters数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'})
六、Router 路由
1、基本路由使用
src/router/index.js
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About'
import Home from '../pages/Home'
// 创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
src/main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router' // 引入VueRouter
import router from './router' // 引入路由器
Vue.config.productionTip = false
Vue.use(VueRouter) // 应用插件
new Vue({
el:'#app',
render: h => h(App),
router
})
src/App.vue
<!-- Vue中借助router-link标签实现路由的切换 -->
<router-link class="item" active-class="active" to="/about">About</router-link>
<router-link class="item" active-class="active" to="/home">Home</router-link>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
TIP
- 路由组件通常存放在pages或views文件夹,一般组件通常存放在components文件夹
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的**$route**属性,里面存储着自己的路由信息
- 整个应用只有一个router,可以通过组件的**$router**属性获取到
2、嵌套路由
配置路由规则,使用children配置项
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ // 通过children配置子级路由
{
path:'news', // 此处一定不要带斜杠,写成 /news
component:News
},
{
path:'message', // 此处一定不要写成 /message
component:Message
}
]
}
]
跳转(要写完整路径)
<router-link to="/home/news">News</router-link>
3、路由 query 参数
传递参数
// 跳转并携带query参数,to的字符串写法
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
// 跳转并携带query参数,to的对象写法(推荐)
<router-link
:to="{
path:'/home/message/detail',
query:{
id: m.id,
title: m.title
}
}"
>跳转</router-link>
接收参数
$route.query.id
$route.query.title
4、路由 params 参数
配置路由,声明接收params参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // 使用占位符声明接收params参数
component:Detail
}
]
}
]
}
传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="`/home/message/detail/${id}/${title}`">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
TIP
路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数
$route.params.id
$route.params.title
5、命名路由
给路由命名
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' // 给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
简化路由的跳转
//简化前,需要写完整的路径
<router-link to="/demo/test/welcome">跳转</router-link>
//简化后,直接通过名字跳转
<router-link :to="{name:'hello'}">跳转</router-link>
//简化写法配合传递参数
<router-link
:to="{
name:'hello', //name和path不能一起使用,只能2选1
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
6、路由 props 配置
让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
props:{a:900}
//第二种写法:props值为布尔值,只能配合params使用,不能配合query使用;为true时,则把路由收到的所有params参数通过props传给Detail组件
props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return {
id: $route.query.id,
title: $route.params.title
}
}
}
7、路由跳转 replace 方法
浏览器的历史记录有两种写入方式:
- push是追加历史记录
- replace是替换当前记录,路由跳转时候默认为push方式
开启replace模式
//完整写法
<router-link :replace="true" ...>News</router-link>
//简写
<router-link replace ...>News</router-link>
8、命名视图
同时(同级)展示多个视图 router-view 设置名字 默认 default
path: '/about',
name: 'About',
//换成 components 接收 {}
components: {
default: About,
pageA: () => import('../views/Singer.vue'),
pageB: () => import('../views/Song.vue')
}
<!-- 默认展示default -->
<router-view></router-view> //about组件在这显示
<router-view name="pageA"></router-view>
<router-view name="pageB"></router-view>
9、路由别名
可以通过别名访问到 /home路由组件
path: '/home',
name: 'Home',
component: Home,
alias: ['/home1', '/home2']
10、路由重定向
当访问根路由时跳转到 /home
path: '/',
redirect: '/home'
11、编程式路由导航
不借助<router-link>
(如button)实现路由跳转,让路由跳转更加灵活
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward()
this.$router.back()
this.$router.go(n)
12、缓存路由组件
让不展示的路由组件保持挂载,不被销毁
// 缓存一个路由组件
<keep-alive include="News"> // include中写想要缓存的组件名,不写表示全部缓存
<router-view></router-view>
</keep-alive>
// 缓存多个路由组件
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
activated与deactivated
activated和deactivated是路由组件所独有的两个钩子,用于捕获路由组件的激活状态
- activated路由组件被激活时触发
- deactivated路由组件失活时触发
<template>
<ul>
<li :style="{opacity}">欢迎学习vue</li>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
<li>news003 <input type="text"></li>
</ul>
</template>
<script>
export default {
name:'News',
data(){
return{
opacity:1
}
},
activated(){
console.log('News组件被激活了')
this.timer = setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated(){
console.log('News组件失活了')
clearInterval(this.timer)
}
}
</script>
13、路由守卫
全局守卫
// 全局前置守卫:初始化时、每次路由切换前执行
router.beforeEach((to,from,next) => {
if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制,meta写在路由规则里
if(localStorage.getItem('school') === 'atguigu'){ // 权限控制的具体规则
next() // 放行
}else{
alert('暂无权限查看')
}
}else{
next() // 放行
}
})
// 全局后置守卫:初始化时、每次路由切换后执行
router.afterEach((to,from) => {
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
独享守卫
//写在路由规则里
beforeEnter(to,from,next){
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
}
}
组件内守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {... next()}
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {... next()}
//通过路由规则,该组件被复用时调用
beforeRouteUpdatee(to, from, next) {... next()}
14、路由器工作模式
hash模式
- 后端不会把 /#/home/singer 识别为后端路由 (不会和后端路由发生冲突 )
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
history模式
- 再次刷新页面会把 /home/singer 当做后端路由,页面就会丢掉,可后端配合解决
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
const router = new VueRouter({
mode:'history',
routes:[...]
})
export default router
TIP
若使用Node搭建本地服务器,可npm安装connect-history-api-fallback插件解决404问题。