vue相关技术梳理

vue的梳理对照react展开,文章:React相关技术梳理

Vue

概览

核心概念

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。

在这里有了2个基本元素,html的模板,以及Vue示例。html模板相当于JSX元素,Vue示例是控制模板的。这与react不同,react组件渲染之后就是元素。Vue是一种组合方式,React是一种类封装方式。

Vue实例中有2个属性:el(挂载的元素)、data。这里的data与react相比不再分props与state,它俩都是data

vue指令

  • v-bind

    绑定模板中的attribute与Vue实例中的property。

    <div id="app-2">
      <span v-bind:title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
      </span>
    </div>
    
    var app2 = new Vue({
      el: '#app-2',
      data: {
        message: '页面加载于 ' + new Date().toLocaleString()
      }
    })
    

    将这个元素节点的 title attribute 和 Vue 实例的 message property 保持一致”。

    v-bind大多是对显示类组件绑定,表单类组件一般用的v-model

  • v-if

    把数据绑定到 DOM 文本或 attribute,还可以绑定到 DOM 结构。控制切换一个元素是否显示。

  • v-for

    v-for 指令可以绑定数组的数据来渲染一个项目列表li

  • v-on

    为了让用户和你的应用进行交互,可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法。

    <div id="app-5">
      <p>{{ message }}</p>
      <button v-on:click="reverseMessage">反转消息</button>
    </div>
    
    var app5 = new Vue({
      el: '#app-5',
      data: {
        message: 'Hello Vue.js!'
      },
      methods: {
        reverseMessage: function () {
          this.message = this.message.split('').reverse().join('')
        }
      }
    })
    

    这里我们看到了Vue实例第3个属性:methods,这里定义函数。

组件系统

这一部分与react的理念部分类似,都是讲解怎么来使用vue,下边来看看:

  • 组件拆分

    几乎任意类型的应用界面都可以抽象为一个组件树,

    components
  • 模板、Vue示例、Vue组件3元素

    为了能够重用页面,这里也必须需要组件(Component),那这样Vue就有3个内容:模板、Vue示例、Vue组件3元素

    Vue组件更偏View,是将部分的View做了封装,这样可以进行重用。Vue示例做的绑定,并且可以做控制。

    <!-- 模板 -->
    <div id="app-7">
      <ol>
        <todo-item
          v-for="item in groceryList"
          v-bind:todo="item"
          v-bind:key="item.id"
        ></todo-item>
      </ol>
    </div>
    
    // Vue组件
    Vue.component('todo-item', {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
    
    // Vue实例
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { id: 0, text: '蔬菜' },
          { id: 1, text: '奶酪' },
          { id: 2, text: '随便其它什么人吃的东西' }
        ]
      }
    })
    

    Vue组件有2个属性:props与template,子单元通过 prop 接口与父单元进行了良好的解耦。

下边分别来看看Vue的基本内容

Vue实例

本节来看看Vue实例。虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。反观Vue实例,确实做了data-binding的事情,将数据(model)与模板(view)做了绑定。但又不仅仅是数据绑定,应为它还有methods,所以也做了部分controller的事情。

在这里我们看到:你只需要明白所有的 Vue 组件都是 Vue 实例,它俩确实很接近,都做了data-binding的事情。定义的方式不同,Vue实例是绑定到模板,Vue组件是被模板所使用。到目前为止,Vue组件中还没有出现methods,算是低配的Vue实例

响应式

vue示例中的数据发生变化,模板中的数据就相应变化。这也是mvvm的好处。

可以通过Object.freeze(obj)来阻止这种binding

它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值

// 我们的数据对象
var data = { a: 1 }

// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
  data: data
})

// 获得这个实例上的 property
// 返回源数据中对应的字段
vm.a == data.a // => true

$ 方法

为了区别Vue示例中自带的,与用户自定义的,通过$符号来加以区分。

var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
  // 这个回调将在 `vm.a` 改变后调用
})

生命周期

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  }
   updated: function(){...}
    destroyed: function(){...}
})

create => mount => update => destroy

lifecycle

computed属性*

这个计算属性与react中的计算属性类似。

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。这时候就用计算属性。

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

这里我们看到vue示例的第4个属性:computed、el、 data、 method(除生命周期函数)

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。

与react类似,computed是响应式的,只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

计算属性默认只有getter,我们可以在需要时增加一个setter

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

watch

当需要在数据变化时执行异步或开销较大的操作时,watch是最有用的。

官方例子中,将axios请求放到了watch中执行。地址

这里看到了Vue的第5个属性watch(el、data、methods、computed、watch)

它通过录入内容,实时向后端请求数据,通过v-model,绑定input的数据到Vue的question属性上,然后通过watch来监听该propterty,当该property发生变化后,调用this.debouncedGetAnswer()。

模板语法

模板语法是vue特有的,上边介绍的vue指令,就是模板语法。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

插值

将vue示例中的数据,绑定在模板上,可以绑定在content中,也可以绑定在attribute上。

  • 双大括号

    绑定在内容上

    <span>Message: {{ msg }}</span>
    

    Mustache 标签(“双大括号”)将会被替代为对应数据对象上 msg property 的值。

    双大括号中,可以使表达式,如:{{ number + 1 }}

  • v-html

    双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令

    绑定在attribute上

  • v-bind

    Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind

    绑定在attribute上

指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute。

  • v-bind

    v-bind是一种单向绑定,将vue示例的property渲染到模板上。

  • v-if

  • v-for

  • v-on

  • 动态参数

    <a v-bind:[attributeName]="url"> ... </a>
    

    这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href

    <a v-on:[eventName]="doSomething"> ... </a>
    

    在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

    注意:

    动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。因此要避免使用在动态参数中使用空格与引号

    在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写

    <!--
    在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
    除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
    -->
    <a v-bind:[someAttr]="value"> ... </a>
    

    ps:通过directive自定义命令。

修饰符*

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

<form v-on:submit.prevent="onSubmit">...</form>

事件修饰符

.prevent修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault().

.stop, 阻止事件冒泡

.once,只触发一次

.self, 事件绑定的元素本身触发时才触发回调

.native,将一个vue组件变成一个普通的html,使其可以监听click等原生事件

表单修饰符

.lazy在输入框输入完内容,光标离开时才更新视图

.trim 过滤首尾空格

.number 如果先输入数字,那它就会限制你输入的只能是数字;如果先输入字符串,那就相当于没有加.number

缩写

Vue 为 v-bindv-on 这两个最常用的指令,提供了特定简写。还有一个#

v-bind

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>

v-on@

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

v-model? #

class**

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,**所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。**不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 active 这个 class 存在与否将取决于数据 property isActivetruthiness

当isActive为true事,会被渲染成:

<div class="active"></div>

除了对象,还可以把数组传给

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

会被渲染成:

<div class="active text-danger"></div>

style

<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

表单绑定 v-model

以上对条件渲染(v-if)、列表渲染(v-for)、事件(v-on)介绍挺多了,这里不再赘言了。

v-on有几个修饰符在这里.

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

下边来看看:

  • 单行录入 input

    <input v-model="message" placeholder="edit me">
    <p>Message is: {{ message }}</p>
    
  • 多行录入 textare

    <span>Multiline message is:</span>
    <p style="white-space: pre-line;">{{ message }}</p>
    <br>
    <textarea v-model="message" placeholder="add multiple lines"></textarea>
    
  • 复选框 input checkbox

    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">{{ checked }}</label>
    
  • 单选框 input radio

    <div id="example-4">
      <input type="radio" id="one" value="One" v-model="picked">
      <label for="one">One</label>
      <br>
      <input type="radio" id="two" value="Two" v-model="picked">
      <label for="two">Two</label>
      <br>
      <span>Picked: {{ picked }}</span>
    </div>
    
  • 下拉 select

    <div id="example-5">
      <select v-model="selected">
        <option disabled value="">请选择</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>
      <span>Selected: {{ selected }}</span>
    </div>
    

组件基础

组件是可复用的 Vue 实例,且带有一个名字。

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

这里讲清楚了,vue组件包含这与vue实例相同的属性,除了vue实例用el进行挂载,而vue组件有自己名字之外,其余相同。

data必须是一个函数

因为组件可以复用,而data如果是一个对象,就会发现所有共用的是一个状态。一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。

data: function () {
  return {
    count: 0
  }
}

注册

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的

Vue.component('my-component-name', {
  // ... options ...
})

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。

局部注册:

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

可以先定义组件

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

然后在components中定义组件

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

注意局部注册的组件在其子组件中*不可用*。如果想用,就需要注册进该组件。

通过props向子组件传递数据**

这个与React的相同

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

监听子组件事件(自定义事件)*

Vue 实例提供了一个自定义事件的系统,使父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件,子组件中用$emit('事件名')来触发事件。

父组件:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

子组件:

<button v-on:click="$emit('enlarge-text')">
  Enlarge text
</button>

疑问:这种方式是否可以在兄弟组件之间传参?

不可以直接在兄弟组件之间传递数据

$emit()函数可以抛出第二个参数,用于给相应函数的参数,如下:

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

组件上使用v-model

<input v-model="searchText">

等价于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

v-model做了2个事,绑定到数据property上,接收数据改变;监听输入,修改对应的数据。自定义组件上,v-model是这样:

疑问:v-bind不就是双向的吗?

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 <input> 必须:

  • 将其 value attribute 绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出

写成之后是这样:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

内部通过emit将事件与数据抛出,外部通过v-on绑定到searchText上。

通过插槽向子组件传递内容

和 HTML 元素一样,我们经常需要向一个组件传递内容,将content传递到相应的位置上

<alert-box>
  Something bad happened.
</alert-box>
Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})

插槽是把内容插到组件的相应位置上。

动态组件*

有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里,可以过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现。

如下:

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括

  • 已注册组件的名字,或
  • 一个组件的选项对象

示例

通过keep-alive来缓存失活的组件

<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

或者

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

这一部分理解的不深刻,暂时先这样。

Vuex

对比Redux,来看看vuex。

基础概念

vuex

在redux中,action被dispatch到reducer中去执行,在ruducer中决定新的状态,并返回给store,然后触发view发生变化。

这里也类似,view与store之间也是view向上发送action,而在mutation中改变state,然后触发view发生变化。

这里的action并不直接触发state的改变,而是先触发了一个mutaion(reducer)然后再触发state的改变,state触发view的改变。

state

mapState

getters

mapGetters

mutations

mapMutations

Vuex 中的 mutation 非常类似于事件,各mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler).

mutations的实现就是基于事件的,emit与on。

state与getters都是从store中获取数据,mutations则是改变store中的数据。

mutations必须是同步函数。

mutations对标的reducer,计算新的state。

actions*

mapActions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

可以在 action 内部执行异步操作。

action通过store.dispatch 方法触发

Action通常是异步的,如何才能组合多个 action,以处理更加复杂的异步流程?

store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

然后就可以

store.dispatch('actionA').then(() => {
  // ...
})

module

于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象

对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState.

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }

它还有命名空间,这个先不做深入的学习。

Router

router与react的router相比很类似,也是有link、router、route3元素,以及栈结构组成,然后就是动态路由、路由传参等内容,下边简单看一下:

还有一个router-view,router-view像是一个占位符,将route带的componet渲染到router-view的位置上

基础概念

在模板中只用router-link来做导航:

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>

然后通过创建VueRouter的实例来创建路由,并将其注入到Vue实例中

// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')

// 现在,应用已经启动了!

最后在组件中,可以通过this.$route来访问当前路由,也可以通过this.$router来访问路由器如下:

// Home.vue
export default {
  computed: {
    username() {
      // 我们很快就会看到 `params` 是什么
      return this.$route.params.username
    }
  },
  methods: {
    goBack() {
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
    }
  }
}

动态路由

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用

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

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

可以通过 watch来监测$route 对象的变化

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

路由的配置同样是按顺序进行匹配的,所以需要注意顺序。最后用path:'*' 来匹配404错误。

嵌套路由*

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
            // 当 /user/:id 匹配成功,
        	// UserHome 会被渲染在 User 的 <router-view> 中
            path: '', 
            component: UserHome 
        },  
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

children 配置就是像 routes 配置一样的路由配置数组,所以可以嵌套多层路由。

对于在children中增加path:'',这条有点疑惑:那匹配的User还是UserHome?这样留到实践中。

编程式导航

router本质是个栈,除了用 <router-link :to="...">外还可以使用router.push(...)

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123

router.push({ path: `/user/${userId}` }) // -> /user/123

// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意:如果提供了 pathparams 会被忽略

与react-router类似,除了push还包括replace、go,这里不赘述了。

命名路由

可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

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

这样可以使用name来进行导航:

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

或者:

router.push({ name: 'user', params: { userId: 123 }})

命名视图*

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default

router-view可以有多个,这样在router中可以带多个component与其相匹配。

<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
</div>

这样,在componets中可以携带多个组件

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

router-view是在模板中占位,通过router将相应的componet渲染到对应的位置上。

路由传参

重定向与别名与react-router是相同的,这里不做介绍了。

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

下边来简单来看看路由传参。

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性

使用 props 将组件和路由解耦:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

以上代码将组件User与router做了绑定,修改如下:

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})

上例通过props设置为ture,route.params 将会被设置为组件属性。

通过props解耦有3种方式,除了上例的布尔模式,还有函数模式,如下:

const router = new VueRouter({
  routes: [
    { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
  ]
})

URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×