Vue 回顾重新学习

178 阅读6分钟

- 单向绑定

v-bind:
:

- 双向绑定

v-model
v-model.number  //收集到的为num格式
v-model.lazy    //失焦的时候获取数据
v-model.trim    //去空格

- 事件处理

v-on
@

- vue实例中el,data两种写法

new Vue({
    el:'#root',
    data:{
        name:'123'
    },
    或
    data(){                           //只要是Vue管理的函数,都不要写箭头函数,不然this指向不对
        return{
            name:'123'
        }
    },
    methods:{},
    computed:{
        fullName:{
            get(){
                return this.name+'123'
            }
        }
        //简写:当确定只读不改的时候
        fullName(){
            return this.name+'123'
        }
    }
})
const vm = new Vue({
    data:{}
})

v.$mount('#root')     //不写el,通过挂载

- Object.defineProperty()

let number= 18

let person = {
    name:'genji'
}

Object.defineProperty(person,'age',{
    //value:18,
    //enumerable:true,   //控制属性是否可以枚举,默认false
    //writable:true,   //控制属性是否可以被修改,默认值是false
    //configurable:true   //控制属性是否可以被删除,默认是false
    
    //当有人读取person的age属性时,get函数(getter)会被调用,返回值是age的值
    get(){
        console.log('读取age属性了')
        return number   //这样把age和number联动起来,当number变化,age也会变化
    },
    
    //当有人修改person的age属性时,set函数(setter)会被调用,且收到修改的具体值
    set(value){
        console.log(`有人修改了age属性,且值是${value}`)
        number = value    //修改了number
    }
})

- 数据代理

let obj1 = {x:100}
let obj2 = {y:200}

Object.defineProperty(obj2,'x',{
    get(){
        return obj1.x
    },
    set(value){
        obj1.x = value
    }
})

- 事件中用$event来占位

@click='clickBtn($event,123)'

- 事件修饰符

@click.prevent='clickBtn'   //阻止默认行为
@click.stop='clickBtn'   //阻止冒泡
@click.once    //事件只触发一次
@click.capture    //使用时间的捕获阶段
@click.self   //只有event.target是当前操作的元素时才会触发事件
@click.passive   //事件的默认行为立即执行,无需等待事件回调执行完毕

- 键盘事件

@keyup.enter   //按回车触发
@keyup.delete   //删除触发
@keyup.esc
@keyup.space
@keyup.tab
@keyup.up
@keyup.down
@keyup.left
@keyup.right

- 监听

new Vue({
    el:'#root',
    data:{},
    watch:{
        a:{    //需要被监听的属性
            immediate:true,   //初始化时让handler调用一下
            deep:true   //深度监听
            handler(newVal,oldVal){
                ...
            }
        }
    }
    //简写,当不需要立刻监听和深度监听时
    watch:{
        a(newVal,oldVal){
            ...
        }
    }
})

在外部调用监听

const vm = new Vue({
    el:'#root',
    data:{},
})

vm.$watch('a',{
    ...
}

- computed和watch的区别

  1. computed能完成的,watch都可以完成
  2. watch能完成的,computed不一定能完成,例如异步操作

- 绑定class样式

//字符串写法,适用于:样式的类名不确定,需要动态指定
<div :class='mood' @click='changeMood' ></div>

new Vue({
    ...
    data:{
        mood:'a'
    },
    methods:{
        changeMood(){
            this.mood = 'b'
        }
    }
})

//数组写法,适用于:要绑定的样式个数不确定,名字也不确定
<div :class='classArr' ></div>

new Vue({
    ...
    data:{
        classArr:['a','b','c']
    },
})

//对象写法,适用于:要绑定的样式个数确定,名字确定,但要动态决定用不用
<div :class='classObj' ></div>

new Vue({
    ...
    data:{
        classObj:{
            a:false,   //false时不生效
            b:true   //true时该class生效
        }
    },
})

- 绑定style样式

<div :style='{fontSize: fsize+'px'}' ></div>
<div :style='styleObj' ></div>

new Vue({
    ...
    data:{
       fsize:40,
       styleObj:{
           fontSize:'40px'
       }
        }
    },
})

- 条件渲染

v-if   //组件会销毁
v-else
v-else-if    //上面三个中间不允许被打断
v-show   //组件不会销毁

<template v-if='n===1'>    //template不会影响结构,但是只能和v-if使用,不能和v-show使用
    <h2>1</h2>
    <h2>2</h2>
    <h2>3</h2>
</template>

- 遍历

//遍历数组
<li v-for='(p,index) in presons' :key='p.id' >
    {{p.name}}
</li>

//遍历对象
<li v-for='(value,key) in car' :key='key' >   //参数为键值对
    {{key}}:{{value}}
</li>

//遍历字符串
<li v-for='(char,index) in str' :key='index' >
    {{char}}-{{index}}
</li>

new Vue({
    el:'#root',
    data:{
        persons:[{
            name:'genji'
        },
        {
            name:'Mcree'
        }
        ],
        car:{
        name'audi',
        price: 70
        },
        str:'12423432'
        },
    
})

- Vue监视数据原理

Vue会监视data中所有层次的数据
如何监测对象中的数据?

通过setter实现监视,且要在new Vue时就传入要监测的数据

  1. 对象中后追加的属性,Vue默认不做响应式处理
  2. 如需给后添加的属性做响应式,要用如下API
    Vue.set(target,propertyName/index,value)或者
    vm.$set(target,propertyName/index,value)
如何监测数组中的数据?

通过包裹数组更新元素的方法实现,本质就是做了两件事:

  1. 调用原生对应的方法对数组进行更新
  2. 重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
使用这些API:
1push(), pop(),shift(),unshift(),splice(),sort(),reverse()
2、Vue.set() 或 vm.$set()

注意:Vue.set()和vm.$set()不能给vm或vm的根数据对象(_data)添加属性!!!

- 数据劫持

其实就是

  1. 改变数据
  2. 重新解析模板,进而更新页面

- 过滤器

注册过滤器

Vue.filter(name,callback) 或者
new Vue({
    filter:{}
})

使用过滤器 {{xxx | 过滤器名}} 或 v-bind:属性 = 'xxx | 过滤器名'

过滤器可以接收额外参数,多个过滤器也可以串联

v-cloak指令(没有值)

本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题

<style>
    [v-cloak]{
        display:none
    }
</style>


<div v-cloak >{{name}}</div>

v-once指令(没有值)

  1. v-once所在节点在初次动态渲染后,就视为静态内容了
  2. 以后的数据改变,不会引起v-once所在节点的数据更新

v-pre指令(没有值)

  1. 跳过其所在节点的编译过程
  2. 可利用它跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译

自定义指令

  • 局部指令
//配置对象的属性和回调函数中的参数为 element,binding
new Vue({
    directives:{指令名,配置对象}
})

new Vue({
    directives{指令名:回调函数)
})
  • 全局指令
Vue.directive(指令名,配置对象)

Vue.directive(指令名,回调函数)
  • 配置对象中常用的3个回调
  1. bind 指令与元素成功绑定调用
  2. inserted 指令所在元素被插入页面时调用
  3. updated 指令所在模板结构被重新解析时调用

指令定义时不加v-,使用时加v- 指令名称使用kebab-case命名方式,不要用小驼峰

生命周期

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

组件

//全局注册组件:
Vue.component('header',header)

//局部组件:
options不写el, 并且data得为函数
Vue.extend(options)

Vue文件

<template>
...
</template>

<script>
    //简略写法
    export default{
    ...
    }
    //详细写法
    const name = Vue.extend({
        ....
    })
    export default name
</script>

<style>
...
</style>

Ref属性

<template>
    <div>
    <h1 ref='title'></h1>
    <button @click='showDom'></button>
    </div>
</template>

<script>
    export default{
        name:'',
        methods:{
            showDom(){
                console.log(this.$refs.title)
            }
        }
    }
</script>

props配置

  • props只读,不接受修改 子组件:
<template>
    <div>
        名字:{{name}}  年龄:{{age+1}}
    </div>
</template>

<script>
    export defualt{
        props:['name','age']   //简单声明接收
        props:{                //接受的同时对数据进行类型限制
            name:String,
            age:Number
        }
        props:{               //最完整,包括必须和默认值
            name:{
                type:String,
                required:true
            },
            age:{
                type:Number,
                default:18
            }
        }
    }
</script>

父组件

<template>
    <div>
        <child name='genji' :age='18' />    //:age动态绑定,子组件接收时18不为字符串,可以用作{{age+1}}
    </div>
</template>

mixin

在mixin.js文件中暴露方法,然后在组件中引用

  • 局部混合
<script>
    import {mixin} from '..'
    
    export default{
        mixins:[mixin]
    }
</script>
  • 全局混合 在main.js中引入,混合,之后在vm和每个vuecomponent中都有该混合中的数据,方法等
//main.js
import {mixin} form '...'

Vue.mixin(mixin)

插件

  • 用于增强Vue,包含install方法的一个对象,install的第一个参数是Vue构造函数,第二个以后的参数是插件使用者传递的数据
//plugins.js
export default{
    intall(Vue){
        //全局过滤器
        Vue.filter(){}
        //全局指令
        Vue.directive()
        //全局混入
        Vue.mixin()
        //添加实例方法
        Vue.prototype.$myMethod = function(){}
    }
}

//使用插件:在main.js中导入,
Vue.use(...)

scoped

在样式style中加scoped

<style scoped lang='less'>     // 需要安装less-loader
...
</style>
  • 如果webpack版本是4,less-loader需要安装7版本
npm i less0loader@7

父子组件事件

  • 父组件
<template>
    <div>
        //利用props传递
        <Child :customeEvent='customeEvent' />
        //利用绑定自定义事件
        <Child @customeEvent='customeEvent' />
        //利用ref定位元素,$on绑定自定义事件
        <Child ref='child' />
    </div>
 </template>   
 <script>
        export default{
            methods:{
                customeEvent(){...}
            },
            mounted:{
                this.$refs.customeEvent.$on('自定义事件名称',this.cusromeEvent)
            }
        }
 </script>

  • 子组件
<template>
    <div>
        <button @click='clickbtn'></button>
    </div>
 </template> 

<script>
    export default{
        //利用props传递
        props:['customeEvent']
        //利用绑定自定义事件
        methods:{
            clickbtn(){
                this.$emit('customeEvent',参数...)
            }
            //解绑自定义事件
            unbind(){
                this.$off('customeEvent')   //解绑单个
                this.$off([...])   //解绑多个
                this.$off()    //解绑全部
            }
        }
    }
</script>
  • 组件添加原生DOM事件
//父组件
<Child @click.native='clickbtn' />
  • 自定义事件只触发一次 使用once修饰符或者$once方法

全局事件总线

  1. 安装全局事件总线
//main.js

nwe Vue({
    ...
    beforeCreate(){
        Vue.prototype.$bus = this  
    }
})
  1. 使用事件总线
//1、A组件 接收数据
...
methods(){
    demo(){...}
},
...
mounted(){
    this.$bus.$on('xxx',this.demo)
},
beforeDestroy(){                       //事件总线最好在组件销毁时解绑
    this.$bus.$off('xxx')
}


//2、B组件 提供数据
...
methods(){
    ...(){
        this.$bus.$emit('xxx',参数)
    }
}
...

消息订阅与发布

  • 可利用pubsub-js这个库,这个库适用于各个框架,例如React
npm install pubsub-js --save

import pubsub from 'pubsub-js'
//A组件发布订阅,接收数据
<script>
    export default{
        ...
        mounted(){
            //subscribe函数第一个参数是订阅的消息名,第二个是回调函数(第一个参数是消息名,即hello,第二个是接收的数据)
            this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
                console.log('接收到了消息,消息是',data)
            })
        },
        beforeDestroy(){
            pubsub.unsubscribe(this.pubId)
        }
    }
</script>

//B组件传递数据
...
    methods:{
        sendData(){
            pubsub.publish('hello',12345)
        }
    }
...

nextTick

  • 在下一次模板更新后,执行nextTick中的回调函数
...
this.nextTick(function(){
    this...
})

过渡和动画

<template>
    <div>
        //Vue中,把需要动画的标签用transition包起来,如果没有name,会自动追加v-enter/leave-active,
        <transition name='hello' appear>    //有name,会追加name-enter/leave-active, appear为真时,初次渲染也会有动画
            <h1 v-show='...' >hello world</h1>
        </transition>
    </div>
</template>

//动画实现
<style>
    .hello-enter-active{
        animation:donghua 1s linear;
    }
    .hello-leave-active{
        animation:donghua 1s linear reverse;
    }
    
    @keyframes donghua {
        from{
            transform:translateX(-100%)
        }
        to{
            transform:translateX(0px)
        }
    }
</style>

//过渡实现
<style>
    //进入的起点,离开的终点
    .hello-enter,.hello-leave-to{
        transform:translateX(-100%)
    }
    .hello-enter-active,.hello-leave-active{
        transition:0.5s linear       //也可写在h1,即需要添加动画的元素style上
    }
    //进入的终点,离开的起点
    .hello-enter-to,.hello-leave{
        transform:translateX(0)
    }
</style>
  • 如果是多个组件使用共同的动画,用transtion-group包裹,并给每个标签唯一的key值
  • 推荐使用第三方库,举例:animate.css,不需要写style样式
<transition 
appear
name='animate_animated animate_bounce'
enter-active-class='...'    //官方文档直接复制
leave-active-class='...'
>

</transtion>
...
import 'animate.css'

代理

  • 在vue.config.js中添加代理服务器
module.exports={
    ...
    //方式一
    devServer:{
        proxy:'后端服务器地址'
    }
    //方式二:
    devServer:{
        proxy:{
            '/api':{
                target:'后端地址',
                changeOrigin:true,       //用于控制请求头中的host值
                ws:true,                 //用于支持websocket
                pathRewrite:{
                    '^/api':''           //改变请求的地址,去掉/api
                }
            }
        }
    }
}

插槽

  • 默认插槽
//子组件
...
<div>
    <slot>默认内容,当没有传递结构时展示</slot>
</div>


//父组件
...
<Child> 传入插槽的结构 </Child>
  • 具名插槽
//子组件
...
<div>
    <slot name='content'>默认内容,当没有传递结构时展示</slot>
</div>


//父组件
...
<Child> 
    <div slot='content' >...</div> 或者
    <template v-slot:footer></template>   v-slot仅在template上可用,后面用:且不加''
</Child>
  • 作用域插槽 数据在子组件中,父组件要使用子组件中数据。作用域插槽也可使用name
//子组件
...
<div>
    <slot x:'hello'>默认内容,当没有传递结构时展示</slot>
</div>


//父组件
...
<Child> 
    <template scope='data'或者scope='{x}'或者slot-scope='{x}'>    //必须用template标签
            <h1>{{data.x}}或者{{x}}</h1>
    </template>
</Child>

VueX

Vue2中,要用Vuex的3版本;Vue3中,要用Vuex的4版本

  • 下载
npm install vuex@3 --save
  • 引入
//main.js
import store from './store'

new Vue({
    ...
    store
    ...
})
  • 创建Vuex,创建store文件夹,创建index.js,使用Vuex
// /src/store/index.js

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

//用于响应组件中的动作
const actions = {}
//用于操作数据(state)
const mutations = {}
//用于存储数据
const state = {}
//用于将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.xxx*10
    }
}

export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

Vuex使用

//xxx.vue
...
<div>总数:{{$store.state.sum}}</div>
<button @click='increase'>加一</button>

...
increase(){
    this.$store.dispatch('increase',1)
}
...

// store/index.js
const actions = {
    increase(context,value){
        context.commit('INCREASE',value)
        //可以继续dispatch别的actions,
        //context.dispatch('..',value)
    }
}
const mutations = {
    INCREASE(state,value){
        state.sum += value
    }
}
const state = {
    sum:0
}

export default new Vuex.Store({
    actions,
    mutations,
    state
})

mapState,mapGetters使用

  • 从state或getters中读取数据,不必一个个写 this.$store.state.xx
// xxx.vue
...
import {mapState,mapGetters} from 'vuex'
...
computed:{
    //对象写法,
    ...mapState({sum:'sum',school:'school'})
    //数组写法
    ...mapState(['sum','school'])
}

mapMutations,mapActions

// xxx.vue

<button @click='increment'></button>
//使用mapMutations时,需要传参
<button @click'increment(n)'></button>
...
import {mapState,mapGetters} from 'vuex'
...
methods:{
    //increment(){
    //    this.$store.commit('JIA',this.n)
    //}
    
    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations,需要在函数事件中进行传参
    ...mapMutations({increment:'JIA'})
    //数组方法
    ...mapMutations(['JIA'])
    
    //decrement(){
    //    this.$store.dispatch('jian',this.n)
    //}
    ...mapActions({decrement:'jian'})
    ...mapActions(['jian'])
}

vuex模块化

// store/index.js
...
const a = {
    namespaced:true,  //命名空间,为true后分类名才能被mapState等识别
    actions:{...},
    mutations:{...},
    state:{...},
    getters:{...},
}

const b = {
    namespaced:true,
    actions:{...},
    mutations:{...},
    state:{...},
    getters:{...},
}

export default new Vuex.Store({
    modules:{
        a,
        b
    }
})

// xxx.vue
...
import {mapState,mapGetters} from 'vuex'
...
computed:{
    //获取b命名空间中的state中的sum和school
    ...mapState('b',['sum','school'])
},
methods:{
    add(){
        //a命名空间下的mutations中的ADD
        this.$store.commit('a/ADD',values)
    }
}

...
  • 如果用mapState等写法,都是第一个参数为命名空间,第二个参数为数组或对象
  • 如果用$store的方法读取或者调用
state:$store.state.命名空间.xxx
commit,dispatch,getters:
$store.getters['a/...']
$store.commit('a/...',values)

vue-router

3版本只能在Vue2中使用,4版本只能在Vue3中使用

npm i vue-router@3 --save
// main.js
import VueRouter from 'vue-router'
import router from './router'

Vue.use(VueRouter)

new Vue({
    ...
    router
})
// router/index.js
import VueRouter from 'vue-router'

export default  new VueRouter({
    routes:[
        {
            path:'/a',
            component:'../.....'
        }
    ]
})
// xxx.vue
...
//默认push模式,添加 :replace='true'或replace,切换为replace模式,替换当前目录
<router-link active-class='active' to='/a' >a</router-link>

//需要展示路由页面的地方
<router-view></router-view>
  • 整个应用只有一个router,可以通过$router获取到

嵌套路由

routes:[
    {
        path:'/a',
        component:'',
        children:[
            {
                path:'b',         //不要加 / 
                component:''
            }
        ]
    }
]

路由传参

  • query传参
//query传参,字符串写法
:to='`/.../..?key=${value}`'

//获取query传参
this.$route.query

// query传参,对象写法
:to='{
    path:'/../..',
    query:{
        id:..,
        name:..
    }
}'
  • 命名路由,可以简化路由的跳转,在对象写法中使用
{
    name:'',
    path:'/a',
    component:'../.....'
}
:to='{
    name:'...',
    query:{
        id:..,
        name:..
    }
}'
  • param传参
//  router/index.js
...
{
    name:'',
    path:'/.../../:id/:name',   //占位
    component:''
}

// xxx.vue
:to='/../../6/genji'

:to='{
    name:'',          //params传参,用对象写法,此处不能用path,只能用name
    params:{
        ...
    }
}'

路由的props配置

// router/index.js
...
{
    name:'',
    path:'../..',
    component:'',
    //第一种写法,值为对象,该对象中的所有k-v都会以props的形式传给该组件
    //props:{a:1,b:2}
    
    //第二种写法,值为布尔值,为真,就会吧该路由组件收到的所有params参数,以props的形式传给该组件
    //props:true
    
    //第三种写法,值为函数,以props的形式传给该组件
    props($route){
        return {a:$route.query.a,b:$route.query.b}
    }
    //解构,简化
    props({query}){
        return {a:query.a,b:query.b}
    }
}
// xx.vue

...
props:['a','b']

编程式导航

不借助router-link进行路由跳转

this.$router.push(...)

缓存路由组件

让不展示的路由组件保持挂载,不被销毁

<keep-alive include='组件名'>
    //需要缓存的路由组件
    <router-view></router-view>
</keep-alive>

如果缓存多个组件,写作 :include='['组件名','组件名']'

新的生命周期钩子 activated激活,deactivated失活

路由组件所独有的钩子,用于捕获路由组件的激活状态

  1. activated 路由组件被激活时触发(被展示的时候,不是挂载的时候,所以缓存的路由组件特殊需求用这个)
  2. deactivated路由组件失活时触发(不展示的时候,不是销毁的时候)

路由守卫

  • 全局前置路由守卫
// router/index.js
...
const router = new VueRouter({
    ...
})

//全局前置路由守卫:初始化的时候被调用,每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
    if(...){
        next()
    }
})

export default router
  • 可以在meta(路由元信息)中配置属性,来统一进行鉴权,不需要一个个判断
const router = new VueRouter({
   routes:[{
       name:'..',
       path:'..',
       component:'..',
       meta:{isAuth:true}
   }]
})

router.beforeEach((to,from,next)=>{
    if(to.meta.isAuth){
        next()
    }
})
  • 全局后置路由守卫
router.afterEach((to,from)=>{})
  • 独享路由守卫
const router = new VueRouter({
   routes:[{
       name:'..',
       path:'..',
       component:'..',
       meta:{isAuth:true},
       beforeEnter:(to,from,next)=>{
           ...
       }
   }]
})
  • 组件内路由组件
// xxx.vue

...
export default{
    ...
    //通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from,next){
        ...
    },
    //通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){
        ...
    }
    
}