vue学习纪录(二)

基础

数组更新

vue包含一组观察数组变异方法,使用它们改变数组也会触发视图更新,push/pop/shift/unshift/splice/sort/reverse,但是非变异方法不改变数组(filter/concat/slice),可以用新数组代替就数组实现视图更新。xx = xx.filter()
注意:arr[3]=newVal 不会被检测到
可以:

  1. 使用vue内置的set

    1
    this.$set(arr, 3, newVal)
  2. 使用数组的变异方法splice

    1
    arr.splice(3,1, newVal)

方法与事件

@click调用的方法名可以不带括号”()”,此时,如果方法有参数,默认会将原生事件对象event传入。在大部分场景下,如果方法不需要写参数,可以简便不写括号。Vue提供了一个特殊变量$event,用于访问原生DOM事件,例如下面阻止链接打开。如果使用内联语句,语句可以访问一个 $event 属性:v-on:click=”handle(‘ok’, $event)”

1
2
3
4
5
<a herf="www.baidu.com" @click="handle('禁止打开',$event)></a>
handle(message, event){
event.preventDefault()
window.aleart(message)
}

事件修饰符

  • prevent

    1
    2
    <form @submit.prevent="submit"></form> // 阻止表单提交刷新页面
    <a href="www.baidu.com" @click.prevent="goTo"> // 阻止a链接点击跳转
  • stop

    1
    <div @click.stop="show"> // 阻止点击事件冒泡
  • self

    1
    <div @click.self="show"> // 只在该元素触发时(非子元素)触发回调
  • capture

    1
    <div @click.capture="show"> // 使用事件捕获模式
  • once

    1
    <div @click.once="show"> // 只触发一次

键盘事件按键修饰符

  • enter
  • tab
  • delete
  • space
  • up/ down
    1
    <div @keyup.shift.13="doSomething"> // 只触发一次

v-model

表单控件在实际业务较为常见,比如单选、多选、下拉和输入。v-model用于在表单类元素绑定数据。

1个单选按钮 (不需要v-model, 直接绑定一个boolean)

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<input type="radio" :checked="picked">
</div>
<script>
var app = new Vue({
el:'#app',
data:{
picked:true
}
})
</script>

多个单选按钮 (v-model+ value)

1
2
3
4
5
6
7
8
9
10
<input type="radio" v-model="picked" value="html">
<label for="html">HTML</label>

<input type="radio" v-model="picked" value="CSS">
<label for="css">css</label>

<input type="radio" v-model="picked" value="js">
<label for="js">js</label>

picked: 'js'

多个多选按钮 (v-model+ value)

1
2
3
4
5
6
7
8
9
10
<input type="checkbox" v-model="checked" value="html">
<label for="html">HTML</label>

<input type="checkbox" v-model="checked" value="CSS">
<label for="css">css</label>

<input type="checkbox" v-model="checked" value="js">
<label for="js">js</label>

checked:['js','css']

v-model修饰符

  • .lazy (不在input,而在change时候同步数据,即失焦或回车)
  • .number (将输入转换为number类型,否则虽然输入的是数字,类型其实是Strng, 这个在数字输入框比较有用)
  • trim (自动过滤首尾空格)

组件

注册和使用

全局注册, 任何vue实例都可以使用。Vue.component(‘my-comp’,{//options})
组件内注册,只有该实例作用域才能使用。 使用components选项

Vue组件的模板在某些情况下会受html的限制,比如table标签规定只允许是tr/td这些表格元素,所以在table里面直接使用组件是无效的,可以用特殊的is属性来挂载组件。

1
2
3
<table>
<tbody is="my-component"></tbody>
<table>

props传递数据

props中声明的数据与组件data函数reutrn的数据主要区别是props来自父级,而data中的是组件自己的数据,作用域是组件本身。这两种数据都可以在模板template、计算属性computed和方法methods中使用。

有时候传递的数据不是直接写死的,而是来自父级的动态数据,可以用指令v-bind来动态绑定props的值,当父组件数据变化时,也会传递给子组件。
注意:当传递的不是字符串,而是数字、布尔值、数组或对象,而不用v-bind, 传递的仅仅是字符串

1
2
3
4
5
<my-comp msg='[1,2,3]'></my-comp>
<my-comp :msg='[1,2,3]'></my-comp>

props:[msg]
template:'<div>{{msg.length}}</div>' // 7 3

单向数据流
业务中经常遇到两种需要改变prop的情况,一种是子组件将prop作为初始值保存起来,在自己的作用域下可以随意使用和修改,这种情况可以在组件data内再声明一个数据,引用prop

1
2
3
4
5
6
props:[initCount]
data:function(){
return {
count: this.initCount
}
}

另外一种是prop作为需要被转变的值传入,这种情况用计算属性就可以

1
2
3
4
5
props:[width],
template:'<div :style="style"></div>',
computed: style(){
return {width: this.width+'px'}
}

组件通信

自定义事件

v-on除了监听DOM事件外,还可以用于组件间的自定义事件。
$emit 第一个参数是自定义事件的名称。

v-on除了在组件上监听自定义事件外,还可以监听DOM事件,用.native修饰符表示监听的是一个原生事件,是该组件的根元素

1
<comp @click.native="handelClick"></comp>

使用v-model

语法糖

1
<input v-model="price">

语法糖,实际上是

1
<input :value="price" @input="price=$event.target.value">

v-model作用在自定义组件上

1
<cust-input v-model="price"></cust-input>

语法糖,实际上是(两者效果相同)

1
2
3
<cust-input :value="price" @input="price=argument[0]">
<cust-input :value="price" @input="price= $event"">
//在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值

实现

原理: 数据从父组件通过props传入,子组件内部修改了数据,通过$emit通知父组件修改props
实现一个具有双向绑定v-model的组件需要满足下面两个要求

  1. 接收一个value属性
  2. 在有新的value时触发input事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

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

slot

当需要让组件组合使用时,混合父组件内容与子组件的模板时,就会用到slot, 这个过程叫做内容分发。
props传递数据,event触发事件和slot内容分发,构成了vue组件的3个api来源,再复杂的组件也是由这3部分组成。

理解作用域

父组件的模板是在父组件作用域内编译的,子组件模板的内容是在子组件作用域内编译的。slot分发的内容,作用域是在父组件上的。

slot用法

在子组件内使用特殊的slot元素就可以为这个子组件开启一个插槽,在父组件模板里插入子组件标签内的所有内容都会替代子组件的slot标签及其内容。

具名slot

1
2
3
4
5
6
7
8
9
10
<child-comp>
<div slot="header">i am header</div>
<div>test1</div>
<div>test2</div>
<div slot="content">i am content</div>
</child-comp>

Vue.component('child-comp',{
template:'<div><slot name='header'></slot><slot></slot><slot name='content'></slot></div>'
})

渲染出

1
2
3
4
5
6
<div>
<div>i am header</div>
<div>test1</div>
<div>test2</div>
<div>i am content</div>
</div>

作用域插槽

可从子组件插槽获取数据。作用域插槽的使用场景是既能复用子组件的slot, 又可以使slot的内容不一致。
简单理解,父组件可以利用子组件数据为子组件定制模板,定制化信息输出。

一个todolist组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// <todo-list>
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

// 修改后
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>

//使用
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>

访问

$slots, 主要用于独立组件开发。

高级组件

递归组件
只要给组件设置name选项就可以,在组件内部递归,必须给一个条件限制递归数量,否则会栈溢出。

动态组件
Vue提供了一个特殊的元素component来动态挂载不同的组件,使用is特性来选择要挂载的组件。

异步组件
vue允许将组件定义为1个工厂函数,只在组件需要渲染的时候触发工厂函数,并把结果缓存起来,用于后面再次渲染。工厂函数接收一个resovle回调,在收到服务器下载的组件定义时使用。

其他

$nextTick

重要概念异步更新队列
vue在挂测到数据变化的时候并不会直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有的数据改变。在缓冲时去除重复数据,从而避免不必要的计算和DOM操作。然后在下一个事件循环中,vue刷新队列更新DOM
$nextTick就是用来知道什么时候DOM更新完的

手动挂载实例

1
2
3
4
5
6
7
8
9
10
11
// demo 1 手动挂载
new Vue().$mount('#root')

// demo 2
new Vue({
el:'#root'
})

// demo 3 处于文档外随后挂载
var instance = new Vue().$mount()
doucment.getElementById('root').appendChild(instance.$el)

插件

vue-router

前端路由实现方式

  1. hash
    js 通过hashChange事件监听url改变
  2. history(HTML5)
    这种模式需要服务端支持,服务端在收到所有请求后,都指向同一个html文件,不然会出现404.因此SPA只有一个html,整个网站的内容都在这个html里面,通过js来处理。
    webpack-dev-server也要配置增加支持history路由,增加–history-api-fallback,所有的路由会指向index.html

前端路由优点

  1. 页面持久性,像大部分音乐网站,可以播放的时候调到其他页面,音乐不中断
  2. 前后端分离

基础用法

webpack会把每一个路由打包成一个js文件,在请求到该页面时,采取加载这个页面的js,这就是异步实现的懒加载,按需加载。
路由切换时,切换的是标签router-view挂载的组件。

跳转?

  1. 使用内置的router-link, 会被渲染成为1个a标签
  2. $router.replace()/go()/push()

高级用法

路由列表的meta字段可以自定义一些信息。如beforeEach钩子从路由对象to里获取meta信息,从而改变标题。

beforeEach和afterEach可以做很多事情来提升用户体验

  1. 页面跳转后滚动条位置

    1
    2
    3
    router.afterEach((to, from, next)=>{
    window.scrollTo(0, 0)
    })
  2. 页面过渡的全局loading动画, beforeEach开始,afterEach结束

  3. next()方法设置参数,如校验登录
    1
    2
    3
    4
    5
    6
    7
    router.beforeEach((to, from, next)=>{
    if(window.localStorage.getItem('token')){
    next()
    }else{
    next('/login')
    }
    })

vuex

模块

modules/ state/ getter/ mutations/ actions,
每个moudle有自己的state/ getter/ mutations/ actions

重点

mutation尽量不要做异步操作,如果有,组件commit后,数据不能立即改变,而且不知道什么时候改变?
action提交的是mutaion, 而且可以异步操作业务逻辑,组件内通过$store.dispatch触发action.

所有涉及数据改变,就用mutation, 存在业务逻辑的,就用actions, 至于将业务逻辑放在actions还是组件内部,根据实际场景拿捏