# 基本语法
# 渐进式框架
# 渐进式
- 通过各种指令进行申明式渲染
- 组件系统
- 页面路由
- 大型的状态管理-VUEX
- 构建整个系统
# 框架
- Jquery是JS库,函数的集合,不提供逻辑,逻辑由程序员自己控制
- VUE是JS框架,一整套的解决方案,大部分逻辑已经确定好
# MVVM
一种更好的UI模式解决方案,通过数据双向绑定让数据自动的双向同步
- M:Model数据模型
- V:view试图(页面)
- VM:ViewModel视图模型
# 插值表达式
在data中必须存在: { { msg } }
# 指令(14个)
# v-bind
- 动态地绑定一个或多个属性,或一个组件 prop 到表达式,简写
:
- class和style可以绑定对象或数组
<!--数据 obj:{class1:true,class2:true,class3:true}-->
<div class="base fz" :class="obj"></div>
<!--数据 arr:['class1','class2','class3']-->
<div class="base fz" :class="arr"></div>
<!--注意单括号-->
<div class="base fz" :class="{class1:true}"></div>
<!--数据 w:'200px'-->
<div :style="{width:w}"></div>
<div :style="{fontSize:12px}"></div>
# v-model
在表单控件或者组件上创建双向绑定,会忽略掉表单元素原本的value
- 收集表单数据
<input type="text"/> # v-mode1收集的是value值,用户输入的就是value值
<input type="radio"/> # v-model收集的是value值,且要给标签配置value值
<input type="checkbox"/>
# 没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
# 配置input的value属性,并且v-model的初始值是数组,那么收集的的就是value组成的数组
三个修饰符:
v-model.lazy # onChange时触发
v-model.number # 输入框的内容转为数字
v-model.trim # 去除数据前后的空格
- 视图改变数据跟着改变
<p></p>
<input type="text">
<script>
const data={msg:'哈哈哈'};
const p=document.querySelector('p');
const input=document.querySelector('input');
p.innerText=data.msg;
input.value=data.msg;
// 键盘弹起时触发,不管弹起的什么键都会触发
input.addEventListener('keyup',function(){
console.log('keyup');
});
// 输入完毕后触发
input.addEventListener('change',funtion(){
console.log('change');
})
// 只要input框中输入内容就会触发
input.addEventListener('input',function(){
console.log('input');
data.msg=input.value;
})
</script>
- 数据改变视图跟着改变
- angular.js 1.0版本通过脏数据检查机制(数据轮询),性能比较低,兼容IE8
- vue使用的数据劫持,ES5的语法:Object.defineProperty(),不兼容IE678
<p></p>
<input type="text">
<script>
const data={msg:'哈哈哈'};
let temp=data.msg;
// 作用:给对象的某个属性增加修饰
// 参数:对象名、属性名、修饰(是一个对象)
Object.defineProperty(data,'msg',{
// get方法会再获取到msg这个属性的时候执行
// 劫持后获取不到msg原来的值,需要先定义let temp=data.msg
get:funtion(){
return temp;
},
// set方法会劫持到msg这个属性的修改操作
set:function(value){
temp=value;
}
})
</script>
# v-on
绑定事件监听,简写 @
事件修饰符:
.stop # 阻止冒泡,等效于event.stopPropagation()
.prevent # 阻止默认事件,等效于event.preventDefault()
.capture # 捕获到事件时使用,先父再子,默认是先子再父(冒泡)
.self # 点击元素本身上触发,可能不在子元素上
.once # 只触发一次回调
.left # 只当点击鼠标左键时触发
.right # 只当点击鼠标右键时触发
.middle # 只当点击鼠标中键时触发
.passive # 以 { passive: true } 模式添加侦听器
.native # 监听组件根元素的原生事件
@dblclick # 双击事件
@keyup # 按键事件 @keyup.enter/tab/delete/esc/space……
<!--capture捕获到事件,先执行father()再执行child()-->
<a @click.capture="father()">
<button @click.capture="child()"></button>
</a>
<!--事件传递参数-->
<!--事件处理函数中增加两个参数 $event,item 。 item就是要传递的对象参数-->
<el-radio v-model="item" label="A" @change="answer($event, item)"></el-radio>
<script>
// 也可以自己定义按键
Vue.config.keyCodes.sylone=13
// 使用
@keyup.sylone
</script>
注意
在开发过程中会遇到按键修饰符不生效的情况,此时我们需要加上 .native 按键修饰符
// 只适用于 input 框 获得焦点 时按下回车时生效,失去焦点时,此功能仍不可用
<input v-on:keyup.enter.native="submit">
// 如果是button按钮,那么应该把它绑定在document上
created: function () {
document.onkeyup = e => {
if (e.keyCode === 13 && e.target.baseURI.match('/')) {
this.onSubmit('form')
}
}
}
# v-text/v-html
- v-text:更新元素的innerText属性(textContent属性),不如插值表达式好用
- v-html:更新元素的innerText属性(textContent属性),可以识别html标签
<div id="app">
<p>{{content}}</p>
<p v-text="text"></p>
<p v-html="html"></p>
</div>
<script>
new Vue({
el:"#app",
data:{
content:"<p>测试差值表达式</p>",
text:"<p>测试v-text指令</p>",
html:"<p>测试v-html指令</p>",
}
})
</script>
# v-show/v-if v-else-if v-else
- v-show:通过display:none隐藏,用于频繁的显示和隐藏
- v-if:通过删除或者创建一个元素来显示或隐藏
# v-for
- 维护状态:不加key默认使用"就地更新"策略,在有临时状态的元素(checkbox)时会出现bug
- 使用
key='index'
虽然不会报错,但就地更新策略默认用的就是index,建议还是用id key的作用 (opens new window)
# v-pre/v-once
作用时用于性能优化,如果有大量的文字,不需要vue进行编译的时候
不要轻易使用,除非能明显感觉到速度变慢的时候才会使用
- v-pre:会跳过插值表达式的编译
- v-once:插值表达式会编译一次,后续数据更新,插值表达式不会更新
<div id="app">
<div v-pre>{{ msg }}</div>
<div v-once>{{ msg }}</div>
<div>{{ msg }}</div>
</div>
<script src="vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
# v-cloak
- 用于解决插值表达式的闪烁问题,需要添加样式
[v-cloak]{display:none;}
- 只有在通过script引用vue.js文件的时候才会用到v-cloak
# 计算属性 computed
- 计算属性性能非常高,基于缓存实现,只有当它依赖的属性发生改变,才会重新执行
- 计算属性的完整形态:如果需要修改计算属性的值
<div id="app">
<input type="text" placeholder="请输入你的姓" v-model="lastName">
<input type="text" placeholder="请输入你的名" v-model="firstName">
<input type="text" placeholder="你的名字是" v-model="fullName">
</div>
<script>
const vm=new Vue({
el:'#app',
data:{
lastName:'',
firstName:''
},
computed:{
// 普通用法
fullName:function(){
return this.lastName+''+this.firstName
}
fullName() {
return this.lastName+''+this.firstName
}
// 完整形态
fullName:{
get(){
return this.lastName+''+this.firstName
},
set(value){
this.lastName=value.split(' ')[0];
this.firstName=value.split(' ')[1];
}
}
}
})
</script>
# 侦听器watch
使用watch来响应数据的变化,watch的用法大致有三种
# 第一种:简单用法
直接在watch里面写一个监听处理函数,当每次监听到 cityName 值发生改变时,执行函数
<input type="text" v-model="cityName"/>
new Vue({
el: '#root',
data: {
cityName: 'shanghai'
},
watch: {
cityName(newName, oldName) { // ... }
}
})
//也可以在所监听的数据后面直接加字符串形式的方法名
watch: {
cityName: 'nameChange'
}
# 第二种:使用immediate和handler
这样使用watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性 比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true
new Vue({
el: '#root',
data: {
cityName: ''
},
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
immediate: true
}
}
})
# 第三种:使用deep深度监听
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
<input type="text" v-model="cityName.name"/>
const vm=new Vue({
el: '#root',
data: {
cityName: {id: 1, name: 'shanghai'}
},
watch: {
cityName: {
handler(newName, oldName) { // ... },
deep: true,
immediate: true
}
}
})
设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性:
watch: {
'cityName.name': {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
}
# 第四种:使用$watch进行属性监听
//监听单个属性
vm.$watch('cityName',function(newValue,oldValue){
// ...
})
//监听多个属性
vm.$watch(function(){
return this.name+this.age;
},function(newValue,oldValue){
console.log(newValue+''+oldValue);
})
# 过滤器 filters
常用于格式化我们的文本,其结构为
<!--过滤器接收的第一个参数是companyName-->
<div>{{ companyName | Name }}</div>
<script>
const vm=new Vue({
el:'#app',
data: {
companyName: '浙江杭州阿里巴巴',
},
// 局部过滤器 :只有在当前实例中能使用的过滤器
filters: {
Name: function (value) {
return value.substr(0, 4);
}
}
})
// 全局过滤器 :可以在所有vue实例中都能使用的过滤器
Vue.filter('Name',funtion(value){
return value.substr(0, 4);
})
</script>
- 可以传递参数
<div>{{ companyName | Name(4) }}</div>
<script>
filters: {
Name: function (value,length) {
return value.substr(0, length);
}
}
</script>
- 可以传递多个过滤器
<!--将name1过滤后的数据再传递给name2-->
<div>{{ companyName | Name1 | Name2}}</div>
# axios
- vue1.0使用vue-resource,引入它之后可以基于全局Vue,基于xhr,支持jsonp跨域
- vue2.0开始使用axios,是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,不支持jsonp
axios({
method:'post',
url:'/user/list',
params:'id=2', // 设置URL地址里面的参数
data:{
firstName:'zhao',
lastName:'sylone'
}
}).then(res=>{
// res.data
}).catch(err=>{ // 使用箭头函数
// err
})
# DOM的异步更新
- DOM渲染完成后会立即执行nextTick
- vue在数据发生改变的时候,视图会自动跟着改变,但这个过程是异步的,我们在修改完数据后不能立马获取更新后的DOM结构,为了提高渲染的性能,vue中会等待数据都修改完成才会渲染DOM
// 全局的
Vue.nextTick(function(){
})
// 局部的,自动绑定到调用它的实例上
vm.$nextTick(function(){
})
// 或者
// created的先执行,再执行nextTick
// 此时的nextTick可以获取到dom结构
created(){
this.$nextTick(function(){
console.log('这是执行了nextTick')
})
},
mounted(){
console.log('这是执行了mounted')
}
# 动态添加/修改的属性不是响应式
如果给data中的引用类型(数组或对象)动态添加或修改了一个属性,这个属性不是响应式的
// 参数1:需要添加属性的对象,参数2:增加的属性名,参数3:属性的值
Vue.set(this.car,'color','red');
vm.$set(this.car,'color','red');
this.$set(this.car,'color','red');
// 更新数组的值
this.$set(this.person.hobby,0,'逛街');
// 添加一个属性'brand'值为'bmw'
this.$set(this.car,'brand','bmw');
// 删除一个属性'brand'
this.$delete(this.car,'brand');
Vue.delete(this.car,'color','red');
# ref操作DOM
- 给元素设置ref的属性值,在Vue的实例选项mounted方法中通过this.$refs.属性值 获取到要操作的DOM
- 给组件设置ref的属性值,可以在父组件中使用$refs访问子组件
<div id="app">
<child ref="btn1"></child>
<!--添加了ref 使用默认事件时需要加native-->
<child ref="btn2" @click.native="show"></child>
</div>
<script>
Vue.component('child',{
template:'<button>{{count}}</button>',
data(){
return count:0
},
method(){
hello(){
console.log('hello')
}
this.$on('childmethod',function(){
console.log('我是子组件的方法')
})
}
})
const vm=new Vue({
el:'#app'
})
// 获取值
vm.$refs.btn1.count=0;
vm.$refs.btn2.count=0;
// 也可以修改值
vm.$refs.btn1.count=1;
vm.$refs.btn2.count=2;
// 操作子组件的方法
this.$refs.btn1.hello();
// 或者
this.$refs.btn1.$emit('childmethod',数据)
// 在组件对象中使用$el获取DOM节点
const navbar = this.$refs.nav.$el.offsetTop
</script>
# 完整的定时器
const vm=new Vue({
el:'#app',
data:{
msg:'张三的速递',
timeID:''
},
methods:{
start(){
// 如果有定时器在跑,直接结束
if(this.timeID){
return
}
this.timeID=setInterval(()=>{
// 文字跑马灯,每次取字符串的第一个字符放到最后
this.msg=this.msg.slice(1)+this.msg.slice(0,1)
},300)
},
end(){
clearInterval(this.timeID)
// 清空定时器ID
this.timeID=''
}
}
})
# 生命周期
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue'
},
beforeCreate(){
console.log('beforeCreate','会在vue实例数据初始化前执行');
},
created(){
console.log('created','会在vue实例数据初始化后执行');
},
beforeMount(){
console.log('beforeMount','在渲染的结构替换el之前执行');
},
mounted(){
console.log('mounted','在渲染的结构替换el之后执行');
},
beforeUpdate(){
console.log('beforeUpdate','数据发生改变,DOM更新之前执行');
},
Updated(){
console.log('beforeUpdate','数据发生改变,DOM更新之后执行');
},
beforeDestory(){
console.log('beforeDestory','vue实例销毁前执行');
},
destoryed(){
console.log('beforeDestory','vue实例销毁后执行');
}
})
# created 创建
钩子函数,类似Uniapp的OnLoad,发送ajax、从缓存读取数据都在此方法内
# mounted 挂载
钩子函数,一般在初始化页面完成后,可以对dom节点进行相关操作,类似Uniapp的OnReady,通常是为 metheds 函数提前定义
mounted() {
this.initData()
},
methods: {
// initData:function() { }的简写
initData() {
}
}
# 组件使用
模块化:一个JS文件就是一个模块,把一个独立的功能写到一个单独的JS文件,称之为一个模块
组件化:一个组件会包含有结构、样式、功能(js),一个组件就是一个vue实例
// 全局组件,在所有的vue实例中都可以使用
// 参数1:组件名 参数2:可以配置和vue实例相同的配置 methods/computed/watch/template
Vue.component('demo',{
template:'<div>这是一个全局组件</div>'
// 以下是错误的,只能有一个根元素
// template:'<div>hello</div><div>hello</div>'
})
// 根组件
const vm=new Vue({
el:'#app',
data:{},
// 局部组件
componets:{
demo:{
template:'<div>这是一个局部组件</div>'
},
// 组件名是多个字母 或者 MyDemo
"my-demo":{
template:'<div>这是一个局部组件</div>'
}
}
})
注意
- template参数是必须的,并且template中只能有一个根元素
- VueComponent的实例对象,简称vc,也成为组件实例对象
- Vue的实例对象简称vm,一个项目只有一个在main.js中
# render函数
在main.js中引入的是残缺版的vue不包含template,需要使用render函数接收createElement指定具体内容
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
router,
store,
// 将App组件放入容器中
render: h => h(App)
// render:q=>q('h1','你好啊' )
}).$mount('#app')
<template>
<div id="app">
<img alt="vue logo" src="./assets/logo.png" />
<HelloWorld mgs="Welcome to your vue" name="李四" age="15"></HelloWorld>
</div>
</template>
import { createApp, defineComponent, h } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const img = require('./assets/logo.png')
const App = defineComponent({
render() {
// 第一个参数 节点类型 p为dom原生节点,需要通过字符串"p"来标识
// 第二个参数 节点属性 p节点得属性
// 第三个参数 节点的孩子节点 内部节点(子内容)
return h('p', { id: 'app' }, [
h('img', {
alt: 'vue.logo',
src: img,
}),
h(HelloWorld, {
msg: 'Welcome to your vue',
name: '李四',
age: 15
}),
])
},
})
vue.js与vue.runtime.xxx.js的区别
- vue.js是完整版的Vue,包含:核心功能 + 板解器
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器
- 普通的vc组件中是通过 vue-template-compiler(package.json中) 组件解析template
# template属性
定义模板的四种形式:
- 直接使用字符串
- < script type="text/x-template" id="tpl1">,使用template:"#tpl1"
- 标签< template id="tpl1">,使用template:"#tpl1"
- 使用.vue组件
# el属性
<div id="root">
<h1>你好,{{name}}</h1>
</div>
<script>
Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
const v = new Vue({
el: "#root",
data:{
name:"zhangsan"
}
})
// 或者
v.$mount("#root")
</script>
# data属性
组件中的data必须是一个函数,且函数内部需要返回一个对象,这样可以保证每个组件的数据是独立的
Vue.component('demo',{
template:'<div>这是一个全局组件</div>',
data:function(){
return {
money:100
}
}
// 或者是
data(){
return {
money:100
}
}
})
# Prop属性
- props中的数据是不能修改的,只读的,单向数据流,防止意外改变父组件的信息
- 如果props传递的是对象,是可以增、删、改对象的某个属性的,但是不提倡这样,不容易定位错误
Vue.component('demo',{
props:{
propA:Number, // 基础类型检测,首字母大写
propA:[ Number,String ], // 多种类型检测
propC:{
// 必须是字符串
type:String,
required:true
},
propD:{
// 有默认值
type:Number,
default:100
}
}
})
- html的属性名是忽略大小写的,所以Prop中不能出现大写,或者camelCase(驼峰命名法) 的prop名需要使用其等价的kebab-case(短横线分隔命名)命名
<!--在HTML中是 kebab-case-->
<blog post-title="hello!"></blog>
<script>
Vue.component('blog', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'], // 数组不支持校验
template: '<h3>{{ postTitle }}</h3>'
})
</script>
- 非props属性,会自动合并到子组件上,class和style也会自动合并
<div id="app">
<hello class="aa" style="color:#ff0000"></hello>
</div>
<script>
Vue.component('hello',{
template:'<div class="bb" style="font-size:14px">hello vue</div>'
})
</script>
# is属性
像table、ol、ul、select这种有特殊结构的html标签,不能直接使用组件
<table id="app">
<!--自定义标签嵌套失效-->
<!--<hello></hello>-->
<tr is="hello"></tr>
</table>
<script>
Vue.component('hello',{
template:'<h1>hello vue</h1>'
})
const vm=new Vue({
el:'#app'
})
</script>
# 插件
相信很多人在用Vue使用别人的组件时,会用到 Vue.use(),例如:Vue.use(VueRouter)、Vue.use(Vuex)
但是用 axios时,就不需要用 Vue.use(axios),是因为开发者在封装 axios 时,没有写 install 这一步
<!--1、在 Loading.vue 中定义一个组件-->
<template>
<div class="loading-box">
Loading...
</div>
</template>
<script>
//2、在index.js中引入Loading.vue,并导出
import LoadingComponent from './loading.vue'
const Loading={
// install 是默认的方法
// 当外界在 use 这个组件的时候,就会调用本身的 install 方法,同时传一个 Vue 这个类的参数
install:function(Vue){
Vue.component('Loading',LoadingComponent)
}
}
export default Loading
//3、在main.js中引入loading文件下的index
import Loading from './components/loading/index'
// 这时需要 use(Loading),如果不写 Vue.use()的话,浏览器会报错
Vue.use(Loading)
</script>
本质是一个包含install方法的对象,install的第一个参数是vue,第二个以后的参数是传递的数据
对象.install = function (Vue, options) {
// 添加全局过滤器
Vue.filter(……)
// 添加全局指令
Vue.directive(……)
// 配置全局混入
Vue.mixin(……)
// 添加实例方法
Vue.prototype.$myMethod = function(){……}
Vue.prototype.$myProperty = xxx
}
// 使用插件
Vue.use()
注意
通过全局方法 Vue.use() 使用插件,Vue.use 会自动阻止多次注册相同插件
# 父传子
子组件通过属性props,可以接受父组件传递过来的数据
<div id='app'>
<son :money='money' :car='car'></son>
</div>
<script>
Vue.component('son',{
template:'<div>这是子组件 {{money}} {{car}}</div>',
props:['money','car']
});
const vm=new Vue({
el:'#app',
data:{
money:1000,
car:'朗逸'
}
})
</script>
# 子传父
- 子组件触发一个自定义事件,通过this.$emit()触发某个实例来传递事件名和参数
- 父组件给子组件注册自定义事件,使用@标记
- 父组件提供一个方法,注册事件时使用的方法
<div id='app'>
<!--第一种方式,只触发一次可以加once-->
<son @son-fn='parentFn'></son>
</div>
<script>
Vue.component('son',{
template:`<div>这是子组件 {{money}} <button @click='fn'>传值给父组件</button> </div>`,
data:{
money:1000,
car:'朗逸'
},
methods:{
fn(){
// 传递的参数可以是一个对象
this.$emit('son-fn',this.money,this.car);
// 只触发一次
this.$once('son-fn',this.money,this.car);
}
}
});
const vm=new Vue({
el:'#app',
data:{
money:1000,
car:'朗逸'
},
mounted(){
// 第二种方式
this.$on('son-fn',this.parentFn)
},
methods:{
parentFn(money,car){
this.money=money;
this.car=car;
}
}
})
</script>
# 任意组件间通信
- 创建一个bus(事件总线:event bus),所有组件都能访问,bus实质是一个空的vue实例
- vm.$on 注册当前实例上的自定义事件,可以由vm.$emit触发
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
// 1 注册一个bus:事件总线 main.js
new Vue({
el:'#app',
render: h=> h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
Vue.component('jack',{
template:'<div>我是Jack組件 <button @click="say">Jack说</button></div>',
data:(){
return {
msg:'you jump,i look'
}
},
methods:{
say(){
// 2 去触发bus的一个自定义事件,可以传递参数
this.$bus.$emit('shuo',this.msg)
}
},
beforeDetory(){
this.$bus.$off('shuo') // 解绑单个事件
this.$bus.$off(['shuo1'],['shuo2']) // 解绑多个事件
this.$bus.$off() // 解绑所有事件
}
})
Vue.component('rose',{
template:'<div>我是Rose組件 <font>{{msg}}</font></div>',
data(){
return {
mag:''
}
},
// 3 注册这个自定义的事件,提供一个函数,通过这个函数传递参数,这个事件注册的越早越好
created(){
// 必须使用箭头函数,否则this指向的是bus
this.$bus.$on('shuo',(msg)=>{
this.msg=msg
})
}
})
# keep-alive
是Vue提供的一个抽象组件,用来对组件进行缓存,而不是销毁它们,从而节省性能,由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个DOM元素
- 当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行
- 当组件使用keep-alive的时候created钩子函数只会被执行一次
<!--创建一个A组件-->
<template>
<div>
<h1>我是A组件</h1>
</div>
</template>
<script>
export default {
name: "index",
// 当第一次点击切换到A的时候,调用了A组件的create钩子
created(){
console.log("created");
},
// 当第一次点击切换到A的时候,调用了A组件的activeted钩子
// 点击切换到A的时候执行了activeted钩子函数
activated(){
console.log("activated");
},
// 点击切换到B的时候执行了deactivated钩子函数
deactivated(){
console.log("deactivated");
}
}
</script>
<!--创建一个B组件-->
<template>
<div>
<h1>我是B组件</h1>
</div>
</template>
<script>
export default {
name: "index"
}
</script>
<!--App.vue文件-->
<template>
<div id="app">
<router-link to="/a">切换到a</router-link>
<span>-----</span>
<router-link to="/b">切换到b</router-link>
<keep-alive>
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
# 如何强制刷新某些组件
- 在keep-alive激活会触发activated钩子函数 (页面被切换回来,展示在页面上的时候执行)
- 利用include、exclude属性(注意是组件的名字,不是路由的名字)
<!--include属性表示只有name属性为bookLists,bookLists的组件会被缓存-->
<keep-alive include="bookLists,bookLists">
<router-view></router-view>
</keep-alive>
<!--其它组件不会被缓存exclude属性表示除了name属性为indexLists的组件不会被缓存-->
<keep-alive exclude="indexLists">
<router-view></router-view>
</keep-alive>
- 利用meta属性
<script>
export default[
{
path:'/',
name:'home',
components:Home,
meta:{
keepAlive:true // 需要被缓存的组件
},
{
path:'/book',
name:'book',
components:Book,
meta:{
keepAlive:false // 不需要被缓存的组件
}
]
</script>
<template>
<keep-alive>
<!--这里是会被缓存的组件-->
<router-view v-if="this.$route.meat.keepAlive"></router-view>
</keep-alive>
<!--这里是不会被缓存的组件-->
<keep-alive v-if="!this.$router.meta.keepAlive"></keep-alive>
</template>
# 动态组件
我们之前在一个多标签的界面中使用 is attribute 来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重新渲染导致的性能问题
<div id="app">
<button @click="page=='index'"></button>
<button @click="page=='news'"></button>
<button @click="page=='login'"></button>
<!--可以动态调用不同的组件,keep-alive失活的组件将会被缓存-->
<keep-alive>
<component :is="page"></component>
</keep-alive>
</div>
<script>
Vue.component('index',{
template:'<h1>首页</h1>'
})
Vue.component('news',{
template:'<h1>新闻</h1>'
})
Vue.component('login',{
template:'<h1>登录</h1>'
})
const vm=new Vue({
el:'#app',
data:{
page:'index'
}
})
</script>
# 插槽使用
- Vue实现了一套内容分发的API,将slot元素作为承载分发内容的出口
- 让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式
# 插槽的类型
- 默认插槽、匿名插槽:只有一个,不带name的slot会有一个隐含的default
- 具名插槽:带有名字的插槽 v-slot:header,具名插槽的内容需要包含在template标签中
- 作用域插槽:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
<div id="app">
<modal>
<!--具名插槽-->
<tempalte v-slot:header>
<h3>温馨提示</h3>
</template>
<!--默认插槽、匿名插槽-->
<p>确定要删除吗?</p>
<!--作用域,是一个对象,其中包含插槽中所有的数据-->
<tempalte v-slot:btnSlot1="scope">
<button>{{scope.aa}}</button>
</template>
<tempalte v-slot:btnSlot2="scope">
<button>{{scope.ab}}</button>
</template>
</modal>
</div>
<script>
Vue.component('modal',{
template:`
<div class="modal">
<div class="top">
// 具名插槽
<slot name="header"></slot>
</div>
<div class="content">
// 默认插槽、匿名插槽
<slot></slot>
</div>
<div class="bom">
// 作用域插槽,value字符串可以随便写
<slot name="btnSlot1" :aa="btn1"></slot>
<slot name="btnSlot2" :ab="btn2"></slot>
</div>
</div>
`,
data(){
return {
btn1:'确定',
btn2:'取消'
}
}
})
</script>
# 自定义指令
<div id="app">
<!--必须加 v--->
<input type="text" v-focus>
</div>
<script>
// 全局指令(指令名、对象)
Vue.directive('focus',{
// 写指令的5个钩子函数
// 表示指令所在的元素已经插入到页面中
// el:指的就是当前的元素
inserted(el){
el.focus();
}
})
const vm=new Vue({
el:'#app',
data:{
msg:hello vue''
},
// 局部指令
directives:{
focus:{
inserted(el){
el.focus();
}
}
}
})
</script>
# 5个钩子函数
bind() # 只会执行一次,在指令绑定到元素上的时候执行,此时元素不一定在页面中显示
inserted() # 所在的元素插入到页面中(此时元素在页面中显示)
update() # 当指令的值发生改变的时候触发,例如:v-text="msg"
componentUpdated() # 当所有的DOM都更新完成的时候触发
unbind() # 当指令在DOM元素上移除的时候触发
# 钩子函数的参数
所有钩子函数的参数是一样的
- el:指令所在的DOM元素
- binding:是一个对象,所含以下属性
v-on:click.stop.prevent='clickFn' # 对应以下
v-指令名:指令的参数.指令的修饰符.指令的修饰符='指令的值'
<div id="app">
<!--指令的值必须在vue的data存在--->
<h1 v-demo:aa.bb.cc="msg">{{msg}}</h1>
</div>
<script>
Vue.directive('demo',{
bind(el,binding){
// 指令的名字->demo
binding.name
// 指令值->hello vue
binding.value
// 指令的参数 冒号后面的部分->aa
binding.arg
// 指令的修饰符,可以有多个->bb、cc
binding.modifiers
}
})
</script>
# 参数的使用
<div id="app">
<h1 v-mybind:title="msg">{{msg}}</h1>
<h1 v-color:bg.bold="color">{{color}}</h1>
<h1 v-myon:click.prevent="clickFn">{{color}}</h1>
</div>
<script>
Vue.directive('mybind',{
bind(el,binding){
el.setattribute(binding.arg,binding.vue)
},
// 修改msg的时候title的值会跟着变
update(el,binding){
el.setattribute(binding.arg,binding.vue)
}
})
Vue.directive('color',{
bind(el,binding){
if(binding.modifiers.bold){
el.style.fontWeight='bold'
}
},
update(el,binding){
if(binding.modifiers.bold){
el.style.fontWeight='bold'
}
}
})
Vue.directive('myon',{
bind(el,binding){
el.addEventListener(binding.arg,function(e){
binding.value();
if(binding.modifiers.prevent){
e.preventDefault();
}
if(binding.modifiers.stop){
e.stopPropagation();
}
});
},
update(el,binding){
el.addEventListener(binding.arg,function(e){
binding.value();
if(binding.modifiers.prevent){
e.preventDefault();
}
if(binding.modifiers.stop){
e.stopPropagation();
}
});
}
})
</script>
# 指令简写
如果自定义指令只需要提供bind和update,并且逻辑是一样的
Vue.directive('color',function(el,binding){
})
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue',
color:'red'
},
methods:{
color(el,binding){
}
}
})
# 路由使用
2022年2月7日以后,vue-router的默认版本为4版本
# 懒加载和魔法注释
魔法注释:对打包起作用
const router=new VueRouter({
routes:[{
path:'about',
name:'About',
component:()=>import(/* webpackChunkName:"about" */ '../views/About.vue')
}]
})
# 路由器工作模式
history模式
- 优点:URL更加美观,不带有#,更接近传统的网站URL
- 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误
server {
listen 80;
server_name localhost;
root /root/gshop;
location /api {
root /root/gshop;
index index.html;
// nginx添加配置
try_files $uri $uri/ /index.html;
}
}
hash 模式
- 优点:兼容性更好,因为不需要服务器端处理路径
- 缺点: URL 带有#不太美观,且在 SEO 优化方面相对较差
# 路由导航
router-link 最终会渲染成a标签
<router-link to="/index">首页</router-link>
const router=new VueRouter({
routes:[
// 路由的重定向
{path:'/',redirect:'/index'},
{path:'/index',component:index},
{path:'/user',component:user},
{path:'/login',component:login},
]
})
# 路由命名
{path:'/index',component:index,name:'index'}
// 使用
<router-link to="/index">首页</router-link>
<router-link :to="{path:'/index'}">首页</router-link>
<router-link :to="{name:'index'}">首页</router-link>
# 当前导航
- router-link-active:模糊匹配
- router-link-exact-active:精确匹配
// 让它精确匹配
<router-link to="/" exact></router-link>
- 修改方式一:直接该类的样式
.router-link-exact-active,
.router-link-active{
color:red;
font-size:24px;
}
- 修改方式二:修改默认类名
// 全局配置<router-link>默认激活的class类名
const router=new VueRouter({
routes:[],
linkActiveClass:'cur',
linkExactActiveClass:'cur'
})
# 路由嵌套
- 子路由需要配置children,子路由path不需要加'/'
- 子路由需要单独加显示的位置 router-view
const index={
template:`
<div>
这里是首页组件
// 跳转(要写完整路径)
<router-link to='/index/login'>登录</router-link>
<router-link to='/index/reg'>注册</router-link>
<hr>
<router-view></router-view>
//或者
<router-view/>
</div>
`
}
const router=new VueRouter({
routes:[
{path:'/index',component:index,
children:[
{path:'reg',component:reg},
{path:'log',component:log},
]}
]
})
# 编程式导航
除了使用router-link 创建声明式导航外,还可以使用router的实例方法,通过编写代码来实现路由的跳转
比如登录成功后跳转到用户中心
methods:{
login(){
// this.$router指整个的路由对象
this.$router.push('index'); // index
this.$router.push({path:'index'}); // index
this.$router.push({name:'index',params:{id:3}}); // index/3
this.$router.push({path:'index',query:{age:30}}); // index?age=30
this.$router.replace({path:'home'}); // 替换当前路由
this.$router.go(-1);
this.$router.forward();
this.$router.back();
}
}
# 动态路由匹配
- $router:整个项目的路由对象,一个项目只有一个
- $route:当前的路由规则 => 路径(当前地址栏中的路径)
const product={
template:`
<div>这是id为{{this.$route.params.id}}的商品</div>
`,
created(){
console.log(this.$router);
console.log(this.$route);
}
}
const router=new VueRouter({
routes:[
// :id 动态路由匹配
{ path:'/product/:id',component:product},
]
})
// $route详解
例:http://blog.1ge0.com/vue/02.html#/product/2?age=18&name=123
fullpath:#后面的内容 "/product/2?age=18&name=123"
params:匹配动态路由的数据 {id:"2"} -> this.$route.params.id -> 需要设置:id 动态路由匹配
query:?后面的内容 "age=18&name=123" -> this.$route.query.age
path:fullpath中除了query的部分"/product/2"
# 路由props
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
// 写法一:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
// 写法二:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
// 写法三:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return{
id:route.query.id,
title:route.query.title
}
}
}
# 多视图控制
<div class="app">
<h3>hello vue</h3>
<!--可以打开多个视图-->
<router-link to="/user/12">user</router-link>
<router-view></router-view> <!--默认视图-->
<router-view name="a"></router-view> <!--a视图-->
<router-view name="b"></router-view> <!--b视图-->
</div>
<script>
const user={props:['id'],template:'welcome to you'};
const tim={props:['id'],template:'<h4>tim</h4>'};
const tom={props:['id'],template:'<h4>tom</h4>'};
// 只替换默认视图的部分
const routes=[
{
path:'/user',
components:{default:user,a:tim,b:tom},
// 只有a和b视图能获取到id的值12
props:{default:false,a:true,b:true}
}
]
const router=new VueRouter({
routes
})
const vm=new Vue({
el:'#app'
})
</script>
# 路由守卫
路由的钩子函数,分为全局的、路由的、组件的
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/12">User-12</router-link>
<router-link to="/def/34">Def-34</router-link>
<router-link to="/def/56">Def-56</router-link>
</div>
<script>
const user={
props:['id'],
template:`<div>User:welcome!{{id}}</div>`
}
// 组件的守卫
const ref={
props:['id'],
template:`<div>Ref:welcome!{{id}}</div>`,
beforeRouteEnter(to,from.next){
// 在渲染该组件的对应路由前调用
// 不能使用this,因为当前组件实例还没创建
console.log('组件def的守卫before……')
next(vm=>{
// 通过vm访问组件实例
})
},
beforeRouteUpdate(to,from,next){
// 在当前路由改变,组件被复用时调用
// 例如:对于带有动态参数的路径/user/:id,在/user/1和/user/2之间跳转的时候
// 由于会渲染同样的组件,因此组件实例会被复用,此时就用调用这个钩子函数
// 可以使用this
console.log('组件def的守卫update……')
next(vm=>{
// 通过vm访问组件实例
})
},
beforeRouteLeave(to,from,next){
// 导航离开该组件的时候调用
// 可以使用this
console.log('组件def的守卫leave……')
next(vm=>{
// 通过vm访问组件实例
})
}
}
// 路由的守卫
// 与全局守卫相比路由守卫只是对当前路由进行单一控制,参数和全局前置守卫相同
const routes=[
{
path:'/user/:id',component:user,props:true,
beforeEnter:(to,from,next)=>{
console.log('路由守卫before……')
next(vm=>{
// 通过vm访问路由实例
})
}
},
{path:'/def:id',component:tom,props:true}
]
const router=new VueRouter({
routes
})
// 全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('全局守卫开始……')
next(vm=>{
// 通过vm访问路由实例
})
})
// 全局前置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from,next)=>{
console.log('全局守卫结束……')
if(to.meta.title){
document.title=to.meta.title //修改网页的title
} else {
document.title ='vue test'
}
next(vm=>{
// 通过vm访问路由实例
})
})
const vm=new Vue({
el:'#app',
router
})
</script>
# 数据获取
- 导航完成之后获取:跳转到页面后,在组件的生命周期created钩子函数中获取数据。在数据获取期间显示“加载中”之类指示
- 导航完成之前获取:页面跳转之前,在组件守卫beforeRouteEnter中获取数据,在数据获取成功后执行导航
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-view></router-view>
</div>
<script>
const user= {
template:'<div>user:welcome!</div>',
beforeRouteUpdate(to,from,next){
console.log('导航完成之前获取……')
}
}
const routes=[
{ path:'/user/:id',component:user }
]
const router=new VueRouter({
routes
})
const vm=new Vue({
el:'#app',
router,
created(){
console.log('导航完成之后获取……')
}
})
</script>
# 路由元信息
mate字段来定义路由的额外条件
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/12">User</router-link>
<router-link to="/login">Login</router-link>
<router-view></router-view>
</div>
<script>
const user= { template:'<div>user:welcome!</div>'}
const login= { template:'<div>name:<input/> pass:<input/> <button>登录</button></div>'}
const routes=[
{
path:'/user/:id',component:user,
meta:{ requiresAuth:true }// 元信息,需要登录校验
},
{
path:'login',component:login
}
]
const router=new VueRouter({
routes
})
// 全局守卫检查元信息
router.beforeEach((to,from,next)=>{
// 检查匹配的路径元信息,判断是否需要登录
if(to.matched.some(record=>record.meta.requiresAuth)){
let login=false;// 从后台查询的
if(!login) {
next({
path:'/login',
query:{ reditect:to.fullPath }
})
}else{
next()
}
}else{
next()
}
})
const vm=new Vue({
el:'#app',
router
})
</script>
# 路由动画
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在transition上
<router-view v-slot="{ Component, route }">
<!-- 使用任何自定义过渡和回退到 `fade` -->
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
<script>
const routes = [
{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},
{
path: '/other-transition',
component: PanelRight,
meta: { transition: 'slide-right' },
},
]
</script>
也可以根据目标路由和当前路由之间的关系,动态地确定使用的过渡。使用和刚才非常相似的片段
<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
我们可以添加一个 after navigation hook,根据路径的深度动态添加信息到 meta 字段
router.afterEach((to, from) => {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})
# 滚动行为
当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,需要我们在创建一个router实例的时候,可以提供一个scrollBehavior方法,该方法会在用户切换路由时触发
// history 模式
const router = new VueRouter({
routes: [...],
// 如果没有传入滚动条位置信息(savedPosition),就回到页面的顶部,即返回位置信息 {x:0,y:0}
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
// hash 模式,需要使用官方的导航守卫中的router.beforeEach
router.beforeEach((to, from, next) => {
window.scrollTo(0, 0)
next()
})
# 状态管理
vue2种使用vuex3,vue3种使用vuex4
# Module
store模块分割,每个模块拥有自己的State、Mutation、Action、Getter
const store=new Vuex.Store({
modules:{
user,
login
}
})
// user.js
export default {
namespaced: true, // 设置模块声明
state: {
userStatus: false,
userInfo: {},
userToken: ''
},
……
}
# State 公共数据源
提供唯一的公共数据源,所有共享的数据都要统一当道Store的State中存储
//创建数据源
const store=new Vuex.Store({
state:{
// 登录状态
loginStatus:false,
// token
userToken:'',
// 用户信息
userInfo:{}
}
})
// 第一种访问数据源方式
this.$store.state.userToken
this.store.state.userToken
<div>{{$store.state.userToken}}</div>
// 第二种访问数据源方式:辅助函数
// 只有mapState没加s
import { mapState } from 'vuex'
computed:{
...mapState(['loginStatus','userToken','userInfo'])
//或者
...mapState({
loginStatus:state=>state.user.userInfo,
userInfo:state=>state.login.userInfo,
userToken:state=>state.user.userToken
})
}
# Getter 加工处理Store中的数据
- Getter用于对Store中的数据进行加工处理形成新的数据,不会改变原数据,类似计算属性
- Store中的数据发生变化,Getter的数据也会发生变化
const store=new Vue.Store({
state:{
count:0
},
getters:{
showNum:state=>{
return '当前的数量是:'+ state.count +''
}
// 或者
showNum(state){
return '当前的数量是:'+ state.count +''
}
}
})
// 使用方式一
this.$store.getters.showNum
// 使用方式二
import {mapGetters} from 'vuex'
computed:{
…mapGetters(['showNum'])
// 或者
...mapGetters({
showNum1:state=>state.user.showNum1,
showNum2:state=>state.login.showNum2
})
}
# Mutation 变更State中的数据
不能在方法中执行异步的操作 setTimeOut(()=>{},1000)
const ADD_CART='addCart'
const store=new Vuex.Store({
mutations:{
login(state,userinfo){
state.userInfo =userinfo
state.loginStatus = true
state.userToken = userinfo.UserToken
// 持久化存储
localStorage.userInfo=JSON.stringify(userinfo)
},
// 常量事件类型
[ADD_CART](state){
// todo
}
}
})
// 触发方式一
this.$store.commit('login',userinfo)
this.store.commit('login',userinfo)
// 触发方式二
import { mapMutations } from 'vuex'
methods:{
// mutation依然没有命名空间的概念 所以在定义 mutations 时要注意全局的唯一性
...mapMutations(['login','loginOut'])
...mapMutations('user',['loginUser','loginOutUser'])
...mapMutations('shop',['loginShop','loginOutShop'])
}
// 可以直接调用此方法
<button @click="login">点击</button>
<button @click="loginOut">点击</button>
# Action 处理异步任务
在Action中还是要通过触发Mutation的方式间接变更数据
const store=new Vue.Store({
state:{
count:0
},
mutations:{
add1(state){
state.count++
},
add2(state,step){
state.count+=step
},
},
actions:{
addAsync1(context){
setTimeout(()=>{
// mutation中的方法add
context.commit('add1')
},1000)
},
addAsync2(context,step){
setTimeout(()=>{
// mutation中的方法add
context.commit('add2',step)
},1000)
}
}
})
// 触发方式一
this.$store.dispatch('addAsync1');
this.$store.dispatch('addAsync2',5);
this.store.dispatch('addAsync1');
this.store.dispatch('addAsync2',5);
// 触发方式二
import { mapActions } from 'vuex'
methods:{
...mapAction(['addAsync1','addAsync2'])
...mapAction('user',['loginUser','loginOutUser'])
...mapAction('shop',['loginShop','loginOutShop'])
}
// 可以直接调用此方法
<button @click="addAsync1">点击</button>
<button @click="addAsync2(5)">点击</button>
# Mixin混入
可以把多个组件公用的配置提取成一个混入对象
# 第一步 定义混入
创建minxins/index.js,写入
export const MixinsFn={
data(){ ………… }
created(){
console.log("这是minxins触发的created")
}
}
# 第二步 使用混入
// 全局混入 Vue.mixi(MixinsFn)
// 局部混入
import { MixinsFn } from '@/mixins/index.js'
export default {
mixins:[ MixinsFn ],
created(){
console.log("这是组件触发的created")
}
}