VUE基础部分详解

580 阅读16分钟

自己为了查缺补漏写了VUE基础部分详解 这篇文章可以作为一个vue使用者快速回顾Vue基础部分的使用及其细节的文档

computed和watch

  1. computed有缓存,data不变则不会重新计算(提高运算性能)

  2. watch如何深度监听?

  3. wtach监听引用类型,拿不到oldVal

    区别

    **computed:**通过属性计算而得来的属性

      **1、**computed内部的函数在调用时不加()。

      **2、**computed是依赖vm中data的属性变化而变化的,也就是说,当data中的属性发生改变的时候,当前函数才会执行,data中的属性没有改变的时候,当前函数不会执行。

      **3、**computed中的函数必须用return返回。

      **4、**在computed中不要对data中的属性进行赋值操作。如果对data中的属性进行赋值操作了,就是data中的属性发生改变,从而触发computed中的函数,形成死循环了。

      **5、**当computed中的函数所依赖的属性没有发生改变,那么调用当前函数的时候会从缓存中读取。

      使用场景:当一个值受多个属性影响的时候------------购物车商品结算

      computed: {
        fullNameReverse() {
          return this.name + ' ' + this.info.city
        },
        fullName: {
            get() {//回调函数 当需要读取当前属性值是执行,根据相关数据计算并返回当前属性的值
                return this.info.city + ' ' + this.name
            },
            set(val) {//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据
                //val就是fullName的最新属性值
                console.log(val)
                const names = val.split(' ');
                console.log(names)
                this.firstName = names[0];
                this.lastName = names[1];
            }
        }
      },
    

    **watch:**属性监听

      **1、**watch中的函数名称必须要和data中的属性名一致,因为watch是依赖data中的属性,当data中的属性发生改变的时候,watch中的函数就会执行。

      **2、**watch中的函数有两个参数,前者是newVal,后者是oldVal。

      **3、**watch中的函数是不需要调用的。

      **4、**watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。"obj.name"(){}------如果obj的属性太多,这种方法的效率很低,obj:{handler(newVal){},deep:true}------用handler+deep的方式进行深度监听。

      **5、**特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者set。this.arr.splice(0,1,100)-----修改arr中第0项开始的1个数据为100,this.set(this.arr,0,100)-----修改arr第0项值为100。

      **6、**immediate:true 页面首次加载的时候做一次监听。

      使用场景:当一条数据的更改影响到多条数据的时候---------搜索框

      watch: {
        name(oldVal, val) {
          console.log('watch name', oldVal, val)
        },
        info: {
          handler(oldVal, val) {
            console.log('watch info', oldVal, val) // 引用类型,拿不到oldVal
          },
          deep: true // 深度监听
        }
      }
    

class和style

  1. 对象模式:class变量值对应boolean (class变量值对应写在样式文件的classname)
  2. 数组模式: 数组内填写多个class变量 (class变量值对应写在样式文件的classname)
  3. 变量模式:直接在script内部以写对象的方式写css (驼峰式)
<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

区别

  • 1.手段:v-if是通过控制dom节点的存在与否来控制元素的显隐;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
  • 2.编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
  • 3.编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
  • 4.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;

总结

​ 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

<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-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用。 下面这个例子是先进行 v-for 的渲染,然后在进行 v-if 的渲染。

如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素

<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>

事件

特点

  1. event 是原生的
  2. 事件被挂载到当前元素和 DOM 事件一样
  3. 用 vue 绑定的事件,组建销毁时会自动被解绑 (自己绑定的事件,需要自己销毁!!!)
<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>

事件修饰符

事件处理

  • .stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡
  • .prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播)
  • .capture:与事件冒泡的方向相反,事件捕获由外到内
  • .self:只会触发自己范围内的事件,不包含子元素
  • .once:只会触发一次

键盘修饰符

  • .enter:回车键
  • .tab:制表键
  • .delete:含deletebackspace
  • .esc:返回键
  • .space: 空格键
  • .up:向上键
  • .down:向下键
  • .left:向左键
  • .right:向右键

组件通信方式

这里列举一下 Vue 本身支持的通信方式:

  • prop

  • $emit

    这个其实类似 React 的 props 回调。

  • provide / inject

    如果你熟悉 React,这与 React 的 context 特性很相似。

props/$emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

下面通过一个TodoList来帮助理解父子组件通信

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)
        }
    }
}
</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)
            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>

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)
        }
    }
}
</script>

空的Vue实例作为中央事件总线(事件中心)emit/on

    var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});

$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

attrs/listeners

**attrs**:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

简单点讲就是包含了所以父组件在子组件上设置的属性(除了prop传递的属性、classstyle )。

**listeners**:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件

补充:inheritAttrs: 默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrsfalse,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

index.vue

<template>
  <div class="about">
    <attrs-child
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
      @test1="onTest1"
      @="$listeners"></attrs-child>
  </div>
</template>

<script>
import attrsChild from '@/components/attrsChild';
export default {
  components: {
    attrsChild
  },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  },
  methods: {
    onTest1() {
      console.log("test1 running...");
    }
  }
}
</script>

*attrsChild

<template>
  <div>
    <p>foo: {{ foo }}</p>
    <p>$attrs: {{ $attrs }}</p>
    <button @click="$emit('test1','按钮点击')">触发事件</button>
  </div>
</template>

<script>
export default {
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
}
</script>

parent/children与ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例
  • 使用 this.parent查找当前组件的父组件。
使用 this.children查找当前组件的直接子组件,可以遍历全部子组件, 需要注意 $children 并不保证顺序,也不是响应式的。
  • 不过,这两种方法的弊端是,无法在跨级或兄弟间通信
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}

// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

provide/inject

介绍

以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。

举例

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}

// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

注意:

provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档 所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的

如何实现数据响应式依赖注入

一般来说,有两种办法:

  • provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
  • 使用2.6最新API Vue.observable 优化响应式 provide(推荐)

第一种

// A 组件 
  provide() {
    return {
      theme: this//方法一:提供祖先组件的实例
    };
  },
// B 组件 
  inject: ['theme']

方法二

  // 方法二:使用2.6最新API Vue.observable 优化响应式 provide
provide() {
  this.theme = Vue.observable({
    color: "blue"
  });
 return {
    theme: this.theme
  }
 },
// B 组件 
  inject: ['theme']

vuex

介绍

Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。在具有VueX的Vue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。

成员列表:

  • state 存放状态
  • mutations state成员操作
  • getters 加工state成员给外界
  • actions 异步操作
  • modules 模块化状态管理

工作流程:

安装

  1. 在已经创建的vue项目中使用npm安装
npm i vuex -s
  1. 在项目的根目录下新增一个store文件夹,在该文件夹内创建index.js

使用

初始化store下的index.js

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承载变化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上,这里面的参数除了state之外还传了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const actions = {
    hideFooter(context) {  //自定义触发mutations里函数的方法,context与store 实例具有相同方法和属性
        context.commit('hide');
    },
    showFooter(context) {  //同上注释
        context.commit('show');
    },
    getNewNum(context,num){   //同上注释,num为要变化的形参
        context.commit('newNum',num)
     }
};
  const store = new Vuex.Store({
       state,
       getters,
       mutations,
       actions
});
export default store;

将store挂载到Vue实例中

  1. 在main.js中修改
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,  //store:store 和router一样,将我们创建的Vuex实例挂载到这个vue实例中
  render: h => h(App)
})

在组件中使用vuex

<template>
  <div id="app">
    <router-view/>
    <FooterBar v-if="isShow" />
  </div>
</template>
 
<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'; //先要引入
import FooterBar from '@/components/common/FooterBar'
import config from './config/index'
export default {
  name: 'App',
  components:{
    FooterBar:FooterBar
  },
  data(){
    return {
    }
  },
  computed:{
    ...mapState(['isShow']),  //这里的...是超引用,ES6的语法,意思是state里有多少属性值我可以在这里放多少属性
     //你也可以用下面的mapGetters来获取isShow的值,貌似下面的更简洁
   ...mapGetters(['isShow']),
   ...mapMutations(['show', 'hide'])
  },
  watch:{
      $route(to,from){
        if(to.name=='book'||to.name=='my'){
           this.show()
        }else{
           this.hide()
        }
      }
  }
}
</script>

规范目录结构

如果把整个store都放在index.js中是不合理的,所以需要拆分。比较合适的目录格式如下:

store:.
│  actions.js
│  getters.js
│  index.js
│  mutations.js
│  mutations_type.js   ##该项为存放mutaions方法常量的文件,按需要可加入
│
└─modules
        Astore.js

对应的内容存放在对应的文件中,和以前一样,在index.js中存放并导出storestate中的数据尽量放在index.js中。而modules中的Astore局部模块状态如果多的话也可以进行细分。

改写

store/index.js


import Vue from 'vue';
import Vuex from 'vuex';
import footerStatus from './modules/footerStatus'
import collection from './modules/collection'
Vue.use(Vuex);

export default new Vuex.Store({
    modules:{
         footerStatus,
         collection
    }
});

store/module/footerStatus.js

//footerStatus.js
 
const state={   //要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承载变化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上,这里面的参数除了state之外还传了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const actions = {
    hideFooter(context) {  //自定义触发mutations里函数的方法,context与store 实例具有相同方法和属性
        context.commit('hide');
    },
    showFooter(context) {  //同上注释
        context.commit('show');
    },
    getNewNum(context,num){   //同上注释,num为要变化的形参
        context.commit('newNum',num)
     }
};
export default {
    namespaced: true, //用于在全局引用此文里的方法时标识这一个的文件名
    state,
    getters,
    mutations,
    actions
}

store/module/collection.js**

//collection.js

const state={
    collects:[],  //初始化一个colects数组
};
const getters={
  renderCollects(state){ //承载变化的collects
    return state.collects;
  }
};
const mutations={
     pushCollects(state,items){ //如何变化collects,插入items
        state.collects.push(items)
     }
 };
const actions={
    invokePushItems(context,item){ //触发mutations里面的pushCollects ,传入数据形参item 对应到items
        context.commit('pushCollects',item);
    }
};
export default {
     namespaced:true,//用于在全局引用此文件里的方法时标识这一个的文件名
     state,
     getters,
     mutations,
     actions
}

使用vuex

<template>
  <div id="app">
    <router-view/>
    <FooterBar v-if="isShow" />
  </div>
</template>
 
<script>
import {mapState,mapGetters,mapActions} from 'vuex'; //先要引入
import FooterBar from '@/components/common/FooterBar'
import config from './config/index'
export default {
  name: 'App',
  components:{
    FooterBar:FooterBar
  },
  data(){
    return {
    }
  },
  computed:{
    ...mapState({  //这里的...是超引用,ES6的语法,意思是state里有多少属性值我可以在这里放多少属性值
         isShow:state=>state.footerStatus.showFooter //注意这些与上面的区别就是state.footerStatus,
                                                      //里面定义的showFooter是指footerStatus.js里state的showFooter
      }),
     //你也可以用下面的mapGetters来获取isShow的值,貌似下面的更简洁
    /*...mapGetters('footerStatus',{ //footerStatus指的是modules文件夹下的footerStatus.js模块
         isShow:'isShow' //第一个isShow是我自定义的只要对应template里v-if="isShow"就行,
                         //第二个isShow是对应的footerStatus.js里的getters里的isShow
      })*/
  },
  watch:{
      $route(to,from){
        if(to.name=='book'||to.name=='my'){
           this.$store.dispatch('footerStatus/showFooter') //这里改为'footerStatus/showFooter',
                                                           //意思是指footerStatus.js里actions里的showFooter方法
        }else{
           this.$store.dispatch('footerStatus/hideFooter') //同上注释
        }
      }
  }
}
</script>

生命周期

图解

Vue 实例生命周期

详解

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

在beforeCreate和created钩子函数之间的生命周期****

在这个生命周期之间,进行初始化事件,进行数据的观测,可以看到在created的时候数据已经和data属性进行绑定(放在data中的属性当值发生改变的同时,视图也会改变)。 注意看下:此时还是没有el选项

created钩子函数和beforeMount间的生命周期

首先会判断对象是否有el选项如果有的话就继续向下编译,如果没有el选项**,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)。**此时注释掉代码中:

然后,我们往下看,template参数选项的有无对生命周期的影响。 (1).如果vue实例对象中有template参数选项,则将其作为模板编译成render函数。 (2).如果没有template选项,则将外部HTML作为模板编译。 (3).可以看到template中的模板优先级要高于outer HTML的优先级。

在vue对象中还有一个render函数,它是以createElement作为参数,然后做渲染操作,而且我们可以直接嵌入JSX.

new Vue({
    el: '#app',
    render: function(createElement) {
        return createElement('h1', 'this is createElement')
    }
})

所以综合排名优先级: render函数选项 > template选项 > outer HTML.

beforeMount和mounted 钩子函数间的生命周期

可以看到此时是给vue实例对象添加**$el成员**,并且替换掉挂在的DOM元素。之前beforeMount之前el上还是undefined。

beforeUpdate钩子函数和updated钩子函数间的生命周期

当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdateupdated钩子函数

beforeDestroy和destroyed钩子函数间的生命周期

beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。 destroyed钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。


VueRouter

命名路由

有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

要链接到一个命名路由,可以给 router-linkto 属性传一个对象:

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

const User = {
  template: '<div>User</div>'
}

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})

现在呢,像 /user/foo/user/bar 都将映射到相同的路由。

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

模式 匹配路径 $route.params
/user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }

vue-router如何传参

params,query是什么?

params:/router1/:id ,/router1/123,/router1/789 //这里的id叫做params
query:/router1?id=123 ,/router1?id=456 //这里的id叫做query。
比如:跳转/router1/:id
<router-link :to="{ name:'router1',params: { id: status}}" >正确</router-link>
<router-link :to="{ name:'router1',params: { id2: status}}">错误</router-link>
// 上面的router-link传参,也可以使用编程式导航跳转
this.$router.push({  name:'router1',params: { id: status ,id2: status3},query: { queryId:  status2 }});
//编程跳转写在一个函数里面,通过click等方法来触发

取数据

params 传参

this.$router.push({name: 'dispatch', params: {paicheNo: obj.paicheNo}})

this.$route.params.paicheNo

query 传参

this.$router.push({path: '/transport/dispatch', query: {paicheNo: obj.paicheNo}})

this.$route.query.paicheNo

this.router 和 this.route

我们知道this.$routerrouter实例,可以用来直接访问路由。我们称router配置中每一个对象为一个路由记录,this.$route是暴露出来用来访问每个路由记录的。因此我们获取参数时使用的是this.$route跳转路由时使用的是道this.$router

我们知道this.$routerrouter实例,可以用来直接访问路由。我们称router配置中每一个对象为一个路由记录,this.$route是暴露出来用来访问每个路由记录的。因此我们获取参数时使用的是this.$route跳转路由时使用的是道this.$router

this.router.push 与 this.router.replace

这两者的区别是会不会在history中产生记录。replace不会新增记录,而是直接替换掉了这条路由记录。

响应路由参数的变化

提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}

或者 beforeRouteUpdate 导航守卫

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

捕获所有路由或 404 Not found 路由

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*

{
  // 会匹配所有路径
  path: '*'
}
{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

重定向

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

HTML5 History模式

介绍

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

代码演示

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

后端配置

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

原生NodeJS

const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})

警告!!!!!

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。