Vue的MVVM模型

知道什么是MVVM

  • M: (Model 模型) 即后端传递过来或者自己定义的数据(对应vue组件当中的data,props,computed等)
  • V: (View 视图) 即用户看到的界面UI (也就是我们组件当中的template部分)
  • VM: ViewModel,负责实现View和Model之间数据状态同步的中间对象

MVVM的关系

MVVM关系

MVVM优点

在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,
Model 和 ViewModel 之间的交互是双向的,
因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 和 Model 连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

假如面试问到我说说看MVVM,我可能会这样子回答

  • 我理解的MVVM就是M是model,也就是我们通过ajax请求获取到的数据或者是自己定义的数据
  • V:也就是用户在浏览器所看到的视图
  • VM:vue的实例化对象,我们通过vm完成model和view的数据状态同步,从而实现数据变动,视图也变动

Vue的绑定键盘鼠标操作和在组件使用上的要点

Vue的事件操作(v-on:click=”xxx”或@click)

  • 基本使用:
    • <div @click="事件"></div>
    • 或者<div @click.xxxx="事件"></div>
    • 或者<div v-on.click="事件"></div>
    • 或者<div v-on.click.xxx="事件"></div>
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
//除了@click="鼠标单击事件外"
//还有@click.xxx="经过修饰符指定的类型的事件"
.stop //停止事件冒泡(等同于event.stopPropagination())
.prevent //阻止默认行为(比如单击一个按钮莫名其妙提交了表单,等用于event.preventDefault())
.capture
.self
.once //只执行一次
.passive

<!-- 阻止单击事件继续传播 -->
<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>

Vue的按键操作的别名(这样子就不用通过判断键代码来获取用户是哪一个操作了)

  • 基本使用: <div @keyup.xxxx="事件"></div> 或者<div v-on.keyup.xxx="事件"></div>
1
2
3
4
5
6
7
8
9
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

组件使用操作按钮注意点

  • 不管是什么,在组件上传递的事件,都是自定义事件!在HTML标签上添加就是原生DOM事件,比如说@click,@mousemove这些系统自带的原生事件在组件上直接使用的话都是自定义事件,组件内部需要对其进行处理才可以使用! 具体可以看看这博客
  • 所以如果组件上想使用原生事件,需要添加.native
1
2
3
4
5
6
7
8
9
比如有一个自定义组件MyButton

//这里的@click即为自定义事件(因为是绑定在组件上的!)
//如果MyButton组件没有对这个自定义事件进行处理,那么这个事件是没有用的
<MyButton @click="事件"></MyButton>

//这里的@click即为原生事件(因为使用了.native)
//即使MyButton组件没有对这个事件进行处理,这个事件依旧会触发(因为是原生的,vue帮我们处理好了)
<MyButton @click.native="事件"></MyButton>

Vue的数据绑定响应式

vue当中,除了data当中的数据会被vue转化为响应式数据以外,其他地方添加的数据均不是响应式,如果需要,必须要使用$set或者set方法官方是这样子说的,但是好像computed其实也是响应式

如何追踪变化

简单一句就是setter和getter方式,这里就不多说了,感兴趣的可以看看官方,这里记录下官方的话语

  • 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
  • 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
  • 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

图片

特别注意

对于对象
  • data当中初始化的数据为响应式(也就是数据变了,视图也会发生变化!)
1
2
3
4
5
6
7
8
9
10
11
var vm = new Vue({
data:{
a:1
}
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的
(数据发生变化,依据这个数据绑定的标签之类的不会发生变化)
  • 非响应式数据变成响应式数据$set或者set(使得数据发生变化,视图也会发生变化)
1
2
3
4
5
6
7
//以myObj当中新添加的属性 b ,并设置为响应式数据 为例子
//myObj.b = 2;//直接添加-非响应式
Vue.set(myObj,'b',2);//set添加-响应式

//或者 this为Vue的实例化对象
this.$set(myObj,'b',2);//$set添加-响应式

  • 多个属性设置为响应式数据Object.assign方法
    • 通过整体替换的形式
    • 在线演示 @地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var vm = new Vue({
data(){
return {
someObject:{
name:"李白",
sex:"男"
}
}
}
})

this.someObject = Object.assign({}, this.someObject, {
a: 1,
b: 2
});
//这样子myObj就成为了一个响应式对象
console.log(this.someObject);
//{ name: '李白', sex: '男', a: 1, b: 2 }

注意,不可以this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });因为这依旧是对原来的数据进行添加删除,而不是进行了替换!

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
36
37
38
39
40
41
42
//证明
//有效(数据变化,视图也变换!)
this.someObject = Object.assign({}, this.someObject, { a: 1,b: 2 });

//无效(数据变化了,视图没有变化!)
this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });

<template>
<div>
<button @click="addProperty">单击我</button>
<span>{{ someObject.a }}</span>
</div>
</template>

<script>
export default {
name: "Home",
data() {
return {
someObject: {
name: "李白",
sex: "男",
},
};
},
methods: {
addProperty() {
// 单击后视图没有出现 新添加的数字1,既然是响应式数据,数据变化了,视图也会发生变化,这里却没有
// 所以代码1 的写法是错误的!
// 代码1 : this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });

//单击后视图出现了 新添加的数字1
//数据变化,视图变化,响应式数据
this.someObject = Object.assign({}, this.someObject, {
a: 1,
b: 2,
});//出现了1
},
},
};
</script>

对于数组

由于对象当中有setter和getters,数组没有,所以数组的所有操作并不像对象那样容易引起数据改变,视图改变的状况

例子

1
2
3
4
5
6
7
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

解决

  1. 使用Vue.set或者$set
1
2
3
4
5
6
// Vue.set的实例,$set和set是一样的使用
// 参数1为要设置的数组items
// 参数2为设置的索引
// 参数3为新添加的值
// 这样子新添加的newValue即为响应式数据了
Vue.set(vm.items, indexOfItem, newValue)
  1. 使用Vue当中重写的几个数组的方法,这样子操作就不会影响数据响应式效果
1
2
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
  1. Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:**(大白话就是Vue重写了这些方法,通过这些方法修改的数组也会触发视图的更新)**
1
2
3
4
5
6
7
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
  1. 也可以替换数组来达到修改后依旧是响应式效果
1
2
3
4
5
6
7
//example1.items 本身为一个响应式数组
//这里进行了替换操作
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

//filter()、concat() 和 slice()等返回新数组的操作都可以~

官网对于替换数组的解释:你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

Vue组件通信的六种方式

Props通信

  • 主要用于父子间的通信,不仅仅是普通的子,也可以是孙子之类的之间的通信),
  • 为了方便,这里就不区分什么孙子,曾孙子了,统一以父亲和儿子来说
props通信主要有
  • 传递非函数数据——-适用于父亲向儿子传递数据
    • 如果是普通数据-则是父亲想给儿子东东
    • 如果是传递的是函数,则是儿子想传递某些东东给父亲

例子1: 父亲向儿子传递普通数据

父亲(向儿子传递’title’,告诉儿子显示这个’title’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父亲
<template>
<div>
<h1>props通信</h1>
<!-- 传递title属性给儿子,告诉儿子title信息 -->
<Son title="标题设置为动感超人"></Son>
</div>
</template>

<script>
import Son from "./son.vue"
export default {
name: 'Father',
components:{
Son
}
}
</script>

儿子(负责显示父亲传递过来的’title’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//儿子
<template>
<div>
<h1>{{title}}</h1>
</div>
</template>

<script>
export default {
name: 'Son',
//儿子接收父亲传递过来的props
// 父亲刚刚传递了一个 title,这里就添加一个title
props:["title"]
}
</script>

效果图

props-子向父传递非函数数据

例子2 : 传递函数数据——儿子想告诉父亲什么东西(也就是想传递数据给父亲)

父亲(传递一个函数 ‘getInfo’ 给儿子)

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
//父亲
<template>
<div>
<h1>props通信</h1>
<!-- 传递getInfo属性给儿子,告诉儿子老爸想要你的信息 -->
<Son :getInfo="returnInfo"></Son>
</div>
</template>

<script>
import Son from "./son.vue";
export default {
name: "Father",
components: {
Son,
},
methods: {
//获取儿子的信息
returnInfo(info) {
console.log("儿子传递给我的信息是", info);
},
},
};
</script>

儿子(接收父亲传递过来的函数’getInfo’,并调用这个函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//儿子
<template>
<div>
<button @click="tellFather">单击这个按钮告诉爸爸信息</button>
<!-- 或者简单点 -->
<button @click="getInfo('儿子很好,爸爸放心1')">简单调用写法</button>
</div>
</template>

<script>
export default {
name: 'Son',
//儿子接收父亲传递过来的props
// 父亲刚刚传递了一个 getInfo,这里就添加一个getInfo
props:["getInfo"],
methods:{
//告诉爸爸
tellFather(){
this.getInfo("儿子很好,爸爸放心2");
}
}
}
</script>

效果图(单击’’这个按钮告诉爸爸信息’’,儿子就会告诉父亲’儿子很好,爸爸放心’)

props-父亲向儿子传递函数数据

props的类型
  • 数组

    • props:[“title” , “name” , “say”];
    • 儿子接收父亲传递过来的数据,都是写在数组里面的,并且是以字符串形式组成的数组(如上面二个案例就是用数组接收传递的)
  • 对象(普通key,value)

    • 用于指明props的值类型

    • 例子如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    props: {
    //指明title只能是字符串类型
    title: String,
    //指明likes只能是数值类型
    likes: Number,
    //指明isPublished只能是布尔值类型
    isPublished: Boolean,
    //指明commentIds只能是数组类型
    commentIds: Array,
    //指明author只能是对象类型
    author: Object,
    //指明callback只能是函数类型
    callback: Function,
    //指明contactsPromise只能是Promise类型
    contactsPromise: Promise // or any other constructor
    }
  • 对象里面又有配置对象

    • 配置对象参数

      • type:(指明props的值的类型)

      • required:(布尔值,代表是否必填,和default冲突)

      • default:(默认值参数)

      • validator:(验证参数)

        验证不通过报错

  • 配置对象代码示例

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
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
props通信变量注意点
  • prop的大小写
    • 调用组件时Prop使用驼峰式,props选项声明中需要全部小写
    • props中命名采用驼峰式,在调用组件的标签中使用短横线分割命名方式命名属性
  • 官网说明@地址

也就是说,会把传入的props转化为小写,比如如下例子-采用了驼峰命名方式

  • 这里使用了驼峰命名clickAmount传递了属性
  • 那么vue会自动将其全部转化为小写(其实不管你有没有驼峰命名,vue都会将其转化为小写~),然后接受的时候如果使用相同的名字去接收,会接收失败!!!!!!!!!!!!!!!
  • 所以不能驼峰,如果确实想,就需要使用kebab-case (短横线分隔命名) 命名去传递参数了,然后接收的时候就需要使用驼峰接收了
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
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
</head>
<body>
<div id="box">
<my-component
message='理想是人生的太阳'
v-bind:msg="msg"
myTitle="诗与远方"
// 这里使用了驼峰命名
clickAmount="1000"
my-name="自定义属性">
</my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
//接收的时候就需要全部改为小写,因为
props:['message','msg','mytitle','myName','clickamount'],
template:`<div>
<p>{{msg}}</p>
<p>{{message}}</p>
<p>{{mytitle}}</p>
<p>{{myName}}</p>
<p>{{clickamount}}</p>
</div>
`,
})
var vm = new Vue({
el:'#box',
data:{
msg:'父组件数据'
}
});
</script>
</body>
</html>

  • 使用kebab-case (短横线分隔命名) 命名传递,并使用驼峰命名接收
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
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
</head>
<body>
<div id="box">
<my-component
message='理想是人生的太阳'
v-bind:msg="msg"
myTitle="诗与远方"
// kebab-case (短横线分隔命名) 命名传递
click-amount="1000"
my-name="自定义属性">
</my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
// 驼峰命名接收 clickAmount
props:['message','msg','mytitle','myName','clickAmount'],
template:`<div>
<p>{{msg}}</p>
<p>{{message}}</p>
<p>{{mytitle}}</p>
<p>{{myName}}</p>
<p>{{clickAmount}}</p>
</div>
`,
})
var vm = new Vue({
el:'#box',
data:{
msg:'父组件数据'
}
});
</script>
</body>
</html>

自定义事件通信

知道自定义事件和系统事件(原生事件)的区别
  • 自定义事件(事件类型自己定义,回调函数自己定义)
  • 系统事件(原生事件) (事件类型系统定义,回调函数自己定义)
绑定和触发自定义事件
  • 关键用法是$on$emit

    • $on(参数1,参数2)
      • 参数1为绑定的事件名称(用于后面$emit触发)
      • 参数2为绑定的回调函数
    • $on经常绑定在获取数据的一方(订阅者)

    • $emit(参数1,参数2)
      • 参数1为要触发的事件名称(和$on的第一个参数要对得上)
      • 参数2为给事件名称对应的回调函数传递的参数值
    • $emit经常绑定在发送数据的一方(发布者)
  • 注意,如果$emit需要传递多个参数,要使用对象,比如说$emit(xxxx,{参数1:value,参数2,value2})

自定义事件复杂写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//父亲
<Son ref="erzi"></Son>
var vm = new Vue({
mounted(){
//注意,第一个complex为自己定义的,用于后面$emit触发
//第二个complex为绑定的回调函数!
this.$refs.erzi.$on("complex",complex);
},
methods:{
complex(content){
console.log(content)
}
}
})

//儿子
<span>我是儿子</span>
//单击按钮,调用$emit触发自定义事件,并传递数据'爸爸,我很好'给父亲
<button @click="this.$emit('complex','爸爸,我很好')"></button>
自定义事件的简单写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//父亲
//第一个complex为自己定义的,用于后面$emit触发
//第二个complex为绑定的回调函数!
<Son @complex="complex"></Son>
var vm = new Vue({
methods:{
complex(content){
console.log(content)
}
}
})

//儿子
<span>我是儿子</span>
//单击按钮,调用$emit触发自定义事件,并传递数据'爸爸,我很好'给父亲
<button @click="this.$emit('complex','爸爸,我很好')"></button>
顺带一提
  • 组件配置中
    • data函数,methods的函数,watch的函数,computed中的函数,他们的this均是[VueComponent实例对象]
  • new Vue(options)配置中
    • data函数,methods函数,watch的函数,computed的函数,他们的this均是[Vue实例对象]

全局事件总线通信

选择一个对象作为全局事件总线通信任务的东东需要具备下面几个条件

  1. 要是一个对象
  2. 这个对象可以被所有组件所访问
  3. 这个对象可以调用$emit$on方法

所以选取vm作为全局事件总线通信任务人~

至于为什么是vm,可以看看Vue和VueComponent的关系

Vue和VueComponent的关系

Vue和VueComponent的关系

全局事件总线使用
  • 全局事件总线选取规则
    • 1.所有的组件均可以访问到
    • 2.被选取当做总线的具有$on,$emit方法
    • 3.需要是一个对象
  • 所以选择vm做为全局事件总线
  1. 在主入口文件的main.js当中添加beforeCreate对象,并在里面添加代码(注意绑定位置!)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //正确的示例
    new Vue({
    beforeCreate(){
    Vue.prototype.$bus = this;
    }
    })

    //错误的示例
    //注意
    //这个是不可以的!!!
    var vm = new Vue({
    ...
    })
    Vue.prototype.$bus = vm;
    因为new Vue的那一刻,其他组件也会创建!创建好后才会执行这一行代码
  2. 在A组件(负责接受B组件或其他组件的数据)配置对象mounted当中添加$on,回调函数留在A组件当中(这里以Index.vue组件为例)

    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
    //Index组件
    <template>
    <div>
    <One></One>
    <Two></Two>
    </div>
    </template>
    <script>
    import Two from "@/views/allBus/two.vue"
    import One from "@/views/allBus/one.vue"
    export default {
    name: "Index",
    components:{
    Two,
    One
    },
    mounted(){
    //绑定tellText事件,回调函数为tellText
    this.$bus.$on("tellText",this.tellText);
    },
    methods:{
    tellText(content){
    console.log('有人说:',content);
    }
    }
    };
    </script>
  3. 在B组件或其他组件(负责发送A组件数据)当中添加$emit(以One.vue 和 Two.vue组件为例)

    //One.vue组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <div style="border:1px solid red">
    <h1>我是One</h1>
    <button @click="saySome">我来说句话</button>
    </div>
    </template>

    <script>
    export default {
    name: 'One',
    methods:{
    saySome(){
    //触发全局事件总线上的tellText事件,并向回调函数传递'我是One,我说话完毕'
    this.$bus.$emit("tellText","我是One,我说话完毕")
    }
    }
    }
    </script>

    <style lang="less" scoped>

    </style>

    //Two.vue组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <div style="border:1px solid blue">
    <h1>我是Two</h1>
    <button @click="saySome">我来说句话</button>
    </div>
    </template>

    <script>
    export default {
    name: 'Two',
    methods:{
    saySome(){
    //触发全局事件总线上的tellText事件,并向回调函数传递'我是One,我说话完毕'
    this.$bus.$emit("tellText","我是Two,我说话完毕")
    }
    }
    }
    </script>

    <style lang="less" scoped>

    </style>

Vue.prototype.$bus = vm;为什么不能用,为什么一定要用beforeCreate钩子实现?(可能有误~)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//正确的示例
new Vue({
beforeCreate(){
Vue.prototype.$bus = this;
}
})

//错误的示例
//注意
//这个是不可以的!!!
var vm = new Vue({
...
})
Vue.prototype.$bus = vm;//下面就来解释为什么这样子不可以
首先了解下Vue与VueComponent的关系
  • 这位博主更加详细的介绍了下Vue组件化之VueComponent介绍
  • 组件都是通过一个叫VueComponent的构造函数创建的,并且这个VueComponent不是我们写的,而是Vue.extend函数生成的,并且每次生成的都是不一样的VueComponent的构造函数。
  • 每当我们使用组件标签时(比如说有一个School的自定义组件)使用VueComponent构造函数创建一个VueComponent对象,帮我们执行 new VueComponent(options)
  • this的指向
    • 在组件配置中:data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是VueComponent组件对象。
    • 在vue实例配置中:data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是vue对象。
原因解释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//正确的示例
new Vue({
beforeCreate(){
//在这里挂载到了Vue的原型上,后期通过Vue.extend函数生成的均可以获取到
Vue.prototype.$bus = this;
}
})

//错误的示例

var vm = new Vue({
...
})
Vue.prototype.$bus = vm;//执行到这里组件已经创建完成,没有用了

正确绑定总线流程图执行过程

正确绑定总线流程图执行过程

错误绑定总线流程图执行过程

错误绑定总线流程图执行过程

至于为什么控制台console.log(可以输出查看到)

因为控制台展开会再次去读取最新的代码,所以你可以控制台看到~

pubsub-js通信

前置准备
  • 首先要安装

    1
    npm install pubsub-js --save
  • 然后用到的地方都导入

    1
    import PubSub from "pubsub-js"
  • 当然,你也可以全局使用捆绑在原型上

    • 比如import PubSub from 'pubsub-js'; Vue.prototype.PubSub = PubSub;
  • 一句话说清楚添加订阅和发布订阅

    • 需要数据的成为订阅者
    • 传送数据的成为发布者
绑定事件的应用(可以立即为一颗定时炸弹)
  • 事件名
  • 事件的回调函数
  • 声明事件对象参数
  • 获取数据的一方
  • 对比其他
    • PubSub:订阅者
    • Vue: $on()
触发事件(可以理解为炸弹引爆器-告诉炸弹是否引爆)
  • 事件名
  • 传入事件对象
  • 提供数据的一方
  • 对比其他
    • 1.PubSub:发布
    • 2.Vue: $emit
添加订阅(类似于$on)
  • 语法格式

    1
    PubSub.subscribe("发布消息的名称",回调函数)
  • 简单理解为PubSub.subscribe("要订阅的公众号",回调函数)

注意:

  • 回调函数会传入二个参数
    • 参数1为: 发布消息的名称 (不管用不用,都必须接收并占位!否者收不到第二个参数)
    • 参数2为: 即为发布订阅传递过来的消息
  • 注意:
    • 回调函数当中的第一个参数不管用不用,都需要进行占位
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
//Two.vue
<template>
<div style="border: 1px solid blue">
<h1>Two-测试pubsub-js</h1>
{{ content }}
</div>
</template>

<script>
import PubSub from "pubsub-js";
export default {
name: "",
data() {
return {
content: "",
};
},
mounted() {
//添加订阅(类似于$on),消息名称为'tellMeSome',绑定的回调为'tell'
PubSub.subscribe("tellMeSome", this.tell);
},
methods: {
//回调函数,第一个参数必须要写,否者第二个传递过来的参数接收不到
tell(msg, content) {
console.log(msg); //输出tellMeSome
console.log("有人告诉了我", content);
this.content = content;
},
},
};
</script>


发布订阅(类似于$emit)
  • 语法格式

    1
    PubSub.publish("发布消息的名称",传递的参数)
  • 简单理解为PubSub.subscribe("公众号名称","传输给用户的文章")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//One.vue
<template>
<div style="border:1px solid red">
<h1>One-测试pubsub-js</h1>
<button @click="itellyou">单击我告诉Two</button>
</div>
</template>

<script>
import PubSub from "pubsub-js";
export default {
name: "One",
methods: {
itellyou() {
//发布订阅,并传入数据'Two你好,我是One,来测试pubsub的'
PubSub.publish("tellMeSome", "Two你好,我是One,来测试pubsub的");
},
},
};
</script>

<style lang="less" scoped>
</style>

效果

单击后显示文字,传递信息成功

单击后显示文字,传递信息成功

插槽

作用: 让父组件可以向子组件指定位置插入html结构,适用于父组件 到 子组件

适合在多个组件当中,有一部分内容相同,部分内容不同的情况下使用,如下面这种情况就很适合,3个组件都有按钮,但是按钮下方的内容是不同的

3个组件都有按钮,但是按钮下方的内容是不同的

默认插槽

关键: slot标签什么属性都没有的,就一个纯标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
父组件
<Son>
<!--使用默认插槽-->
<template>
我是html结构
</template>
</Son>
或者省略默认template
<Son>
<!--使用默认插槽-->
我是html结构
</Son>


子组件
<template>
<div>
<!--定义插槽-->
<slot>插槽默认内容(没有被使用的默认显示内容)</slot>
</div>
</template>
具名插槽

理解: slot是含有name属性的标签

关键: 父组件使用具名插槽是关键是添加属性slot="插槽名称(对应插槽slot的name属性值)" 或者 v-slot:插槽名称

  • 不过vue3不推荐使用slot属性去使用具名插槽,而是使用v-slot属性去使用具名插槽
  • 跟 v-on 和 v-bind 一样,v-slot 也有缩写。
    • v-slot: 替换为字符#
    • 例如 v-slot:header 可以被简写为 #header

父组件

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
<template>
<div>
<h3>我是父亲</h3>
<Son>

<!-- slot="xxx"使用具名插槽 -->
<template slot="one">
我会更换具名插槽的内容
</template>

或者 v-slot:one
<template v-slot:one>
我会更换具名插槽的内容
</template>
</Son>
</div>
</template>

<script>
import Son from "./Son.vue"
export default {
name: '',
components:{
Son
}
}
</script>

子组件

1
2
3
4
5
6
7
8
<template>
<div>
<!-- 具名插槽 -->
<slot name="one">
我是具名插槽的默认内容
</slot>
</div>
</template>
作用域插槽

理解:

  • slot是含有绑定数据的标签,并且父组件可以拿到子组件的数据(通过slot给父亲),传递数据的方式类似于props通信结构
  • 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
    • 像element-ui当的<el-table-column></el-table-column>就需要我们使用作用域插槽来决定结构

关键: 父组件使用具名插槽是关键是添加属性slot-scope="" 或者 slot=""

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h3>我是父亲</h3>
<Son>
<!-- 使用作用域插槽 -->
<!-- 传递过来的完整数据其实是一个对象,这里使用了解构赋值 -->
<!-- 接收从插槽当中传递过来的数据,并使用解构赋值接收 -->
<template slot-scope="{games}">
{{games}}
</template>
</Son>
</div>
</template>
<script>
import Son from "./Son.vue"
export default {
name: '',
components:{
Son
}
}
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<!-- 作用域插槽 -->
<slot :games="games"> 我是作用域插槽默认内容 </slot>
</div>
</template>
<script>
export default {
name: "",
data() {
return {
games: ["穿越火线", "英雄联盟", "王者荣耀", "战地之王"],
};
},
};
</script>

输出示例

作用域插槽

vuex(不使用模块化)

安装
  • 对于vue2: npm install vuex@3 --save

  • 对于vue3: npm install vuex --save

  • 查看vue版本

    • 打开你的package.json文件夹就可以看到

      查看vue是2.x还是3.x

  • 查看npm当中vuex的所有版本 npm view vuex versions

    查看vuex所有版本

使用
  1. 添加store文件夹并在里面建立index.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import Vuex from "vuex"
    import Vue from "vue"
    Vue.use(Vuex);
    const state = { ... }
    const mutations = { ... }
    const actions = { ... }
    const getters = { ... }
    //别忘记new Vuex.Store(配置对象)了!
    export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
    })
  2. 主入口文件main.js引入store文件夹的index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import store from "@/store"
    或者
    import store from "@/store/index.js"

    添加到vue的配置对象当中
    new Vue({
    ...
    store,
    ...
    })
  3. 其他组件可以通过this.$store.dispatch(actions的方法名,传递的参数)来进行调用并存储数据(如果需要传递多个参数,请封装成为对象后传递)

详细配置对象参数
  • new Vuex.Store(配置对象),配置对象当中的含义

    • state: 是一个包含多个属性(不包含方法)的对象,用来存储数据

    • mutations: 是一个包含多个方法的对象,用这些方法去操作state当中的对象(也就是操作数据)

      • 只接收普通的函数,不接受任何if ,for,异步函数
      • 里面的方法第一个参数state(名字可以随意),为当前配置对象当中state
      • 里面的方法第二个参数为通过commit方法传递过来的参数
    • actions:是一个包含多个方法的对象,这个对象里面的方法用于给其他组件去调用

      • 可以包含if ,for , 异步函数

      • 里面的方法传入的第一个为参数content(名字可以随意),即为当前的store对象

        输出查看第一个参数

      • 里面的方法传入的第二个参数为通过dispatch传递过来的参数

    • getters: (类似于computed),是一个包含多个方法的对象,通过计算返回数据

      • 里面的方法传入的第一个参数为state(参数名称随意),也就是当前state对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      var state = {...};
      var mutations = {...};
      var actions = {...};
      var getters = {
      sayHello(){

      },
      方法1:(state)=>{

      },
      方法2(state){

      },
      //方法2写法等同于
      //只不过看你是否用到了this
      //方法2:function(state){
      //
      //}
      }
使用示例1(不使用模块化)

单击按钮后vuex的name由”李白” 变成了”动感超人”

普通组件One.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<div>我是One</div>
<button @click="gaibian">单击我改变store当中名字</button>
</div>
</template>

<script>
export default {
name: "One",
methods:{
gaibian(){
//调用vuex当中的dispatch
this.$store.dispatch("changName","动感超人")
}
}
};
</script>

store文件夹当中的index.js

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
import Vuex from "vuex"
import Vue from "vue"
Vue.use(Vuex);

var state = {
name:"李白"
}
var mutations = {
//用于改变state当中的name
SET_NEW_NAME(state,newValue){
state.name = newValue;
}
}
//用户使用this.$store.dispatch()调用的正是actions当中的方法
var actions = {
//用于调用mutations当中的方法去改变name值
//使用解构赋值解构出commit
changName({commit},value){
commit("SET_NEW_NAME",value)
}
}
var getters = {

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

vuex(使用模块化)

  • 其他什么都不需要改变,只需要改变下store文件夹下的index.js(主入口文件)

  • 文件目录结构如下

    • store
      • shop(文件夹)
        • address1.js
        • address2.js
      • food(文件夹)
        • vegetable.js
        • meat.js
      • user(文件夹)
        • vipuser.js
        • user.js
      • index.js(主入口文件)
  • 以后的主入口文件index.js只需要写成下面就可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import Vuex from "vuex"
    import Vue from "vue"
    Vue.use(Vuex);
    // 模块用法

    //引入One.js模块
    import One from "./One.js";
    export default new Vuex.Store({
    //可以用自己的下面四大类,这里没有使用而已
    // state:{}
    // mutations:{},
    // actions:{},
    // getters:{},
    state:{
    name:"李白"
    },
    //使用模块化
    modules:{
    One
    }
    })
  • One.js模块内容

    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
    var state = {
    name:"李白"
    }
    var mutations = {
    //用于改变自己state当中的name
    SET_NEW_NAME(state,newValue){
    state.name = newValue;
    }
    }
    var actions = {
    //用于调用mutations当中的方法去改变name值
    changName({commit},value){
    commit("SET_NEW_NAME",value)
    }
    }
    var getters = {

    }
    //别忘记暴露出现
    export default {
    state,
    mutations,
    actions,
    getters
    }

模块化后this.$store.state内容如图

模块化后this.$store.state内容

模块化之前输出this.$store
  • 可以看到,state当中没有嵌套什么

模块化之前输出this.$store

模块化之后输出this.$store
  • 可以看到,state当中嵌套了另外一个对象

  • 可以看到,One.js暴露的内容当中的state部分成为了this.$store.state里面的对象并且One为key值,value为One.js当中的state对象的值

模块化之后输出this.$store

模块化需要注意的点
  • 默认情况下,模块内部的actionmutation (官网是这样子说的,但是我测试后发现getters也是注册在全局下的) 是注册在全局命名空间的(也就是会放在this.$store对应的actions,mutations,getters对象上)(所以如果不使用命名空间的话,不管有没有模块化,想调用里面的方法只需要this.$store.dispatch(actions当中的名称,传递的参数)即可调用)

  • 如果没有使用命名空间,那么外界想调用模块当中的actions里面的方法,都是可以直接调用的,比如One.js,已经使用了模块化,但是外面想要调用actions当中的方法依旧只需要this.$store.dispatch(actions当中的名称,传递的参数)就可以调用!但是如果想要获取One.js当中state里面的数据,就需要多调用一层,即:this.$store.One.属性名才可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    One.js并且使用了模块化

    //想获取One.js里面的age属性,模块化后要怎么获取?
    //其他组件调用 this.$store.state.One.age即可
    var state = {
    name:"李白",
    age:2000
    }
    var mutations = {...}
    //想调用actions当中的'changeName'
    //在其他组件调用 this.$store.diaptch("changeName","李黑");
    //即可调用One.js当中actions里面的changName方法
    var actions = {
    changName(content,value){
    }
    }
    var getters = {...}
    export default {
    state,
    mutations,
    actions,
    getters
    }
使用命名空间(针对模块化)
  • 影响到actionsgetters方法使用,其他state,mutation不受影响(因为如果不使用命名空间,后面组件调用dispatch方法,只要模块的actions里面有这个被调用的函数名称,就会被调用,不管你有几个重复的,都会被触发)

  • 模块配置对象当中添加namespaced:true即可使用命名空间

  • One.js当中的内容

    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
    var state = {
    name:"李白",
    age:"100"
    }
    var mutations = {
    //用于改变state当中的name
    SET_NEW_NAME(state,newValue){
    state.name = newValue;
    }
    }
    var actions = {
    //用于调用mutations当中的方法去改变name值
    changName({commit},value){
    commit("SET_NEW_NAME",value)
    }
    }
    var getters = {
    allInfo(state){
    return state.name+state.age;
    },
    getOther(){
    return "啊啊";
    }
    }
    export default {
    //使用命名空间
    namespaced:true,
    state,
    mutations,
    actions,
    getters
    }
  • 未使用命名空间如何调用actions里面方法和getters里面的方法

    1
    2
    3
    4
    5
    6
    7
    //调用actions当中的changName,如果有多个changName,也会被调用
    this.$store.dispatch("changName");

    //调用getters当中的allInfo来获取值
    this.$store.getters.allInfo
    //放置在标签上
    <span>{{$store.getters.allInfo}}</span>

    没有使用命名空间之后输出this.$store从而查看getters和actions等其他

  • 使用命名空间后如何调用actions里面方法和getters里面的方法

    1
    2
    3
    4
    5
    6
    7
    //只会调用One.js文件下的actions里面的changName方法
    this.$store.dispatch("One/changName");

    //调用getters当中的allInfo来获取值
    this.$store.getters['One/allInfo']
    //放置在标签上
    <span>{{this.$store.getters['One/allInfo']}}</span>

使用命名空间之后输出this.$store从而查看getters和actions等其他

前缀名字会和modules的key对应

前缀名字会和modules的key对应

如果key改为了OneE

改为了OneE

那么命名空间当中的名字也会改变

  前缀名字会和modules的key对应

vuex使用mapState和mapGetters

  • mapState放在哪里?

    • 放在data肯定不行,因为data一般存放已经定义好的数据,props中,也不行,props一般接收通过标签属性来传递的,所以mapState放在computed当中
  • 为什么要mapState和其他的mapXXXXX(这里以不使用模块化为例)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    假设store文件夹下方的入口文件index.js含有下方内容

    import Vuex from "vuex"
    import Vue from "vue"
    Vue.use(Vuex);
    var state = {
    name:"李白",
    age:"100",
    address:"地球"
    }
    var mutations = {...}
    var actions = {...}
    var getters = {...}
    export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
    });
  • 那么我有一个组件叫One.vue,我想获取到vuex里面的数据放在我这里组件上使用,那么要怎么使用?

    • 那么平时,我们必须要写n多个 this.$store.state.xxxxx
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    One.vue
    那么我们就必须要写n多个 this.$store.state.xxxxx
    export default {
    name: "",
    mounted() {},
    components: {
    One,
    },
    computed: {
    name() {
    return this.$store.state.name;
    },
    age() {
    return this.$store.state.age;
    },
    address() {
    return this.$store.state.age;
    },
    },
    };
使用mapState(不使用模块化下)
  • 先引入 import {mapState} from "vuex"; 别忘记了花括号!

  • 使用(对象的写法)

    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
    import { mapGetters } from 'vuex'
    // 每次会传入一个参数,这个参数一般命名为state
    //state等同于this.$store.state的内容!!!
    var obb = mapState({

    自定义名称:function(state){
    console.log(state === this.$store.state);//输出为true
    return state.要获取的属性
    },
    //也可以简写
    自定义名称:state => state.要获取的属性
    })

    //返回值为一个对象

    比如下方代码
    var myObj = mapState({
    sex:function(state){
    return state.sex;
    }
    });
    //再简单点可以写为
    var myObj = mapState({
    sex: state => state.sex,
    });
    console.log(myObj);//{sex: ƒ}
  • 使用(数组的写法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import { mapState } from 'vuex'

    export default {
    // ...
    computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapState([
    'sex',
    'age',
    // ...
    ])
    }
    }
    //代码等同于
    computed: {
    ...mapState({
    sex:state=>state.sex,
    age:state=>state.age,
    })
    }
  • 有了上面的代码例子,这里做一个小结

    • mapState一般这样子使用,传入一个对象,里面对象的key为自定义名称,value值为一个函数(因为要通过函数当中的参数获取值),并且这个函数默认有一个参数(一般取名state),并且这个参数会等于this.$store.state
结合对象展开符-不使用模块化下(常用)

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

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
//简写
computed: {
...
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
sex: state => state.sex,
address: state => state.address
});
...
}

//复杂写
computed: {
...
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
sex: function(state){
return state.sex;
},
address: function(state){
return state.address;
}
});
...
}
使用mapState(使用模块化下)
  • 先引入 import {mapState} from "vuex"; 别忘记了花括号!

  • 和没有使用模块化相比,对象当中多了一层寻找,也就是对应的模块

    • 比如之前要寻找One.js当中的sex属性,我们只需要this.$store.sex

    • 而模块化后,我们要this.$store.One.sex

      • (注意,对应的模块的名称要和store文件夹下的index.js下书写的models对应)
      1
      2
      3
      4
      5
      6
      7
      8
      import One from "./One.js";
      export default new Vuex.Store({
      modules:{
      One
      }
      })
      //获取One.js当中的sex属性
      this.$store.One.sex
  • 使用

    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
    // 每次会传入一个参数,这个参数一般命名为state
    //state等同于this.$store.state的内容!!!!!!!
    var obb = mapState({
    自定义名称:function(state){
    console.log(state === this.$store.state);//输出为true
    return state.对应的模块.要获取的属性
    },
    //也可以简写
    自定义名称:state => state.对应的模块.要获取的属性
    })

    //返回值为一个对象

    比如下方代码
    var myObj = mapState({
    sex:function(state){
    //注意这里多了一层
    return state.One.sex;
    }
    });

    写简单点可以这样子写
    var myObj = mapState({
    sex:state => state.One.sex,
    });
    console.log(myObj);//{sex: ƒ}
结合对象展开符-使用模块化下(常用)
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
//简写
computed: {
...
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
sex: state => state.One.sex,
address: state => state.One.address
});
...
}

//复杂写
computed: {
...
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
sex:function(state){
return state.One.sex;
},
address:function(state){
return state.One.address;
}
});
...
}
使用mapGetters
先来了解下getters

getters和state也是一样,getters读取如果是模块化了就要多嵌套一层,其他的使用都和state一样,只不过state字换成了

  • 先来看看getters当中的使用(可以看到,getters当中的方法会默认传入参数state)
1
2
3
4
5
6
7
8
9
10
var state = {
name:"李白",
age:"100"
}
var getters = {
allInfo(state){
//返回值会作为allInfo的值,类似于computed的用法
return state.name+state.age;
}
}
  • 其他对象使用getters
1
2
3
4
//未模块化
this.$store.getters.allInfo
//模块化后
this.$store.getters.One.allInfo;//多了一层
使用mapGetters(用不用模块都是这个)
  • 数组写法

    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
    import { mapGetters } from 'vuex'
    export default {
    // ...
    computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
    // ...
    'allInfo',
    // ...
    ])
    }
    }

    !!!!!!!!!代码等同于(注意,只是理解上的效果,实际在这getters写是错误的是不行的!)!!!!!!!!!!!!!!
    // 注意,只是理解上的效果,实际在这getters写是错误的是不行的!
    //注意,只是理解上的效果,实际在这getters写是错误的是不行的!
    import { mapGetters } from 'vuex'
    export default {
    // ...
    computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters({
    allInfo:function(getters){
    return getters.allInfo
    }
    })
    }
    }
  • 对象写法(如果你想将一个 getter 属性另取一个名字,使用对象形式)

    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
    import { mapGetters } from 'vuex'
    export default {
    // ...
    computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters({
    allName:"allInfo"
    })
    }
    }
    !!!!!!!!!代码等同于(注意,只是理解上的效果,实际在这getters写是错误的是不行的!)!!!!!!!!!!!!!!
    // 注意,只是理解上的效果,实际在这getters写是错误的是不行的!
    //注意,只是理解上的效果,实际在这getters写是错误的是不行的!
    import { mapGetters } from 'vuex'
    export default {
    // ...
    computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters({
    allName:function(getters){
    return getters.allInfo
    }
    })
    }
    }
使用mapGetters如果在命名空间下要怎么使用mapGetters
  • 只可以使用mapGetters对象的形式!
1
2
3
4
5
6
7
8
9
10
11
12
其他组件Index.vue内容
//使用mapGetters调用命名空间下的getters
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
allInfo:"One/allInfo"
})
},
};
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
One.js内容
var state = {...}
var mutations = {...}
var actions = {...}
var getters = {
allInfo(state){
return state.name+state.age;
},
}
export default {
namespaced:true,
state,
mutations,
actions,
getters
}
vue-admin-template当中明明使用了mapGetters,为什么不是对象的写法,而依旧是数组的写法

正常来说,你肯定会以为自vue-admin-template的getters是写在对应的模块上的,比如One.jsgetters就写在One.js里面,Two.jsgetters就写在Two.js里面,那你就错了~

vue-admin-template模板作者是将**每一个模块的getters都写在了主入口文件src\index.js当中!**其他模板根本就没有getters这个配置对象!

每一个模块的getters都写在了主入口文件src\index.js当中!

1
2
3
4
5
6
7
8
9
10
11
12
13
//getters.js内容
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
//用户角色
roles: state => state.user.roles,
//最终用户可拥有的路由
currentAsyncRoutes: state => state.user.currentAsyncRoutes,
}
export default getters

所以为什么vue-admin-template依旧可以使用mapGetters的数组写法而使用对象写法,因为人家getters绑定在了主入口文件index.js里面,并且这个index.js是没有开启命名空间的~

1
2
3
4
5
6
7
vue-admin-template其他文件组件使用mapGetters的方法
computed: {
...mapGetters([
'sidebar',
'currentAsyncRoutes'
])
}

vuex其他一些要点

mutations不一定要大写,之前大写的因为这些变量是常量

为什么要通过三步actions-mutations-state来修改数据?

异步修改数据会导致数据不可控,不知道谁先完成,所以要同步

  • mutations是同步修改数据
  • actions是异步请求修改数据
  • 当是同步的时候,就可以直接修改通过mutations修改state当中的数据,就可以不通过actions来修改
mapState,mapActions,mapMutations位置说明
  • mapActions是映射vuex当中函数的,所以写在methods当中(这是本质)

  • 并且mapActions是一个函数,不然怎么映射对吧?mapActions函数需要传入一个对象,不然它怎么知道要映射什么呢?

    • mapActions(["xxxx"])
  • mapState 外部来的数据不可能定义在data当中,data当中只可以放自己定义的,也不是通过标签属性传递过来的,所以也不会出现在props当中,所以只能出现在watch当中

  • 现在watch当中指明在返回哪一个数据,因为你想想看,computed在vue是怎么用的,也是需要返回一个数据

路由组件和非路由组件

基本概念

  • 非路由组件:定义在其他组件当中的,就叫非路由组件(不一定要注册在App.vue当中)
  • **路由组件:**定义,注册在路由当中的,就叫路由组件(路由组件的创建和其他非路由组件一样,只不过最后注册的时候不一样)
  • 一般情况下
    • 非路由组件放在components文件夹当中
    • 路由组件放在views或者pages文件夹当中
  • 路由组件在切换的时候会被销毁,显示的时候重新创建(所以路由组件的生命周期会被重新执行)

使用

  • 安装
    • vue2: npm install vue-router@3 --save
    • vue3: npm install vue-router --save
  • src当中建立router文件夹,里面含有主入口文件index.js

index.js内容(src/router/index.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);
import Home from "@/src/views/Home"
export default new VueRouter({
routes:[
...
{
path:"/home",
component:Home
},
....
//重定向
//浏览器输入http://localhost/就会自动跳转到http://localhost/home
//{
// path:"/",
// redirect:"/home"
//}
]
})
  • main.js当中配置对象添加index.js暴露出的内容

main.js内容(项目主入口文件)

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'

import router from "@/router"

new Vue({
render: h => h(App),
router,
}).$mount('#app')

  • 别忘记了在App.vue添加<router-view></router-view>哦,不然你就算创建了路由没有设置这个也没有用~

App.vue内容

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'App',
}
</script>

Home.vue内容(src/views/Home.vue)

1
2
3
4
5
6
7
8
9
10
<template>
<div>我是Home</div>
</template>

<script>
export default {
name: 'Home',
}
</script>

地址栏输入xxxx/home就会显示

二级路由,三级路由,四级路由,,,n级路由

关键在于配置对象当中children:[],

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
routes:[
//一级路由
{
path:"/home",
component:Home,
//二级路由
children:[
{
// path:"/home/messsage"或者下面这一行
path:"message",//注意: 不需要再写 '/'
component:Message,
//三级路由
children:[
{
// path:"/home/messsage/info"或者下面这一行
path:"info",
component:Info
}
]
}
]
},
{
path:"/about",
component:About
},

//重定向,默认路由
{
path:"/",
redirect:"/home"
}
]

路由链接和路由切换标签(就是单击后切换路由组件和显示路由组件)

<router-link to="切换的路由路径">文本</router-link>用户点击的链接,经过vue编译后会生成一个超链接,会跳转到to内容

例子: 单击标签就会跳转到/food路由

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//路由表
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);

import Home from "@/views/Home";
import Food from "@/views/Food"
export default new VueRouter({
routes:[
{
path:"/home",
component:Home
},
{
path:"/",
redirect:"/home"
},
{
path:"/food",
// component:() => import("@/views/Food"),
component:Food
}
]
})


//Home.vue

<template>
<div>
<h1>我是Home</h1>
<router-link to="/food">用户点击的链接 </router-link>
</div>

</template>

//Food.vue

<template>
<div>
<h1>我是Food</h1>
</div>

</template>

<script>
export default {
name: 'Food',
}
</script>

声明式导航和编程式导航

  • 声明式导航: <router-link to="路径"></router-link>

  • 编程式导航: 运用this.$router.push 或者 this.$router.replace等实现跳转

  • 声明式导航占用资源多,编程式导航占用资源少,所以推荐使用编程式导航

  • this.$router代表路由器 - 我理解为统一管理路由的管理者

    • router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他(this.$router)包含了所有的路由和包含了许多关键的对象和属性

      输出this.$router

    • this.$router一些常见的方法

      1
      2
      3
      4
      5
      1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
      2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
      3. this.$router.back(): 请求(返回)上一个记录路由
      4. this.$router.go(-1): 请求(返回)上一个记录路由
      5. this.$router.go(1): 请求下一个记录路由
  • this.$route代表当前路由对象

    • $route是当前激活的路由,包含了当前激活的路由状态信息,它包含了当前URL解析得到的信息.

    • http://localhost/home下输出this.$route

      输出this.$route

使用编程式导航
  • 示例如下
1
2
3
4
5
6
7
8
9
10
<div>
<h1>我是Home</h1>
<button @click="$router.push('/food')">跳转到food</button>
</div>

//等同于
<div>
<h1>我是Home</h1>
<router-link to="/food">跳转到food</router-link>
</div>

路由传参props和query和params

  • 在不同路由当中,可以存储一定的参数信息,比如下面这张图,就可以看到/home这个路由对象里面有很多参数,我们这里关注queryparams

控制台输出this.$route,如下图

输出this.$route

query方式传参
  1. 直接在路径后面接上去,就如同发送ajax请求的时候后面携带的?aa=bb&cc=dd一样

    1
    2
    3
    4
    5
    //比如声明式导航跳转到/home的时候带了参数
    <router-link :to="`/home/message/msgdetail?id=${ms.id}&msg=${ms.msg}`">{{ms.msg}}</router-link>
    <router-link to="/food?name=茄子&weight=1">跳转到food并且携带query参数</router-link>
    //编程式导航
    <button @click="$router.push('/food?name=茄子&weight=1')">跳转到food并且携带query参</button>
  2. 使用对象的形式传递query参数

    1
    2
    3
    4
    //声明式导航
    <router-link :to="{path:'/food',query:{name:'茄子',weight:1}}">跳转到food并且携带query参数</router-link>
    //编程式导航
    <button @click="$router.push({path:'/food',query:{name:'茄子',weight:1}})">跳转到food并且携带query参数</button>

携带query参数后打印输出foodthis.$route

携带query参数后打印输出`food`的`this.$route`

params方式传参

重要,重要!很重要!

必须要在路由当中提前占位,否者就是跳转到别的路由了!!!!!!!!!!!!!!!!!!!!!!!!!!!

特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

path: “/food/:foodname/:foodweight”,如果这样子填写了,后面访问这个路由就必须要添加这二个参数,比如/food/vegetable/1

不可以少写也不可以漏写,否者访问不了参数

如果需要想写想不写,就可以在占位后面添加一个”?”,比如path: “/food/:foodname?/:foodweight?”就代表这二个参数可写可不写,输入/food/vegetable或者/food或者/food/vegetable/1都可以跳转到指定路由

路由占位和name配置

占位格式: :key值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Home from "@/views/Home";
import Food from "@/views/Food"
export default new VueRouter({
routes:[
...
{
//使用params参数必须要写name,不可以使用path了!!!!
name:"food",
//路由占位
//对应路由组件的this.$route当中params对象的key值
path:"/food/:foodname/:foodweight",
component:Food,
}
...
]
})
  1. 格式就类似于/food/茄子/1这种

    1
    2
    3
    4
    5
    // 声明式导航
    <router-link to="/food/qiezi/1/">跳转到food并且携带params参数</router-link>

    // 编程式导航 注意这里是通过name跳转,使用了params传参,就必须要用name进行跳转
    <button @click="$router.push({name:'food',params:{foodname:'qiezi',foodweight:1}})">跳转到food并且携带params参数</button>

携带params参数后打印输出`food`的`this.$route`

路由当中的meta

我们可以看到,输出this.$route的时候,除了可以看到queryparams这二个经常使用的对象,还可以看到meta这个对象

  • 作用如下

给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。经常用它来做登录校验

设置meta直接在注册路由组件的时候设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);

import Home from "@/views/Home";
import Food from "@/views/Food"
export default new VueRouter({
routes:[
{
name:"food",
path:"/food/:foodname/:foodweight",
component:Food,
//使用meta
meta:{
isShow:true
}
}
]
})

后面想要读取也很简单,就在当前路由组件输出this.$route.meta即可获取

路由props配置对象的配置

使用路由props配置对象,可以使得组件可以更加方便的接收参数

props配置对象传入一个对象(死数据)
  • 只能映射传递额外的静态的数据,一般不用

src\index.js配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);
import Food from "@/views/Food"
const router = new VueRouter({
routes: [
{
name: "food",
path:"/food",
component: Food,
//props为一个对象
props:{
name:"李白",
sex:"男",
age:'100'
}
}
]
});
export default router;

Food.vue接收

对象当中有什么字段,Food.vue就使用props配置对象接收什么字段

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<h1>我是Food</h1>
</div>
</template>
<script>
export default {
name: "Food",
//接收路由src\index.js为food配置的props参数
props:["name","sex","age"],
};
</script>
props配置对象传入一个布尔值
  • 把路径params参数映射为要显示的组件内属性

  • 布尔值为true,则把路由收到的所有params参数通过props传给对应组件

src\index.js配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);
import Food from "@/views/Food"
const router = new VueRouter({
routes: [
{
name: "food",
path: "/food/:foodname?/:foodweight?",
component: Food,
//props为一个布尔值,且为true
props:true
}
]
});
export default router;

Food.vue接收

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h1>我是Food</h1>
</div>
</template>
<script>
export default {
name: "Food",
//接收food当中的params参数
//字段必须要为路由当中占位的字段一样!!!
props:["foodname","foodweight"],
};
</script>
props配置对象传入一个函数
  • 自己手动映射params参数和query参数
  • 该函数返回的对象中的每一组key-value都会通过props传给对应组件

src\index.js配置

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
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter);
import Food from "@/views/Food"
const router = new VueRouter({
routes: [
{
name: "food",
path: "/food/:foodname?/:foodweight?",
component: Food,
//props为一个布尔值,且为true
//$route为默认传入的一个参数(名字可以随意,这里取名$route)
//之所以取这个是因为这个参数代表着当前路由组件
//this.$route === $route
props:($route){
return {
'蔬菜名称':$route.params.foodname,
'蔬菜重量':$route.params.foodweight,
'谁吃':$route.query.user
}
}
}
]
});
export default router;

Food.vue接收

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<h1>我是Food</h1>
</div>
</template>
<script>
export default {
name: "Food",
//字段必须要为路由当中返回的字段名称一样!!!
props:["蔬菜名称","蔬菜重量","谁吃"],
};
</script>

浏览器访问http://localhost:8080/#/food/vegetable/1005?user=李白

浏览器访问后

其他

其他

导航守卫

  • 全局前置守卫beforeEach:

    • 为什么是单词是Each,那是因为全局前置守卫需要对来往的每一个路由进行过滤,所以就是单词Each
    • 进入任意一个路由前,必须要经过全局前置守卫(进入学校,需要经过保安审批~)
  • 全局解析守卫beforeResolve

  • 全局后置钩子afterEach

  • 路由独享守卫beforeEnter

  • 组件内的守卫(不经常使用)

    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave
全局前置守卫

要使用全局前置守卫,我们就需要使用到由VueRouter通过new生成的实例化对象,所以需要改改store\index.js,其他不用动

1
2
3
4
5
6
7
8
9
10
11
store\index.js
//本来是
//export default new VueRouter({...});

//需要改为
const router = new VueRouter({...});
//全局前置守卫
router.beforeEach((to,from,next)=>{
...
})
export default router;

beforeEach回调参数

  • to: 是要去的某一个路由对象信息(目的地)
  • **from:**是来自的哪里的信息(起始点)
  • next控制跳转

beforEach例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
router.beforeEach((to, from, next) => {
//从sessionStorage获取token
let token = sessionStorage.getItem('token');
//如果目的地址不为'/login'并且token不存在
if (to.path != '/login' && !token) {
//跳转到'/login'
next({
path: '/login'
})
}
//目的地址为'/login'并且token存在
else {
//如果目的地址为'/login'并且token存在
if (to.path == '/login' && token) {
//跳转到'/dashboard'
next('/dashboard')
}
//目的地址不为为'/login'并且token存在
else {
next()
}
}
})
路由独享守卫

前面的是全局前置守卫(也就是大老板router),这个是路由独享守卫,也就是属性每一个路由组件的,在进入路由组件之前进行判断是否符合条件,符合条件才让你走

注意:

beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects是不会触发的,它们只有在 从一个不同的 路由导航时,才会被触发

基本使用

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
const router = new VueRouter({
routes: [
{
path: "/home/:id?",
component: Home
},
{
path: "/",
redirect: "/home"
},
{
name: "food",
path: "/food/:foodname?/:foodweight?",
component: Food,
meta: {
isShow: true
},
//从http://localhost:8080/#/home?q=libai到http://localhost:8080/#/food
//会自动帮我们分离参数到对应的路由对象当中,比如这个携带了query,就帮我们把参数写到了query对象里面,并且to.path依旧为/home
//从http://localhost:8080/#/home/100到http://localhost:8080/#/food
//params依旧也会分离参数到params对象里面,不过to.path不会改变,依旧为/home/100
beforeEnter:(to,from,next)=>{
if(from.path === '/home'){
next();//放行
}
else{
next("/home");
}
}
}
]
});
注意点

query参数携带在路径后面不会影响到to.path或者from.path

params参数携带在路径后面会影响到to.path或者from.path

1
2
3
4
http://localhost:8080/#/home?q=libai到http://localhost:8080/#/food
会自动帮我们分离参数到对应的路由对象当中,比如这个携带了query,就帮我们把参数写到了query对象里面,并且to.path依旧为/home
http://localhost:8080/#/home/100到http://localhost:8080/#/food
params依旧也会分离参数到params对象里面,不过to.path不会改变,依旧为/home/100

其他

路由组件和非路由组件的最大区别

生命周期钩子

  • 生命周期,就是组件从创建到销毁期间会自动执行的函数

  • 钩子函数

    • 在生命周期的过程中我们又很多特殊的时间段,我们希望在这些特殊的时间段对vue做一些事情,所以出现了钩子函数
    • 钩子函数就是作者在设计vue的时候,在vue从初始化到销毁这段时间内的特定时间段给我们一些定义函数的权利
    • 如果我们定义了钩子函数,就会执行,不定义就不会执行
  • 这个大佬绘制的图

大佬绘制的图

大佬说的太好了,我自己总结下自己~

  • data,methods,计算属性,event/watch事件回调created的时候就已经完成了初始化,但是dom树没有完成

  • 然后到了beforeMount 这里的dom树并不是真实的dom,而是虚拟的(所以你获取节点返回的是undefined)

    • 比如this.$refs.a ;//返回undefined
  • 然后到了mounted这里的dom已经生成,可以获取到dom节点了,可以对dom进行操作了

    • mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick
    • 发送请求获取数据在mounted生命周期当中发送,具体可以看看这篇文章
    • 理解可能有误,删除-至于为什么需要在mounted当中发送,而不再created发送,这个博主总结的很好,created的时候,虽然数据和事件依旧完成了初始化操作,但是假如因为网络延迟发送的数据直到mounted生命周期完成后(也就是DOM生成后)才获取到,那么有什么意义呢(相当于我建房子的时候你没给我砖块,我建完了你才给我砖块去填充,没意义啊)
    • 到底在哪里发送请求获取数据看了很多,说应该放在created当中,因为此时dom还没有渲染完成,这个时候发送数据就可以获取到数据用于构建dom,而说应该放mounted的说放在created当中会造成数据没有放回造成白屏,然后created的人又反驳说请求是异步的,怎么会白屏…还有人说created会产生分支,而mounted不会

    分支

    1
    2
    3
    4
    5
    6
    7
    //放在created
    created => API请求 => 获取数据 => 组件重新渲染

    => mounted => 组件首次渲染
    //放在mounted
    created => mounted => 组件首次渲染 => API请求 => 获取到数据 => 组件重新渲染

再次学习总结

  • 我们知道,vue2的生命周期全部如下,我们重点关注前四个
1
2
3
4
5
6
7
8
9
beforeCreate 创建前
created 创建后
beforeMounted 挂载前
mounted 挂载后

beforeUpdate 更新前
updated 更新后
beforeDestroy 销毁前
destroy 销毁后
beforeCreate 创建前
  • 刚执行new的操作,其他什么都没有做
created 创建后
  • 此时属性方法都绑定在了实例身上,但是依旧获取不到DOM
beforeMount 挂载前
  • 数据还没有进行替换操作
    • 也就是{{ title }} 没有被替换为data当中的数据
  • 虚拟DOM存在了(但是注意,这是虚拟的DOM,所以不可以对节点进行操作)
    • 所以你获取节点返回的是undefined
mounted 挂载后
  • 数据已经完成了替换操作
    • 也就是{{title }} 被替换为data当中的数据了
  • 虚拟DOM被替换为了真实的DOM(可以对节点进行DOM操作了)
    • 所以你获取节点返回的是节点信息了

vue自定义指令

自定义全局指令

  • Vue.directive("指令名称",回调函数)
  • 指令名称只能小写
  • 回调函数包含二个参数
    • 参数1: 为绑定指令的节点
    • 参数2 :为绑定指令的节点的相关信息

示例,实现小写转化为大小

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义指令</title>
</head>
<body>
<div id="app">
<p v-text="msg"></p>
<p v-upper="msg"></p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
<script>
//自定义全局指令
Vue.directive("upper", function (el, bindings) {
el.textContent = bindings.value.toUpperCase();
});
new Vue({
el: "#app",
data: {
msg: "i love you~ zhao li ying~",
},
});
</script>
</body>
</html>

如图,输出回调函数的二个参数

1
2
3
Vue.directive("upper",function(el,bindings){
console.log(el,bindings);
})

自定义局部指令

  • 和全局指令不同的是在配置对象当中设置directives即可
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义指令</title>
</head>
<body>
<div id="app">
<p v-text="msg"></p>
<p v-upper="msg"></p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
msg: "i love you~ zhao li ying~",
},
// 自定义局部指令
directives:{
upper(el,bindings){
el.textContent = bindings.value.toUpperCase();
}
}
});
</script>
</body>
</html>

vue自定义过滤器

示例效果图 上面为过滤前,下面为过滤后

自定义全局过滤器

  • 通过Vue.filter("自定义名称",回调函数)
  • 自定义名称可以驼峰也可以全小写
  • 回调函数传入一个参数为过滤器前的值,通过return返回一个新值完成过滤
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
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 原始格式 -->
<p>{{timeNow}}</p>
<!-- 经过过滤器 -->
<p>{{timeNow | timeFormat}}</p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js"></script>
<script>

//定义全局过滤器
Vue.filter("timeFormat",function(value){
//传入毫秒数对其格式化后再返回
return moment(value).format("YYYY-MM-DD hh:mm:ss");
});

//vue当中的过滤器可以理解成是为了让数据进一步的计算,得到最终的结果
new Vue({
el:'#app',
data:{
timeNow:Date.now()//当前时间 1970年1月1日0时分0秒到现在的毫秒数
},

})

</script>
</body>
</html>

自定义局部过滤器

  • 注意别漏掉了s
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 原始格式 -->
<p>{{timeNow}}</p>
<!-- 经过过滤器 -->
<p>{{timeNow | timeFormat}}</p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js"></script>
<script>
//vue当中的过滤器可以理解成是为了让数据进一步的计算,得到最终的结果
new Vue({
el:'#app',
data:{
timeNow:Date.now()//当前时间 1970年1月1日0时分0秒到现在的毫秒数
},
filters:{
timeFormat(value){
return moment(value).format("YYYY-MM-DD hh:mm:ss");
}
}
})

</script>
</body>
</html>

mixin混入的基本使用

  • @官方API:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

  • 说通俗点就是混入就是将别人的东东变为自己的东东,这里的混入只说一些基本的使用

  • 先来看示例吧

mixin/test.js文件的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const mixins = {
data(){
return {
sex:"男",
age:"18",
}
},
methods:{
sayHello(){
console.log("你好,世界");
}
}
};
export default mixins;

App.vue

使用mixin/test.js的混入

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
<template>
<div>
<div>姓名:{{name}}</div>
<div>年龄:{{sex}}</div>
<div>性别:{{age}}</div>
<button @click="sayHello">单击我 - 说hello</button>
<button @click="sayThankyou">单击我 - 说谢谢</button>
</div>
</template>

<script>
// 引入混入
import myMixin from "@/mixin/test";
export default {
name: "",
//使用混入
mixins:[myMixin],
data() {
return {
name:"李白",
}
},
methods:{
sayThankyou(){
console.log("谢谢你");
}
}

};
</script>

功能测试是否正常,可以看到,可以正常显示和调用函数

  • 当然了,还有很多情况,比如说混入的时候,当mixin/test.js里面的数据或者方法和App.vue当中的数据或者方法冲突的时候要怎么解决之类的,具体看官网吧~ @官网

其他一些问题

v.for为什么要用key

简单点说就是可以在后期dom发生变化的时候准确识别元素并更新,具体可以看看这位博主写的

Vue.component第一个参数命名规则

  • 说通俗点就是,使用Vue.componet有多个单词,注册和使用都使用短横线!!!

  • 组件命名方式:驼峰式,短横线

  • 注册组件使用驼峰式命名时,使用的时候必须要使用短横线对驼峰进行分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<shop-center></shop-center>
<!-- <shopCenter></shopCenter> 错误的 -->
<div>
Vue.component("shopCenter",{
template:`
<div>
<div>
<slot>
<img src="https://www.dreamlove.top/img/favicon.png"/>
</slot>
</div>
<div>
<slot name="detail">
<h1>手机名称</h1>
<h2 style="color:gray">手机价格</h2>
</slot>
</div>
</div>
`,

});
  • 注册组件使用短横命名时,使用的时候必须要使用短横线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<shop-center></shop-center>
<!-- <shopCenter></shopCenter> 错误的 -->
</div>
Vue.component("shop-center",{
template:`
<div>
<div>
<slot>
<img src="https://www.dreamlove.top/img/favicon.png"/>
</slot>
</div>
<div>
<slot name="detail">
<h1>手机名称</h1>
<h2 style="color:gray">手机价格</h2>
</slot>
</div>
</div>
`,

});
  • 驼峰和短横
1
2
3
4
5
6
7
8
//驼峰式
Vue.component('myComponent',{
template:'<h4>组件命名</h4>'
});
//短横线
Vue.component('my-component1',{
template:'<h4>组件命名</h4>'
});

方法,和computed,watch的区别

  • 方法
    • 页面每次重新渲染都会重新执行,性能消耗大,除非不希望有缓存的时候用
  • computed
    • 是计算属性,依赖其他属性来计算值,并且computed的值具有缓存,只有当依赖的其他属性发生变化的时候才会重新计算
  • watch
    • 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作
  • 总结
    • 除非不希望有数据缓存,否者都不会用方法
    • 一般来说需要依赖别的数据来动态获取值的时候可以使用computed
    • 对于监听来说,需要做异步操作或开销比较大的时候可以用watch
  • 为什么computed不能异步操作?而watch却可以?
1
2
3
4
5
6
7
8
9
10
export default {
computed:{
totalMoney(){
setTimeout(()=>{
//这个return的是内部的,不是外部的,所以不能异步操作
return 100;
},1000)
}
}
}

watch却可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
watch:{
arr:{
handle(){
setTimeout(()=>{
//更新数据
this.totalMoney = 1000;
},1000)
},
deel:true,
immediate:true,
}
}
}

数据代理

  • 什么是数据代理
    • 就是通过vm(vue的实例化对象来直接操作data当中的数据),而不用通过this._data来修改数据
    • 比如data当中有一个name属性,那么如果没有数据代理的话,我们需要操作就需要通过this._data.name属性来获取和修改
    • 微信小程序就是没有实现数据代理,每次我们读取里面的data,都需要通过this.data来进行读取
    • 这样子就简化了我们的操作
  • 数据代理的原理是什么
    • 原理就是通过defineProperty来给vm(vue实例对象)身上添加data当中所有的属性
    • 并设置gettersetter
    • 当获取的时候就调用getter方法
    • 当修改的时候就调用setter方法
1
2
3
4
5
6
7
8
9
10
data() {
return {
name: "李白",
age: "18",
sex: "男",
};
},
created() {
console.log(this);
},

输出查看this,可以看到,this里面有name,age,sex这些属性