vue

168 阅读6分钟

基本使用

指令、插值

<template>
    <div>
        <p>文本插值 {{message}}</p>
        <p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>

        <p :id="dynamicId">动态属性 id</p>

        <hr/>
        <p v-html="rawHtml">
            <span>有 xss 风险</span>
            <span>【注意】使用 v-html 之后,将会覆盖子元素</span>
        </p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: 'hello vue',
            flag: true,
            rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>',
            dynamicId: `id-${Date.now()}`
        }
    }
}
</script>

computed和wacth

  • computed
<template>
    <div>
        <p>num {{num}}</p>
        <p>double1 {{double1}}</p>
        <input v-model="double2"/>
    </div>
</template>

<script>
export default {
    data() {
        return {
            num: 20
        }
    },
    computed: {
        double1() {
            return this.num * 2
        },
        //v-model必须有get、set两个
        double2: {
            get() {
                return this.num * 2
            },
            set(val) {
                this.num = val/2
            }
        }
    }
}
</script>
  • watch
<template>
    <div>
        <input v-model="name"/>
        <input v-model="info.city"/>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: '双越',
            info: {
                city: '北京'
            }
        }
    },
    watch: {
        name(oldVal, val) {
            // eslint-disable-next-line
            console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
        },
        info: {
            handler(oldVal, val) {
                // eslint-disable-next-line
                console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
            },
            deep: true // 深度监听
        }
    }
}
</script>

class和style

<template>
    <div>
        <p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
        <p :class="[black, yellow]">使用 class (数组)</p>
        <p :style="styleData">使用 style</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            isBlack: true,
            isYellow: true,

            black: 'black',
            yellow: 'yellow',

            styleData: {
                fontSize: '40px', // 转换为驼峰式
                color: 'red',
                backgroundColor: '#ccc' // 转换为驼峰式
            }
        }
    }
}
</script>

<style scoped>
    .black {
        background-color: #999;
    }
    .yellow {
        color: yellow;
    }
</style>

条件渲染v-if、v-show

<template>
    <div>
        <p v-if="type === 'a'">A</p>
        <p v-else-if="type === 'b'">B</p>
        <p v-else>other</p>

        <p v-show="type === 'a'">A by v-show</p>
        <p v-show="type === 'b'">B by v-show</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            type: 'a'
        }
    }
}
</script>

循环列表渲染

v-if和v-for不能一起使用,v-for优先级高,先循环,再判断,如果false,浪费性能
<template>
    <div>
        <p>遍历数组</p>
        <ul>
            <li v-for="(item, index) in listArr" :key="item.id">
                {{index}} - {{item.id}} - {{item.title}}
            </li>
        </ul>

        <p>遍历对象</p>
        <ul >
            <li v-for="(val, key, index) in listObj" :key="key">
                {{index}} - {{key}} -  {{val.title}}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            flag: false,
            listArr: [
                { id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key
                { id: 'b', title: '标题2' },
                { id: 'c', title: '标题3' }
            ],
            listObj: {
                a: { title: '标题1' },
                b: { title: '标题2' },
                c: { title: '标题3' },
            }
        }
    }
}
</script>

事件

<template>
    <div>
        <p>{{num}}</p>
        <button @click="increment1">+1</button>
        <button @click="increment2(2, $event)">+2</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            num: 0
        }
    },
    methods: {
        increment1(event) {
            // eslint-disable-next-line
            console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
            // eslint-disable-next-line
            console.log(event.target)
            // eslint-disable-next-line
            console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
            this.num++

            // 1. event 是原生的
            // 2. 事件被挂载到当前元素
            // 和 DOM 事件一样
        },
        increment2(val, event) {
            // eslint-disable-next-line
            console.log(event.target)
            this.num = this.num + val
        },
        loadHandler() {
            // do some thing
        }
    },
    mounted() {
        window.addEventListener('load', this.loadHandler)
    },
    beforeDestroy() {
        //【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
        // 自己绑定的事件,需要自己销毁!!!
        window.removeEventListener('load', this.loadHandler)
    }
}
</script>
事件修饰符

//阻止单击事件继续传播
<a v-on:click.stop="doThis"></a>

//提交事件不再重载页面
<form v-on:submit.prevent="onSubmit"></form>

//修饰符可以串联
<a v-on:click.stop.prevent="doThat"></a>

//只有修饰符
<form v-on:submit.prevent></form>

//添加事件监听器时使用事件捕获模式
//即 内部元素触发的事件先在此处理,然后才交由内部元素进行处理
<div v-on:click.capture="doThis"></div>

//只有当 event.target 是当前元素自身时触发处理函数
//即 事件不是从内部函数触发的
<div v-on:click.self="doThat"></div>
按键修饰符

//即使 Alt 或 Shift 被一同按下时也会触发
<button @click.ctrl="onClick">A</button>

//有且只有 Ctrl 被按下的时候才出发
<button @click.ctrl.exact="onCtrlClick">A</button>

//没有任何系统修饰符被按下的时候才触发
<button @click.exact="onClick"></button>

表单

<template>
    <div>
        <p>输入框: {{name}}</p>
        <input type="text" v-model.trim="name"/>
        <input type="text" v-model.lazy="name"/>
        <input type="text" v-model.number="age"/>

        <p>多行文本: {{desc}}</p>
        <textarea v-model="desc"></textarea>
        <!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->

        <p>复选框 {{checked}}</p>
        <input type="checkbox" v-model="checked"/>

        <p>多个复选框 {{checkedNames}}</p>
        <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
        <label for="jack">Jack</label>
        <input type="checkbox" id="john" value="John" v-model="checkedNames">
        <label for="john">John</label>
        <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
        <label for="mike">Mike</label>

        <p>单选 {{gender}}</p>
        <input type="radio" id="male" value="male" v-model="gender"/>
        <label for="male">男</label>
        <input type="radio" id="female" value="female" v-model="gender"/>
        <label for="female">女</label>

        <p>下拉列表选择 {{selected}}</p>
        <select v-model="selected">
            <option disabled value="">请选择</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>

        <p>下拉列表选择(多选) {{selectedList}}</p>
        <select v-model="selectedList" multiple>
            <option disabled value="">请选择</option>
            <option>A</option>
            <option>B</option>
            <option>C</option>
        </select>
    </div>
</template>

<script>
export default {
    data() {
        return {
            name: '双越',
            age: 18,
            desc: '自我介绍',

            checked: true,
            checkedNames: [],

            gender: 'male',

            selected: '',
            selectedList: []
        }
    }
}
</script>

Vue组件通讯

index.vue

<template>
    <div>
        <Input @add="addHandler"/>
        <List :list="list" @delete="deleteHandler"/>
    </div>
</template>

<script>
import Input from './Input'
import List from './List'

export default {
    components: {
        Input,
        List
    },
    data() {
        return {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
        addHandler(title) {
            this.list.push({
                id: `id-${Date.now()}`,
                title
            })
        },
        deleteHandler(id) {
            this.list = this.list.filter(item => item.id !== id)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('index created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('index mounted')
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('index before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('index updated')
    },
}
</script>

Input.vue

<template>
    <div>
        <input type="text" v-model="title"/>
        <button @click="addTitle">add</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: ''
        }
    },
    methods: {
        addTitle() {
            // 调用父组件的事件
            this.$emit('add', this.title)

            // 调用自定义事件
            event.$emit('onAddTitle', this.title)

            this.title = ''
        }
    }
}
</script>

List.vue

<template>
    <div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {{item.title}}

                <button @click="deleteItem(item.id)">删除</button>
            </li>
        </ul>
    </div>
</template>

<script>
import event from './event'

export default {
    // props: ['list']
    props: {
        // prop 类型和默认值
        list: {
            type: Array,
            default() {
                return []
            }
        }
    },
    data() {
        return {

        }
    },
    methods: {
        deleteItem(id) {
            this.$emit('delete', id)
        },
        addTitleHandler(title) {
            // eslint-disable-next-line
            console.log('on add title', title)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('list created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('list mounted')

        // 绑定自定义事件
        event.$on('onAddTitle', this.addTitleHandler)
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('list before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('list updated')
    },
    beforeDestroy() {
        // 及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
</script>

event.js//非父子组件通讯-自定义事件

import Vue from 'vue'

export default new Vue()

组件生命周期

index created
list created
list mounted
index mounted
index beforeUpdate
list beforeUpdate
list updated
index updated

Vue高级特性

自定义v-model

  • index.vue
<template>
    <div>
    
        <!-- 自定义 v-model -->
        <p>{{name}}</p>
        <CustomVModel v-model="name"/>

    </div>
</template>

<script>
import CustomVModel from './CustomVModel'

export default {
    components: {
        CustomVModel
    },
    data() {
        return {
            name: '双越'
        }
    }
}
</script>
  • CustomVModel.vue
<template>
    <!-- 例如:vue 颜色选择 -->
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change1 和 model.event1 要对应起来
        3. text1 属性对应起来
    -->
</template>

<script>
export default {
    model: {
        prop: 'text1', // 对应 props text1
        event: 'change1'
    },
    props: {
        text1: String,
        default() {
            return ''
        }
    }
}
</script>

$nextTick

<template>
  <div id="app">
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {{item}}
        </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
      return {
        list: ['a', 'b', 'c']
      }
  },
  methods: {
    addItem() {
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
        // 2. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
        this.$nextTick(() => {
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          // eslint-disable-next-line
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
</script>

slot

  • index.vue
<template>
    <div>

        <!-- slot -->
        <SlotDemo :url="website.url">
            {{website.title}}
        </SlotDemo>
        
        //作用域插槽:让插槽内容能够访问子组件中才有的数据
        <ScopedSlotDemo :url="website.url">
            <template v-slot="slotProps">
                {{slotProps.slotData.title}}
            </template>
        </ScopedSlotDemo>
    </div>
</template>

<script>
import SlotDemo from './SlotDemo'
import ScopedSlotDemo from './ScopedSlotDemo'

export default {
    components: {
        SlotDemo,
        ScopedSlotDemo,
    },
    data() {
        return {
            website: {
                url: 'http://imooc.com/',
                title: 'imooc',
                subTitle: '程序员的梦工厂'
            }
        }
    }
}
</script>
  • SlotDemo
<template>
    <a :href="url">
        <slot>
            默认内容,即父组件没设置内容时,这里显示
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {}
    }
}
</script>
  • ScopedSlotDemo
<template>
    <a :href="url">
        <slot :slotData="website">
            {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {
            website: {
                url: 'http://wangEditor.com/',
                title: 'wangEditor',
                subTitle: '轻量级富文本编辑器'
            }
        }
    }
}
</script>
  • 具名插槽
//NamedSlot 组件
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<NamedSlot>
  <template v-slot:header>
    <h1>将插入 header slot 中</h1>
  </template>

  <p>将插入 main slot 中,即未命名的slot</p>

  <template v-slot:footer>
    <p将插入 footer slot 中</p>
  </template>
</NamedSlot>

动态组件

<template>
    <div>

        <!-- 动态组件 -->
        <component :is="NextTickName"/>
        
    </div>
</template>

<script>
import NextTick from './NextTick'

export default {
    components: {
        NextTick
    },
    data() {
        return {
            NextTickName: "NextTick"
        }
    }
}
</script>

异步组件

  • index.vue
<template>
    <div>

        <!-- 异步组件 -->
        <FormDemo v-if="showFormDemo"/>
        <button @click="showFormDemo = true">show form demo</button>

    </div>
</template>

<script>

export default {
    components: {
        FormDemo: () => import('../BaseUse/FormDemo')
    },
    data() {
        return {
            showFormDemo: false
        }
    }
}
</script>

缓存组件 keep-alive

  • index.vue
<template>
    <div>

        <!-- keep-alive -->
        <KeepAlive/>

    </div>
</template>

<script>
import KeepAlive from './KeepAlive'

export default {
    components: {
        KeepAlive
    },
    data() {
        return {
        }
    }
}
</script>
  • KeepAlive.vue
<template>
    <div>
        <button @click="changeState('A')">A</button>
        <button @click="changeState('B')">B</button>
        <button @click="changeState('C')">C</button>
        
        <!-- tab 切换 -->
        //用keep-alive包起来
        <keep-alive> 
            <KeepAliveStageA v-if="state === 'A'"/>
            <KeepAliveStageB v-if="state === 'B'"/>
            <KeepAliveStageC v-if="state === 'C'"/>
        </keep-alive>
    </div>
</template>

<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'

export default {
    components: {
        KeepAliveStageA,
        KeepAliveStageB,
        KeepAliveStageC
    },
    data() {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state) {
            this.state = state
        }
    }
}
</script>
  • KeepAliveStateA.vue
<template>
    <p>state A</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('A mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('A destroyed')
    }
}
</script>
  • KeepAliveStateB.vue
<template>
    <p>state B</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('B mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('B destroyed')
    }
}
</script>
  • KeepAliveStateC.vue
<template>
    <p>state C</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('C mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('C destroyed')
    }
}
</script>

mixin 抽离多个组件的公共逻辑

  • index.vue
<template>
    <div>

        <!-- mixin -->
        <MixinDemo/>
        
    </div>
</template>

<script>
import MixinDemo from './MixinDemo'

export default {
    components: {
        MixinDemo
    },
    data() {
        return {
        }
    }
}
</script>
  • MixinDemo.vue
<template>
    <div>
        <p>{{name}} {{major}} {{city}}</p>
        <button @click="showName">显示姓名</button>
    </div>
</template>

<script>
//引入mixin.js
import myMixin from './mixin'

export default {
    mixins: [myMixin], // 可以添加多个,会自动合并起来
    data() {
        return {
            name: '双越',
            major: 'web 前端'
        }
    },
    methods: {
    },
    mounted() {
        // eslint-disable-next-line
        console.log('component mounted', this.name)
    }
}
</script>
  • mixin.js
export default {
    data() {
        return {
            city: '北京'
        }
    },
    methods: {
        showName() {
            // eslint-disable-next-line
            console.log(this.name)
        }
    },
    mounted() {
        // eslint-disable-next-line
        console.log('mixin mounted', this.name)
    }
}

  • 存在的问题
    • 变量来源不明确,不利于阅读
    • 多mixin可能会造成命名冲突
    • mixin和组件可能出现多对多的关系,复杂度较高

vuex使用

vue-router

基础

  • app.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <!-- 跳转命名路由 -->
      <router-link :to="{name:'About'}">About</router-link>
    </div>
    <!-- 在同一个页面,显示多个视图,命名视图 -->
    <router-view/>
    <router-view name="email"/>
    <router-view name="tel"/>
  </div>
</template>

<style lang="less">

</style>

  • Home.vue
<template>
  <div class="home">
    编程导航
    <button @click="handleClick('back')">返回上一页</button>
    <button @click="handleClick('push')">跳转到argu</button>
    <button @click="handleClick('replace')">跳转到parent</button>
  </div>
</template>

<script>
export default {
  name: 'Home',
  components: {},
  methods: {
    handleClick (type) {
      if (type === 'back') {
        // this.$router.go(-1)
        this.$router.back(-1)
      } else if (type === 'push') {
        // 有历史记录
        // this.$router.push('/parent')
        const name = 'minierpeng'
        this.$router.push({
          // 第一种:
          // name: 'argu',
          // query: {
          //   name: 'lp'
          // }
          // 第二种:
          // name: 'argu',
          // params: {
          //   name: 'erpeng'
          // },
          // 第三种:
          path: `/argu/${name}`
        })
      } else if (type === 'replace') {
        // 没有历史记录
        // this.$router.replace('/parent')
        this.$router.replace({
          name: 'parent'
        })
      }
    }
  }
}
</script>

  • argu.vue
<template>
  <div>
    {{$route.params.name}}
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
  • router.js
import Home from '@/views/Home.vue'
export default [
  // 别名路由
  {
    path: '/',
    alias: '/home_page',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    // 懒加载,只有当访问这个路由,才会加载路由
    component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
  },
  // 动态路由
  {
    path: '/argu/:name',
    name: 'argu',
    component: () => import('@/views/argu.vue')
  },
  // 嵌套路由
  {
    path: '/parent',
    name: 'parent',
    component: () => import('@/views/parent.vue'),
    children: [
      {
        path: 'child',
        component: () => import('@/views/child.vue')
      }
    ]
  },
  // 命名视图
  {
    path: '/named_view',
    components: {
      default: () => import('@/views/child.vue'),
      email: () => import('@/views/email.vue'),
      tel: () => import('@/views/tel.vue')
    }
  },
  // 重定向路由
  {
    path: '/main',
    // redirect:'/',
    // redirect: {
    //   name:'Home'
    // },
    redirect: to => '/'
    // redirect:to=>name:'Home',
  }
]

进阶

路由传参

  • 动态路由页面
    router.js
    
      {
        path: '/argu/:name',
        name: 'argu',
        component: () => import('@/views/argu.vue'),
        //第二种,
        props: true// $route.params作为组件属性
      }
      
    argu.vue
    
    <template>
        <div>
        <!-- {{$route.params.name}} 第一种直接取值-->
        {{name}}
        </div>
    </template>
    
    <script>
    export default {
      props: {
        name: {
          type: String,
          default: 'lp'
        }
      }
    }
    </script>
    
  • 非动态路由传参
    • 第一种、对象模式传入
      router.js
      
      {
          path: '/about',
          name: 'About',
          component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue'),
          //第一种、对象模式传入
          props: {
            food: 'banana'
          }
      }
      about.vue
      
      <template>
        <div class="about">
          <b>{{food}}</b>
        </div>
      </template>
      <script>
      export default {
        props: {
          food: {
            type: String,
            default: 'apple'
          }
        }
      }
      </script>
      
    • 第二种、函数模式
      router.js
      
      {
          path: '/',
          name: 'Home',
          component: Home,
          props: route => ({
            // 路由参数中传入的值:/?food=banana
            food: route.query.food
          })
      }
      
      Home.vue
      
      <template>
        <div class="home">
          <b>{{food}}</b>
        </div>
      </template>
      <script>
      export default {
        props: {
          food: {
            type: String,
            default: 'apple'
          }
        }
      }
      </script>
      

HTML5 history模式

  • 设置
在router的index.js

export default new VueRouter({
  mode: 'history',
  routes
})
  • 配置404页面
在router.js最后配置,因为路由优先级是从上到下。

  {
    path: '*',
    component: () => import('@/views/error_404.vue')
  }

导航守卫

  • 全局守卫

    • 全局前置守卫
    const HAS_LOGINED = false
    router.beforeEach((to, from, next) => {
      if (to.name !== 'login') {
        if (HAS_LOGINED) { next() } else { next({ name: 'login' }) }
      } else { if (HAS_LOGINED) { next({ name: 'Home' }) } else { next() } }
    })
    
    • 全局解析守卫
    // 在导航确认之前,在组件内守卫和异步路由被解析之后,被调用
    // router.beforeResolve()
    
    • 全局后置钩子
    router.afterEach((to, from) => {
      // 取消在router.beforeEach设置的等待login的加载的效果:
      // logining = false
    })
    
  • 路由独享守卫

    {
        path: '/',
        name: 'Home',
        component: Home,
        beforeEnter: (to, from, next) => {
          if (from.name === 'About') { alert('这是从about页来的') } else { alert('这不是从about页来的') }
          next()
        }
    }
    
  • 组件内守卫

    • beforeRouteEnter
      // 在渲染该组件的对应路由被确认前调用
      // 不能通过this获取组件实例
      // 因为当守卫执行前,组件实例还没被创建
      beforeRouteEnter (to, from, next) {
        console.log(from.name, to.name)
        // 可以通过next回调函数获取组件实例
        next(vm => console.log(vm))
      },
    
    • beforeRouteUpdate
      beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 可以访问组件实例 `this`
        console.log(from.name, to.name)
        next()
      }
    
    • beforeRouteLeave
      beforeRouteLeave (to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
        const leave = confirm('您确定离开吗')
        if (leave) { next() } else { next(false) }
      },
    
  • 完整导航解析流程

      1.导航被触发
      2.在失活的组件(即将离开的组件) 里调用离开守卫 beforeRouteLeave
      3.调用全局的前置守卫 beforeEach
      4.如果是重用的组件,则调用 beforeRouteUpdate
      5.调用路由独享的守卫 beforeEnter
      6.解析异步路由组件
      7.在被激活的组件(即将进入的页面) 调用 beforeRouteEnter
      8.调用全局解析守卫 beforeResolve
      9.导航被确认
      10.调用全局的后置钩子 afterEach
      11.触发DOM更新
      12.用创建好的实例调用beforeRouteEnter守卫里的next回调函数
    

路由元信息

    定义路由的时候可以配置 meta 字段:
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue'),
        meta: {
          title: '关于'
        }
      }
      可以在路由守卫里访问到
      router.beforeEach((to, from, next) => {
        console.log(to.meta)
    }

路由切换动效

  • 页面切换特效
<transition-group name="router">
<router-view key="default"/>
<router-view key="email" name="email"/>
<router-view key="tel" name="tel"/>
</transition-group>
    
<style lang="less">
// 页面进入时

// 页面即将显示
.router-enter{
  opacity: 0;
}
// 页面从没有到有的过程的效果
.router-enter-active{
  transition: opacity 1s ease;
}
//页面完全显示时
.router-enter-to{
  opacity: 1;
}

//页面离开时

// 页面即将显离开
.router-leave{
  opacity: 1;
}
// 页面从有到没有的过程的效果
.router-leave-active{
  transition: opacity 1s ease;
}
//页面完全离开时
.router-leave-to{
  opacity: 0;
}
</style>

  • 为某个页面设置特定特效
    <transition-group :name="routerTransition">
    <router-view key="default"/>
    <router-view key="email" name="email"/>
    <router-view key="tel" name="tel"/>
    </transition-group>
    
    <script>
    export default {
      data () {
        return {
          routerTransition: ''
        }
      },
      watch: {
        '$route' (to) {
          to.query && to.query.transitionName && (this.routerTransition = to.query.transitionName)
        }
      }
    }
    </script>

状态管理

Bus

Vuex

Vue 原理

如何理解MVVM

监听data变化的核心API:Object.defineProperty(Vue响应式原理)

  • 简单使用
var obj = {};
var name = 'lp';
Object.defineProperty(obj,"name",{
    get:function (){
        //当获取值的时候触发的函数
        console.log('get')
        return name
    },
    set:function (value){
        //当设置值的时候触发的函数,设置的新值通过参数value拿到
        console.log('set')
        name = value;
    }
});
//获取值
console.log( obj.name );  //get lp

//设置值
obj.name = 'erpeng';// set
  • Vue响应式原理
// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value)

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听
                observer(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
data.name = 'lisi'
data.age = 21
// console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
// data.nums.push(4) // 监听数组
  • Object.defineProperty缺点

    • 深度监听,需要递归到底,一次性计算量大
    • 无法监听新增属性/删除属性(解决:使用Vue.set Vue.delete)
    • 无法原生监听数组,需要特殊处理

虚拟 DOM (Virtual DOM)和 diff