本文主要用于记录Vue2.0的组件化编程功能和vux管理数据,参考视频【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】

实现应用中局部功能代码和资源的集合

代码复用

组件可以产生嵌套

模块与组件 模块化与组件化

  1. 向外提供特定功能的js程序 一般就是一个js文件
  2. 为什么 ? 因为js文件很多很复杂
  3. 作用 复用js 简化js的编写 提高js运行效率
  1. 用来实现局部(特定)功能效果的代码集合(html/css/js/image…)
  2. 为什么? 一个界面的功能很复杂
  3. 作用 复用编码 简化项目编码 提高运行效率

当应用中的js都已模块化来编写 那这个应用就是一个模块化应用

当应用中的功能都是以多组件的形式来编写 那么这个应用就是一个组件化应用

非单文件组件

vue提供非单文件组件的形式 使得一个文件包含有n个组件来编写

为什么组件中要使用data:{return{xxx}}?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
// const data = {
// x:1,
// y:2
// }
//每次都返回一个全新的对象 互不干扰
const data = function(){
return {
x:1,
y:2
}
}
const x1 = data();
const x2 = data();
</script>

组件先声明 后注册 注册分为全局和局部注册

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
52
53
54

<div id="root">
{{intro}}
<hr>
<jojo></jojo>
<hr>
<dio></dio>
</div>
<script>
// 定义组件 用Vue.extend 注意不要写extends 接收记得data函数式且return一个对象
const jojo = Vue.extend({
// 定义模板标签
template:`
<div>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
`,
data:function(){
return {
name:'jotaro',
age:18
}
}
})
// 全局注册一个组件

const dio = Vue.extend({
template:`
<div>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
`,
data:function(){
return {
name:'dio',
age:9999
}
}
})
// 注意不用加s 组件的名字逗号加组件在哪
Vue.component('dio',dio);
new Vue({
el:'#root',
data:{
intro:'vue组件使用'
},
// 注册组件 components记得加s 此为局部注册
components:{
jojo:jojo
}
})
</script>

需要注意的是单词的拼写是否正确
在声明的时候 extend不用加s
在局部注册的时候 使用components 因为可能会局部注册到很多个逐渐
在全局注册的时候 Vue.component不用加s 因为全局一次只能注册一个

一图流总结

常见问题:

组件的嵌套

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<div id="root">

</div>
<script>
const jotaro = Vue.extend({
template:`
<div>
<h1>{{name}}-----{{age}}</h1>
</div>
`,
data:function(){
return {
name:'jotaro',
age:18
}
}
})

const jojohome = Vue.extend({
template:`
<div>
<h1>{{name}}-----{{age}}</h1>
<jotaro></jotaro>
</div>
`,
data:function(){
return {
name:'jostar',
age:18
}
},
components:{
jotaro
}
})
const dio = Vue.extend({
template:`
<div>
<h1>{{name}}-----{{age}}</h1>
</div>
`,
data:function(){
return {
name:'dio',
age:999
}
}
})
const huangmu = Vue.extend({
template:`
<div>
<jojohome></jojohome>
<dio></dio>
</div>
`,
components:{
jojohome,
dio
}
})
new Vue({
template:`
<huangmu></huangmu>
`,
el:'#root',
components:{
huangmu
}
})
</script>

Vuecomponent构造函数

讲解一下vue源码中这个构造函数的作用

总结一图流:

可以直接通过vm查看下面所带领的全部vc 而vc也可以查看自己带领的vc

单文件组件的使用

vue提供了.vue格式的文件作为单文件组件的使用

先在vscode里面安装vtuer插件

创建vue文件 使用<v能快捷创建

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>

</template>

<script>
export default {

}
</script>

<style>

</style>

一个vue文件里面包含三个标签
<template></template>模板标签 用于书写html代码
<script></script>脚本标签 用于书写js代码
<style></style>样式标签 用于书写css代码

需要注意的是

  1. 注释问题 (如果没有脚手架写注释默认报错)
  2. 命名问题 建议首字母大写 或者my-school
  3. js代码暴露问题 参见之前的es6学习 有三种暴露方式

JOJO.vue的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<h1>{{ name }}----{{ age }}</h1>
</div>
</template>

<script>
export default {
name: "JOJO",
data() {
return {
name: "jotaro",
age: 18,
};
}
};
</script>

<style>
</style>

App.vue的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<JOJO></JOJO>
</div>
</template>

<script>
import JOJO from "./JOJO";
export default {
name:'App',
components:{
JOJO
}
};
</script>

<style>
</style>

main.js的代码
1
2
3
4
5
6
7
8
import App from './App'


new Vue({
el:'#root',
template:`<div><App></App></div>`,
components:{App}
})

index.html代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">

</div>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>

需要注意的是

  1. 上面的东西最好按顺序写
  2. 记得暴露 和 导入
  3. 在vue的使用中 一般root写在main.js里面
  4. 最后是html文件中引入js的顺序 一定要先把vuejs引入再引入自己的mainjs

新增注意 记得组件中的data返回的是函数

创建vue脚手架

vue提供脚手架cli为程序员使用,相当于webpack配置好了,方便编译vue代码

首先安装vue cli

1
npm install -g @vue/cli

之后在创建的目录打开输入cmd
1
vue create xxx

然后启动项目 注意是serve
1
npm run serve

分析vue脚手架

一个脚手架项目构建好之后有以下几个文件

自上而下说明
nodexxx这个是node配置文件
favicon.icoico文件
index.html首页文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 避免路径产生的错误以后统一用baseurl这个是vue提供的取代./的方法 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 这个是webpack里面默认去packagejson里面找文件名作为网站标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 如果你的浏览器不支持js 将执行下面的代码 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

main.js入口文件 不用在页面中引入都可以找到该文件 vue已经配置好了
1
2
3
4
5
6
7
8
9
10
11
// 入口文件

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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


gitignoregit文件
babelxxxes6转es5文件

render函数

上面讲到了在入口文件mainjs里面有一句代码叫做render: h => h(App),,下面来讲述它的作用以及相关的vue文件

首先在入口文件中必须写这个render函数作为模板template的替代 否则会报错
其原因是 入口文件中引入的并非完整的vue

而是一个精简版的 去掉模板解析器的vue
这么做是为了精简代码体积,到最后打包的时候减少代码量 虽然只有一点点

再讲讲这个render函数的本质:
它本来是个函数 可以写成

1
2
3
render(createElement){
return createElement('h1','hello');
}

这样就相当于生成了一个h1标签内容是hello
由于它用不到this以及参数的名字可以改变 最后就可以简写成上面的形式
1
render: h => h('h1','hello')

最后说明一下 这个render函数只需要在mainjs里面写 其他需要用到模板的地方可以在vue文件里面写 vue配置了单独的模板解析器 直接使用提供的template标签即可

总结一图流:

ref指令

vue提供ref指令来获取dom元素和组件,方便后续的组件通信

在使用的时候是,xxx是自己定义的名字
ref="xxx"
获取ref的时候是 注意这里是dollar符和refs
this.$refs.xxx
如果不加后续的xxx 则显示全部ref标志的元素或组件

上代码

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>
<h1 ref="title">这是app组件 现在要学习ref</h1>
<button @click="showTitle">点我切换标题</button>
<JOJO ref="jojo"></JOJO>
</div>
</template>

<script>
import JOJO from "./components/JOJO.vue";
export default {
name:'App',
components:{
JOJO
},
methods:{
showTitle(){
let title = this.$refs.title;
title.innerText = '开始拉';
console.log(this.$refs);
}
}
};
</script>

<style>
</style>

ref在标记dom元素的时候 还可以标记组件
像上面标记组件之后输出的结果如下

由此可见 可以完全获取整个组件 而document.getElementById这个原生的方法 只能获取组件的根标签内容 像下面这样

一图流:

配置项props

需求是当组件复用的时候 希望更改组件的内置属性而不是重新写个组件 就要使用到props配置项

第一种方法 用数组的方式 prop:['xxx','xxxx'] 这是简单声明接收
jojovue文件 使用props配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>{{msg}}</h1>
<h1>{{ name }}----{{ age }}</h1>
</div>
</template>

<script>
export default {
name: "JOJO",
data() {
return {
msg:'现在开始学习props配置项'
};
},
props:['name','age']
};
</script>

<style>
</style>

appvue文件 在组件标签中传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<JOJO name="jotaro" age="18"></JOJO>
<hr>
</div>
</template>

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

<style>
</style>

注意 上面的方法中 传进去的age是字符串的形式 并不是number的形式
如果不加双引号 那么再保存的时候他也会自动给你加上双引号
所以如果有需求更改age的值的时候会出现问题
那么解决这个问题的主要方式就是使用v-bind:即动态绑定 进行js代码的运算

并且最好用props的另外一种方法进行类型的限制
1
2
3
4
props:{
name:String,
age:Number
}

这样一来如果接收的东西不对 它就会报错

以及更高级别的写法 接收的同时对数据进行类型限制+默认值的指定+必要性的限制
1
2
3
4
5
6
7
8
9
10
props:{
name:{
typeof:String, //name的类型
required:true //name是否必须传 为true必传
},
age:{
typeof:Number,
default:99 //如果不传 默认值99
}
}

一般来说 requireddefault只能用一个

最后注意一下 传进来的prop是不能修改的 最好不要修改
如果业务上面必须要修改可以参考以下的方法

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
<template>
<div>
<h1>{{ msg }}</h1>
<h1>{{ name }}----{{ myage + 1 }}</h1>
<button @click="changeAge">点我修改age</button>
</div>
</template>

<script>
export default {
name: "JOJO",
data() {
return {
msg: "现在开始学习props配置项",
myage: this.age //由于必须要修改age 所以这里加一个myage接收age 因为优先级prop大于data 所以会在这里接收到age
};
},
props: {
name: {
typeof: String, //name的类型
required: true, //name是否必须传 为true必传
},
age: {
typeof: Number,
default: 99, //如果不传
},
},
methods: {
changeAge() {
this.myage++;
},
},
};
</script>

<style>
</style>

总结一图流:

mixins混入

vue提供mixins混入的配置项 来使用公共的方法

需求 jojo和dio在h1标签中表态 但荒木偷偷混入 点击h1之后就会变成made in heaven
huangmujs

1
2
3
4
5
6
7
export const huangmu = {
methods: {
showMsg(){
this.msg = 'made in heaven!!!!!!!!!'
}
},
}

jojovue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h1 @click="showMsg">{{ msg }}</h1>

</div>
</template>

<script>
import {huangmu} from '../huangmu'
export default {
name: "JOJO",
data() {
return {
msg: "欧拉欧拉欧拉",
};
},
mixins:[huangmu]
};
</script>

<style>
</style>

diovue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h1 @click="showMsg">{{ msg }}</h1>

</div>
</template>

<script>
import {huangmu} from '../huangmu'
export default {
name: "DIO",
data() {
return {
msg: "我不做人啦jojo",
};
},
mixins:[huangmu]
};
</script>

<style>
</style>

appvue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<JOJO></JOJO>
<hr>
<DIO></DIO>
</div>
</template>

<script>
import JOJO from "./components/JOJO.vue";
import DIO from "./components/DIO.vue";

export default {
name:'App',
components:{
JOJO,
DIO
},

};
</script>

<style>
</style>

注意混入的js要暴露 引入的时候也要注意
最后是mixins方法是一个数组的配置,多个的时候要用逗号隔开

混合的原则是 如果你自己有的 就用自己的 如果没有 就用混合的
但如果是生命周期钩子 则都要的 来者不拒
总结一图流

vue插件

vue提供插件功能来增强页面的功能

总结一图流

scoped样式

vue提供scoped关键字 在style标签中使用 可以将该style标签中的样式独立使用于当前vue文件 但最好不要使用在appvue中

语法如下

1
2
<style scoped>
</style>

图例

生成一个随机数data

总结一图流:

由于webpack目前版本已经到5以上 vue求稳定使用它的4.46版本,所以在安装less-loader的时候,会报错,要使用6的版本才能安装

1
npm i less-loader@6

然后在样式中引入
1
2
3
4
5
<style lang="less">
.jojo{
color: pink;
}
</style>

最后补充一个查插件版本的指令
1
npm view xxx versions

todolist案例

学会编写todolist案例来帮助理解vue开发

  1. 把全部静态资源定义好
  2. 分析需要做哪个组件 这里选择了 mylist和myitem
    mylist相关代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <ul class="todo-main">
    <MyItem v-for="todo in todoObj" :key="todo.id" :todo="todo"/>
    </ul>
    </template>

    <script>
    import MyItem from './MyItem'

    export default {
    name:'MyList',
    components:{MyItem},
    data() {
    return {
    todoObj:[
    {id:'001',title:"我不做人了jojo",done:true},
    {id:'002',title:"扎瓦鲁多",done:false},
    ]
    }
    },
    }
    </script>

    这里要注意v-for的使用 注意传进去的内容 以及v-bind绑定 最后传给item的是单个对象
    myitem相关代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <li>
    <label>
    <input type="checkbox" :checked="todo.done"/>
    <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" style="display:none">删除</button>
    </li>
    </template>

    <script>
    export default {
    name:'MyItem',
    props:['todo']
    }
    </script>

    这里需要注意 item需要接收一个对象用于渲染

  3. 开始编写myheader
    myheader中 需要使用的是 输入框 以及输入之后敲回车的执行
    这里就会出现一个问题 因为不知道如何和子组件进行通信 而且也没有相关的list标签可以用于传递参数 所以这里要用到父子组件的通信
    解决前者的问题 需要在app中定义一个带参函数addTodo 在header中prop该函数 当keyupenter之后 header就会往该函数中传入相应的对象
    myheadervue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="addTodos"/>
    </div>
    </template>

    <script>
    import {nanoid} from 'nanoid'
    export default {
    name:'MyHeader',
    props:['addTodo'],
    methods: {
    addTodos(e){
    this.addTodo({id:nanoid(),title:e.target.value,done:false})
    }
    },
    }
    </script>

    部分appvue

    1
    2
    3
    4
    5
    6
    7
    		<MyHeader :addTodo="addTodo"/>
    ------------------------------------------------------------------------
    methods: {
    addTodo(x){
    console.log(x);
    }
    },

    那么后者的问题 就需要将list中的todoObj放到app中 这样做的好处是 全局共享这个数据 这样一来 app也可以像上面给header传东西一样 给list传这个obj 最后交给item去渲染
    mylist代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <template>
    <ul class="todo-main">
    <MyItem v-for="todo in todoObj" :key="todo.id" :todo="todo"/>
    </ul>
    </template>

    <script>
    import MyItem from './MyItem'

    export default {
    name:'MyList',
    components:{MyItem},
    props:['todoObj']
    }
    </script>

    部分app代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    				<MyList :todoObj="todoObj"/>
    ------------------------------------------------------------------------
    data() {
    return {
    todoObj:[
    {id:'001',title:"我不做人了jojo",done:true},
    {id:'002',title:"扎瓦鲁多",done:false},
    ]
    }
    },
    methods: {
    addTodo(obj){
    this.todoObj.unshift(obj);
    }
    },
  4. 开始进行勾选功能的实现
    流程是 勾选myitem组件,改变它的checkbox,由于数据在app中,所以要告诉app它改变了checkbox 然后todoObj中的todoObj.done进行取反操作 又由于app和myitem属于爷孙关系 不能直接通信 需要借助mylist。
    Q: 如何改变?
    A: 使用click事件 点击之后执行一个函数
    Q: 函数应该传什么告诉它改变了呢?
    A: 应该传id 传这种唯一的标识符到app app才能根据id进行遍历,找到对应项的done进行取反
    Q: 这个整体的过程大概是怎么样呢?
    A: app组件将数据共享出来 并定义一个函数改变done值,然后把这个函数传递给mylist mylist用prop接收后 再传递给myitem myitem的checkbox改变的时候 在调用这个函数 就实现了数据的改变
    关键在于app里的data数据是全局共享的
    部分appvue代码 定义checkTodo 遍历todoObj 如果找到相同的id 则done取反
    1
    2
    3
    4
    5
    6
    7
        <MyList :todoObj="todoObj" :checkTodo="checkTodo"/>
    ------------------------------------------------------------------------
    checkTodo(id) {
    this.todoObj.forEach((todo) => {
    if (todo.id === id) todo.done = !todo.done;
    });
    },

本地存储和会话存储

本篇将涉及两个js原生代码localStoragesessionStorage的讲解,前者会存储在用户硬盘上 后者在网页关闭后消失

注意所有的数据将在控制台的application下面的localStorage里面查看

代码如下

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
<h1>本地存储</h1>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="clearData()">点我清空数据</button>
<script>
let person = {
name:'josefu',
age:70
}
function saveData(){
localStorage.setItem('name1','jostar');
localStorage.setItem('name2','jotaro');
// 如果是数字 自动转化为字符串
localStorage.setItem('age',18);
// 如果是对象 需要用JSON.stringify()方法将对象转化为json字符串
localStorage.setItem('person',JSON.stringify(person));
}
function readData(){
console.log(localStorage.getItem('name1'));
console.log(localStorage.getItem('name2'));
console.log(localStorage.getItem('age'));
// 如果想读取到的格式是对象 就要把json转为对象
// console.log(JSON.parse(localStorage.getItem('person')));
const result = JSON.parse(localStorage.getItem('person'));
// 但这里转化的age是由于是对象 所以还是保留原来的格式
console.log(result.age)
// 如果不存在 返回null
const result2 = JSON.parse(localStorage.getItem('person2'));
console.log(result2)

}
function deleteData(){
localStorage.removeItem('name1');
localStorage.removeItem('name2');
}
function clearData(){
localStorage.clear()
}
</script>

代码如下:只是替换了关键字

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
<h1>会话存储</h1>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="clearData()">点我清空数据</button>
<script>
let person = {
name:'josefu',
age:70
}
function saveData(){
sessionStorage.setItem('name1','jostar');
sessionStorage.setItem('name2','jotaro');
// 如果是数字 自动转化为字符串
sessionStorage.setItem('age',18);
// 如果是对象 需要用JSON.stringify()方法将对象转化为json字符串
sessionStorage.setItem('person',JSON.stringify(person));
}
function readData(){
console.log(sessionStorage.getItem('name1'));
console.log(sessionStorage.getItem('name2'));
console.log(sessionStorage.getItem('age'));
// 如果想读取到的格式是对象 就要把json转为对象
// console.log(JSON.parse(sessionStorage.getItem('person')));
const result = JSON.parse(sessionStorage.getItem('person'));
// 但这里转化的age是由于是对象 所以还是保留原来的格式
console.log(result.age)
// 如果不存在 返回null
const result2 = JSON.parse(sessionStorage.getItem('person2'));
console.log(result2)

}
function deleteData(){
sessionStorage.removeItem('name1');
sessionStorage.removeItem('name2');
}
function clearData(){
sessionStorage.clear()
}
</script>

总结一图流:

todolist自然也会用到本地存储 思路就是监视app里面todoObj的改变 并且在一开始初始化todoObj的时候读取本地数据
踩坑的有两点
一是初始化的时候如果本地没有值会报错(因为myfooter里面的初始化要计算数组长度,如果是null是没有长度的)
解决方法是或运算

1
2
3
4
5
data() {
return {
todoObj: JSON.parse(localStorage.getItem("todoObj")) || [],
};
},

二是在勾选完之后刷新会发现选项并没有勾选成功 因为监视属性中只监视了第一层 没有发现数组内部的变化
解决方法 使用深度监视
1
2
3
4
5
6
7
8
9
watch: {
// 这里勾选的时候会不显示改变 因为不是深度监视 无法知道数组内容的改变
todoObj: {
deep: true,
handler(value) {
localStorage.setItem("todoObj", JSON.stringify(value));
},
},
},

组件自定义事件_绑定

组件之间的通信前面学过 需要父组件定义函数 发送到子组件 子组件props之后调用
但现在用两种旧的方法来进行组件的通信 它们分别是直接自定义事件和挂载后自定义事件

首先要了解作用机制
自定义事件能够通信是因为 父组件在子组件身上定义了自定义事件,在子组件中通过某种方式触发该自定义事件 实现函数的调用 以此来通信
流程是 父组件先定义自定义事件 然后写该事件的函数 之后子组件用自己的需要的方式触发该自定义事件 并 传参
app部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1 ref="title">这是app组件 现在要学习自定义事件</h1>
<JOJO @customEvent="getJOJO"></JOJO>
</div>
</template>

<script>
import JOJO from "./components/JOJO.vue";
export default {
name:'App',
components:{
JOJO
},
methods:{
getJOJO(name){
console.log('JOJO的名字是',name);
}
}
};
</script>

jojovue
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>
<h1>{{ name }}----{{ age }}</h1>
<button @click="sendJOJO">点我发送jojoname</button>
</div>
</template>

<script>
export default {
name: "JOJO",
data() {
return {
name: "jotaro",
age: 18,
};
},
methods: {
sendJOJO(){
this.$emit('customEvent',this.name);
}
},
};
</script>

第二种写法 是通过挂载自定义事件来写
流程是用ref定义子组件 然后书写回调函数 之后在mounted里面通过refs拿到子组件 之后通过$on指定在该自定义事件触发的时候调用上面的回调函数

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
<template>
<div>
<h1 ref="title">这是app组件 现在要学习自定义事件</h1>
<!-- <JOJO @customEvent="getJOJO"></JOJO> -->
<JOJO ref="jojo"></JOJO>

</div>
</template>

<script>
import JOJO from "./components/JOJO.vue";
export default {
name:'App',
components:{
JOJO
},
methods:{
getJOJO(name){
console.log('JOJO的名字是',name);
}
},
mounted() {
this.$refs.jojo.$on('customEvent',this.getJOJO);
},
};
</script>

这种方法的好处是灵活性强,可以通过延时函数设定什么时候触发这个自定义事件等

然后是一些细节上或者优化的问题
假如想要自定义事件只触发一次 前面用v-on的方法就是直接在事件后面加once
@customEvent.once="getJOJO"
后者ref的就是

1
2
3
mounted() {
this.$refs.jojo.$once('customEvent',this.getJOJO);
},

以及如果想要在自定义事件中传入多个参数 则最好使用对象发送或者es6的剩余参数rest接收
1
2
3
4
5
methods:{
getJOJO(name,...params){
console.log('JOJO的名字是',name,params);
}
},

解绑事件

上面绑定事件之后如果要想进行事件的解绑操作 需要用到$off

见代码

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
<template>
<div>
<h1>{{ name }}----{{ age }}</h1>
<button @click="sendJOJO">点我发送jojoname</button>
<button @click="unbind">点我解绑jojoname</button>

</div>
</template>

<script>
export default {
name: "JOJO",
data() {
return {
name: "jotaro",
age: 18,
};
},
methods: {
sendJOJO(){
this.$emit('customEvent',this.name);
this.$emit('customEvent2');
},
unbind(){
// 只解绑一个事件
// this.$off('customEvent');
// 解绑多个事件 数组的方式
// this.$off(['customEvent','customEvent2']);
// 或者直接啥都不写
this.$off();
}
},
};
</script>

自定义事件踩坑总结

主要内容有

  1. app组件获取自定义事件的结果渲染到页面
  2. 组件使用原生事件

综上所述 有两种方法自定义 一种是v-on 另外一种是ref
前者代码 思路是在data中定义然后方法调用的时候赋值给定义的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data() {
return {
jojoName:''
}
},
components:{
JOJO
},
methods:{
getJOJO(name){
console.log('JOJO的名字是',name);
this.jojoName = name;
}
},

后者
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
<template>
<div>
<h1 ref="title">这是app组件 jojo的名字是{{jojoName}}</h1>
<!-- <JOJO @customEvent="getJOJO"></JOJO> -->
<JOJO ref="jojo"></JOJO>


</div>
</template>

<script>
import JOJO from "./components/JOJO.vue";
export default {
name:'App',
data() {
return {
jojoName:''
}
},
components:{
JOJO
},
methods:{
getJOJO(name){
console.log('JOJO的名字是',name);
this.jojoName = name;
}
},
mounted() {
this.$refs.jojo.$on('customEvent',this.getJOJO);

},

但是后者如果直接将回调的具体内容写在on后面的话 会出现问题
1
2
3
4
5
6
7
mounted() {
// 此处的function中的this指代的是触发该事件的对象 就是子组件 然鹅子组件里面没有jojoName所以不行
this.$refs.jojo.$on("customEvent", function (name) {
console.log("JOJO的名字是", name);
this.jojoName = name;
});
},

需要写成箭头函数的形式
1
2
3
4
5
//箭头函数的this指向前面一个 即app的vm
this.$refs.jojo.$on("customEvent", (name)=> {
console.log("JOJO的名字是", name);
this.jojoName = name;
});

组件正常来说 如果使用像@click="show"这样的方法 那么他会判断该click是个自定义事件,要用自定义事件的方式去定义才行
那么组件如何使用原生事件呢?
只需要加个.native
1
<JOJO ref="jojo" @click.native="show"></JOJO>

总结一图流:

全局事件总线

全局事件总线用于处理子组件和子组件之间的通信。

为了处理子组件之间的通信 要有一个中间人 当它被定义之后 如果有组件往里面挂载自定义事件用于得到信息,那么另外的组件就可以触发该事件发送信息 从此进行组件间的通信

那么这个中间人有三点需求

  1. 它可以被所有组件知道
  2. 它必须有$on$emit$off

首先第一点只要它在vue原型中出现就可以

1
2
Vue.prototype.x = {a:1,b:2};
//这样子组件直接打印是有数据的

但是要满足第二点的时候 会发现它的对象不合法 因为对象上面并没有$on$emit$off这几个方法
解决这个问题 也很简单 要知道在vm或者vc上面才有这三个方法的存在 所以出发点就是这两个对象
对于vm来说 因为要创建vm实例对象就要接收 那么接收之后再赋值给原型上的x已经晚了 因为这个时候其他组件已经渲染完了
1
2
3
4
5
6
//这是错误做法
const vm = new Vue({
el:'#root',
render:h=>h(App)
})
Vue.prototype.x = vm;

所以只能想到用vc 要用vc的话 之前学到要使用const Demo = Vue.extend({}); 但是不能直接传给x 因为没有新建 之前的做法是直接使用这个标签<Demo/> 所以这里只能自己新建
1
2
3
4
5
6
7
8
const Demo = Vue.extend({});
const d = new Demo();
Vue.prototype.x = d;

new Vue({
el:'#root',
render:h=>h(App)
})

这样做之后就满足要求了
其他组件的通信写法
jojovue 发送端
1
2
3
4
5
methods: {
sendHello() {
this.x.$emit("hello", this.msg);
},
},

diovue接收端
1
2
3
4
5
mounted() {
this.x.$on('hello',(data)=>{
console.log('ko no DIO da!!!',data);
})
},

那么项目中肯定不是以这样的复杂的形式去定义x的 这里引出全局事件总线的写法
首先vm里面有个钩子叫做beforeCreate()这个是在模板解析之前做的操作 所以在这里面创建所谓的x是最好的 vue告诉我们这里的x应该叫做$bus 可以理解未公共汽车 都可以把东西往里面放
1
2
3
4
5
6
7
8
new Vue({
el:'#root',
render:h=>h(App),
beforeCreate(){
// 安装全局事件总线
Vue.prototype.$bus = this
}
})

注意 最好在组件销毁之前解绑bus身上的相关事件 利用beforeDestroy()
1
2
3
beforeDestroy() {
this.$bus.$off('hello');
},

总结一图流:

消息订阅与发布

消息的订阅与发布是另外一种组件间通信的方法 一般引入pubsub.js来调用pubsub对象里面的方法

  1. 安装pubsub
    1
    npm i pubsub-js
  2. 导入pubsub
    1
    import pubsub from 'pubsub-js'
  3. 基本使用
    消息的订阅 使用到的是subscribe 传入两个参数 一个是订阅的名字 一个是进来的数据
    且注意因为每次订阅都会生成不同的id 所以最后取消订阅是要传入一个id的 这里用pubId来演示
    1
    2
    3
    4
    5
    6
    7
    8
    mounted() {
    this.pubId = pubsub.subscribe('hello',function(msgName,data){
    console.log('系内jojo',msgName,data);
    })
    },
    beforeDestroy() {
    pubsub.unsubscribe(this.pubId);
    },
    消息的发布 使用到的是publish
    1
    2
    3
    4
    5
    methods: {
    sendHello() {
    pubsub.publish('hello','欧拉欧拉欧拉欧拉!!!!!!!');
    },
    },
    直接在function里面输出this的话结果是undefined 这时候需要用到箭头函数来将this设置为vc
    1
    2
    3
    4
    5
    6
    mounted() {
    this.pubId = pubsub.subscribe("hello", (msgName, data) => {
    console.log(this);
    console.log("系内jojo", msgName, data);
    });
    },
    特别注意一点 如果订阅的功能是写在methods里面的 那么需要methods里面的方法的第一个参数用占位符代替 因为它第一个要传一个msgName

    总结一图流:

todolist编辑

讲一下todolist编辑这个功能的实现 运用到了动态添加属性$set, 事件总线,事件对象$event 显示指令v-show

首先样式写好 编辑框和点击编辑之后出现的文本框
对于编辑框 需要的是1点击之后 出现文本框 2 出现文本框后 编辑框消失
对于文本框需要的是 1点击编辑框之后自动获取焦点 离开自动失去焦点并修改数据
所以先给编辑框绑定一个事件 叫isEdit 传入todo并添加响应式的edit 如果todo里面的edit为真 那么就显示文本框

1
2
3
4
5
6
7
8
9
10
11
12
      <span v-show="!todo.edit">{{ todo.title }}</span>
<input v-show="todo.edit" type="text" :value="todo.title" @blur="isBlur(todo,$event)"/>
<button v-show="!todo.edit" class="btn btn-danger" @click="isEdit(todo)">编辑</button>

------------------------------------------------------------------------
isEdit(todo) {
if (todo.edit !== undefined) {
todo.edit = true;
} else {
this.$set(todo, "edit", true);
}
},

然后文本框也需要编写一个isBlur函数 用于改变edit状态 来影响编辑框和title
由于失去焦点后要完成改变 需要在app里面编写update函数接收当前的todoid和title值
值得一提的是@blur="isBlur(todo,$event)"传入event找到e.target.value
(这里还没有写自动获取焦点)
1
2
3
4
5
6
7
8
9
10
    isBlur(todo,e){
todo.edit = false;
this.$bus.$emit("updateTodo", todo.id,e.target.value);
}
------------------------------------------------------------------------
updateTodo(id,title) {
this.todoObj.forEach((todo) => {
if (todo.id === id) todo.title = title;
});
},

关于自动获取焦点 由于函数里面的内容线执行完毕再去渲染dom 所以直接写focus是没有作用的
解决的办法有使用定时器和$nextTick 后者的原理是等dom渲染完后执行回调
1
2
3
4
5
6
setTimeout(() => {
this.$refs.edit.focus();
}, 200);
// this.$nextTick(function () {
// this.$refs.edit.focus();
// });

vue动画

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div>
<h1>现在学习vue动画</h1>
<button @click="showMsg">点我显示/隐藏</button>
<hr>
<h1 v-show="isShow" class="msg">我不做人啦</h1>
</div>
</template>

<script>
export default {
data() {
return {
isShow:false
}
},
methods: {
showMsg(){
this.isShow = !this.isShow;
}
},
}
</script>

<style>
@keyframes enter {
from{
transform:translateX(100%)
}
to{
transform: translateX(0);
}
}
.come{
animation: enter 1s linear;
}
.go{
animation: enter 1s reverse;
}
.msg{
background-color: pink;
}
</style>

利用vue提供的transistion标签
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
<template>
<div>
<h1>现在学习vue动画</h1>
<button @click="showMsg">点我显示/隐藏</button>
<hr>
<transition>
<h1 v-show="isShow" class="msg">我不做人啦</h1>
</transition>
</div>
</template>

<script>
export default {
data() {
return {
isShow:false
}
},
methods: {
showMsg(){
this.isShow = !this.isShow;
}
},
}
</script>

<style>
@keyframes enter {
from{
transform:translateX(100%)
}
to{
transform: translateX(0);
}
}
/* 来的时候 */
.v-enter-active{
animation: enter 1s linear;
}
/* 去的时候 */
.v-leave-active{
animation: enter 1s reverse;
}
.msg{
background-color: pink;
}
</style>

如果有多个需要用到不同动画效果的时候 要给transistion标签一个name值 并且在下方样式中的v改成name值 并且如果想要一上来就执行动画 可以使用appear指令
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
<template>
<div>
<h1>现在学习vue动画</h1>
<button @click="showMsg">点我显示/隐藏</button>
<hr>
<transition name="dio" appear>
<h1 v-show="isShow" class="msg">我不做人啦</h1>
</transition>
</div>
</template>

<script>
export default {
data() {
return {
isShow:true
}
},
methods: {
showMsg(){
this.isShow = !this.isShow;
}
},
}
</script>

<style>
@keyframes enter {
from{
transform:translateX(100%)
}
to{
transform: translateX(0);
}
}
/* 来的时候 */
.dio-enter-active{
animation: enter 1s linear;
}
/* 去的时候 */
.dio-leave-active{
animation: enter 1s reverse;
}
.msg{
background-color: pink;
}
</style>

总结一图流

配置代理(axios)

配置代理解决vue中发送请求的时候跨域的问题

安装axios

1
npm i axios

导入axios
1
import axios from 'axios'

实例代码
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>现在开始学习vue发送axios请求</h1>
<button @click="sendAxios">点我发送一个请求</button>
</div>
</template>

<script>
import axios from 'axios'
export default {
methods: {
sendAxios(){
axios.get('http://localhost:3000/timeout-server').then(
response=>{
console.log('请求成功了',response.data);
},
error=>{
console.log('请求失败了',error.message)
}
)
}
},
}
</script>


注意跨域问题
之前在ajax学过 跨域就是违背了同源策略 同源策略规定协议 主机 端口号一致
如果要解决这个问题 可以在nodejs里配置 也可以直接使用代理服务器

原理是前端网页访问代理服务器的时候 代理服务器的协议主机端口号是一致的 所以就可以请求 同时 代理服务器和保存结果的服务器直接又可以互相请求 就达成了效果
新建vue.config.js输入
1
2
3
4
5
6
7
8
9
10
module.exports = {
pages:{
index:{
entry:'src/main.js'
}
},
devServer:{
proxy:'http://localhost:3000'
}
}

注意 里面的proxy的地址就是代理服务器请求的有结果的服务器的地址
然后改变axios的请求地址如下:
1
2
3
4
5
6
7
8
9
10
11
12
methods: {
sendAxios(){
axios.get('http://localhost:8080/timeout-server').then(
response=>{
console.log('请求成功了',response.data);
},
error=>{
console.log('请求失败了',error.message)
}
)
}
},

注意 这里就是往代理服务器8080发送了请求 让该代理服务器从端口号3000的timeoutserver拿结果过来
这里还涉及到一个问题 代理服务器并不是全部的请求都发给实际服务器 如果代理服务器发现public目录下有该资源 就直接返回该资源 不会往服务器请求数据
并且它的缺陷就是配置完只能转发给一个服务器 不能给多个
下面讲解另外一种方式 转发给多个服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    devServer:{
proxy:{
'/jojo':{
target:'http://localhost:3000',
changeOrigin:true,
pathRewrite:{'^/jojo':''}
}
}
}
------------------------------------------------------------------------
//请求端的vue
methods: {
sendAxios(){
axios.get('http://localhost:8080/jojo/timeout-server').then(
response=>{
console.log('请求成功了',response.data);
},
error=>{
console.log('请求失败了',error.message)
}
)
}
},

原理是当请求的地址中第一项是以jojo为名字的话 代理服务器就开始向目标服务器请求数据
前面的'/jojo'指的是代理的服务器请求路径 他会匹配所有以此为路径的地址
target 是目标服务器地址
changeOrigin为true的话 指的是请求目标服务器的时候 伪装自己的host为相同的host
pathRewrite的话 当代理服务器向目标服务器请求数据的时候 地址其实是/jojo/timeout-server但目标服务器中并没有这一项 所以要使用这个方法 以键值对的形式找到所有'/jojo'并改写成空
总结一图流

插槽

slot插槽标签的功能主要是当组件需要传入单独的内容的时候,对该内容进行渲染。常用于组件复用。分为三类 默认插槽 具名插槽 作用域插槽

默认插槽 使用方法


适用场景 只有一个需要独立渲染的内容的时候

具名插槽 适用场景 需要多个slot来渲染数据的时候
使用方法


如果出现多个标签共享同样的slot的时候 如下

建议使用

v-slot可以用#号代替
并且注意该指令只能使用在组件的标签上

作用域插槽
使用场景 当需要组件里面的相同数据进行不同的渲染的时候使用到该方法
比如同样的列表数据 想展示成有序列表 无序列表 两种方式
使用方法


如果直接用插值语法输出scope的名字 会出现传递的数据
主要的概念是传给插槽的使用者

技巧

新的api是slot-scope功能和scope是一致的
总结一图流


Vuex

全局事件总线

vuex

新建一个store文件夹 下面放indexjs
写入如下 注意Store要大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex)

// 准备actions 用于响应组件中的动作
const actions = {

}
// 准备mutations 用于操作数据 state
const mutations = {}
// 准备state 用于存储数据
const state = {}

// 创建并暴露store
export default new vuex.Store({
actions,
mutations,
state
})


然后在mainjs里面导入 注意store后面的index不能省略

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import App from './App'
import store from './store/index'

new Vue({
el:'#root',
render:h=>h(App),
store,
beforeCreate(){
// 安装全局事件总线
Vue.prototype.$bus = this
}
})

注意这里在index里面写入
1
2
3
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex)

是因为cli脚手架它有个代码执行顺序的问题

所以不能在mainjs里面使用vuex再引入store

查看vuex的数据

还有一个案例再下一栏

加减法计算器
jisuanqivue

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
<template>
<div>
<h1>现在开始学习vuex 案例是加减</h1>
<h1>当前求和是:{{ $store.state.sum }}</h1>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>

<script>
export default {
data() {
return {
n: 1,
sum: 0,
};
},
methods: {
increment() {
this.$store.commit("INCREMENT", this.n);
},
decrement() {
this.$store.commit("DECREMENT", this.n);
},
incrementOdd() {
this.$store.dispatch("incrementOdd", this.n);
},
incrementWait() {
setTimeout(() => {
this.$store.dispatch("incrementWait", this.n);
}, 500);
},
},
mounted() {
console.log(this);
},
};
</script>


indexjs
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
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex)

// 准备actions 用于响应组件中的动作
const actions = {
//context 上下文 value 传过来的值
// increment(context, value) {
// context.commit('INCREMENT', value)
// },
// decrement(context, value) {
// context.commit('DECREMENT', value)
// },
incrementOdd(context, value) {
if (context.state.sum % 2 !== 0) {
context.commit('INCREMENT', value)
}
},
incrementWait(context, value) {
context.commit('INCREMENT', value)
}
}
// 准备mutations 用于操作数据 state
const mutations = {
INCREMENT(state, value) {
console.log('mutation里面的INCREMENT被调用了', state, value)
state.sum += value;
},
DECREMENT(state, value) {
state.sum -= value;
},
}
// 准备state 用于存储数据
const state = {
sum: 0
}

// 创建并暴露store
export default new vuex.Store({
actions,
mutations,
state
})

注意这里有比较细节的问题
整个流程是vc传递n值给action action做出判断之后传递给mutation加工 mutation加工之后将数据给到state state里面的值改变重新渲染vc
vc将n值传递给action这个part

  1. 因为已经use了vux这个插件 且已经注册了store 所以vc中会出现$store并且里面的state中有想要的sum值 可以插值语法直接获取
  2. vc如果先和action沟通 使用的是dispatch 并且第一个参数要和action里面的key对应
  3. 如果不需要action的业务逻辑判断 可以使用commit 直接发送给mutation进行加工 这里的加法和减法就是例子

action判断部分

  1. action在这里接收两个参数 一个是context 上下文对象 这个上下文对象依然也包含state 所以可以拿到sum进行数据过滤 另外一个参数是value 就是传递来的值
  2. action判断完之后需要传递两个参数 第一个和mutation里面的key相同 建议使用大写区分 第二个是value

mutation部分

  1. 加工数据 接收两个参数 一个是state 一个是value
  2. 没有发送

state部分

  1. 初始化参数

getters配置项

使用getters配置项可以想计算属性一样对state里面的数据进行操作

首先在store下面的indexjs里面定义getters

1
2
3
4
5
6
const getters = {
// 类似计算属性 传入的是state
bigSum(state) {
return state.sum * 10
}
}

然后追加该配置
1
2
3
4
5
6
7
// 创建并暴露store
export default new vuex.Store({
actions,
mutations,
state,
getters
})

最后在组件中使用 注意此时是getters里面的xx 不是state
1
<h1>当前求和放大十倍是{{ $store.getters.bigSum }}</h1>

总结一图流

mapState和mapGetters

主要是优化模板中插值语法$store.state.xxx的冗余写法 应用场景是需要用到vuex里面的数据并且不想写复杂的$store.state.xxx,用computed进行读取则可以用这种形式优化

首先需要import

1
import {mapState} from 'vuex'

原理就是函数生成函数 这里mapState会把传进来的对象根据key生成函数 并返回从state里面找到的value值 巧妙的一点是用了拓展运算符...将全部对象展开

在vue开发者工具中也可以知道这个它其实最后是隶属于computed的 但是vue把他独立出来显示

简写方法 数组写法

同理 mapGetters

mapMutations和mapActions

作用: 优化methods的写法 但需要导入和传参

首先导入

1
import {mapActions, mapMutations} from 'vuex'

在模板中传参
1
2
3
4
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>

在methods中编写 key是模板中的方法 value是给mutations或者actions传递的信息 注意要写成字符串
1
2
3
4
5
6
7
8
9
10
11
methods: {
//使用这种写法要注意模板里面的函数要传参 值要写成字符串
...mapMutations({
increment:'INCREMENT',
decrement:'DECREMENT'
}),
...mapActions({
incrementOdd:'incrementOdd',
incrementWait:'incrementWait'
})
},

常见错误 不传参

点击一下加之后是1 1之后点奇数加(奇数加没有传参) 就会默认将事件event传进去给actions
在actions中完成拼接 就是这个结果
也有数组写法 不过个人感觉不太适合

vux模块化+namespaced

利用vuex模块化可以更加方便的维护数据 并且利用namespaced属性 可以使得methods和computed添加方法更方便

模块化配置 将所有的actions mutations getters state全部配置在一个options里面

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
const countOptions = {
namespaced:true,
actions: {
incrementOdd(context, value) {
if (context.state.sum % 2 !== 0) {
context.commit('INCREMENT', value)
}
},
incrementWait(context, value) {
setTimeout(() => {
context.commit('INCREMENT', value)
}, 500);
}
},
mutations: {
INCREMENT(state, value) {
console.log('mutation里面的INCREMENT被调用了', state, value)
state.sum += value;
},
DECREMENT(state, value) {
state.sum -= value;
},
},
getters: {
bigSum(state) {
return state.sum * 10
}
},
state: {
sum: 0,
school: '进击的鬼灭学园',
name: '炭之狼'
}
}

创建并暴露store的适合要引入模块 有多少就引入多少
1
2
3
4
5
6
// 创建并暴露store
export default new vuex.Store({
modules:{
countOptions
}
})

添加namespace属性为true
然后重写一下mapstate 以及其他mapxxx方法
注意 如果不配置namespace的话 mapxxx方法是找不到对应的模块的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
computed: {
...mapState("countOptions", ["school", "name", "sum"]),
...mapGetters("countOptions", ["bigSum"])
},
methods: {
...mapMutations("countOptions", {
increment: "INCREMENT",
decrement: "DECREMENT",
}),
...mapActions("countOptions", {
incrementOdd: "incrementOdd",
incrementWait: "incrementWait",
}),
},

一图流:

技巧类

iconfont