Vue组件化编程+Vuex
本文主要用于记录Vue2.0的组件化编程功能和vux管理数据,参考视频【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】
实现应用中局部功能代码和资源的集合
代码复用
组件可以产生嵌套
模块与组件 模块化与组件化
- 向外提供特定功能的js程序 一般就是一个js文件
- 为什么 ? 因为js文件很多很复杂
- 作用 复用js 简化js的编写 提高js运行效率
- 用来实现局部(特定)功能效果的代码集合(html/css/js/image…)
- 为什么? 一个界面的功能很复杂
- 作用 复用编码 简化项目编码 提高运行效率
当应用中的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>
{{name}}</h1>
{{age}}</h1>
{{name}}</h1>
{{age}}</h1>
</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>
{{name}}-----{{age}}</h1>
{{name}}-----{{age}}</h1>
{{name}}-----{{age}}</h1>
</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代码
需要注意的是
- 注释问题 (如果没有脚手架写注释默认报错)
- 命名问题 建议首字母大写 或者
my-school
- 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
8import 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
<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>
需要注意的是
- 上面的东西最好按顺序写
- 记得暴露 和 导入
- 在vue的使用中 一般root写在
main.js
里面 - 最后是html文件中引入js的顺序 一定要先把vuejs引入再引入自己的mainjs
新增注意 记得组件中的data返回的是函数
创建vue脚手架
vue提供脚手架cli为程序员使用,相当于webpack配置好了,方便编译vue代码
首先安装vue cli1
npm install -g @vue/cli
之后在创建的目录打开输入cmd1
vue create xxx
然后启动项目 注意是serve
1
npm run serve
分析vue脚手架
一个脚手架项目构建好之后有以下几个文件
自上而下说明nodexxx
这个是node配置文件favicon.ico
ico文件index.html
首页文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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')gitignore
git文件babelxxx
es6转es5文件
render函数
上面讲到了在入口文件mainjs里面有一句代码叫做render: h => h(App),
,下面来讲述它的作用以及相关的vue文件
首先在入口文件中必须写这个render函数作为模板template的替代 否则会报错
其原因是 入口文件中引入的并非完整的vue
而是一个精简版的 去掉模板解析器的vue
这么做是为了精简代码体积,到最后打包的时候减少代码量 虽然只有一点点
再讲讲这个render函数的本质:
它本来是个函数 可以写成1
2
3render(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符和refsthis.$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
4props:{
name:String,
age:Number
}
这样一来如果接收的东西不对 它就会报错
以及更高级别的写法 接收的同时对数据进行类型限制+默认值的指定+必要性的限制1
2
3
4
5
6
7
8
9
10props:{
name:{
typeof:String, //name的类型
required:true //name是否必须传 为true必传
},
age:{
typeof:Number,
default:99 //如果不传 默认值99
}
}
一般来说 required
和default
只能用一个
最后注意一下 传进来的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
huangmujs1
2
3
4
5
6
7export const huangmu = {
methods: {
showMsg(){
this.msg = 'made in heaven!!!!!!!!!'
}
},
}
jojovue1
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>
diovue1
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>
appvue1
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开发
- 把全部静态资源定义好
分析需要做哪个组件 这里选择了 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需要接收一个对象用于渲染
开始编写myheader
myheader中 需要使用的是 输入框 以及输入之后敲回车的执行
这里就会出现一个问题 因为不知道如何和子组件进行通信 而且也没有相关的list标签可以用于传递参数 所以这里要用到父子组件的通信
解决前者的问题 需要在app中定义一个带参函数addTodo 在header中prop该函数 当keyupenter之后 header就会往该函数中传入相应的对象
myheadervue1
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);
}
},- 开始进行勾选功能的实现
流程是 勾选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原生代码localStorage
和sessionStorage
的讲解,前者会存储在用户硬盘上 后者在网页关闭后消失
注意所有的数据将在控制台的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
5data() {
return {
todoObj: JSON.parse(localStorage.getItem("todoObj")) || [],
};
},
二是在勾选完之后刷新会发现选项并没有勾选成功 因为监视属性中只监视了第一层 没有发现数组内部的变化
解决方法 使用深度监视1
2
3
4
5
6
7
8
9watch: {
// 这里勾选的时候会不显示改变 因为不是深度监视 无法知道数组内容的改变
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>
jojovue1
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
3mounted() {
this.$refs.jojo.$once('customEvent',this.getJOJO);
},
以及如果想要在自定义事件中传入多个参数 则最好使用对象发送或者es6的剩余参数rest接收1
2
3
4
5methods:{
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>
自定义事件踩坑总结
主要内容有
- app组件获取自定义事件的结果渲染到页面
- 组件使用原生事件
综上所述 有两种方法自定义 一种是v-on
另外一种是ref
前者代码 思路是在data中定义然后方法调用的时候赋值给定义的变量1
2
3
4
5
6
7
8
9
10
11
12
13
14data() {
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
7mounted() {
// 此处的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>
总结一图流:
全局事件总线
全局事件总线用于处理子组件和子组件之间的通信。
为了处理子组件之间的通信 要有一个中间人 当它被定义之后 如果有组件往里面挂载自定义事件用于得到信息,那么另外的组件就可以触发该事件发送信息 从此进行组件间的通信
那么这个中间人有三点需求
- 它可以被所有组件知道
- 它必须有
$on
,$emit
和$off
首先第一点只要它在vue原型中出现就可以1
2Vue.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
8const Demo = Vue.extend({});
const d = new Demo();
Vue.prototype.x = d;
new Vue({
el:'#root',
render:h=>h(App)
})
这样做之后就满足要求了
其他组件的通信写法
jojovue 发送端1
2
3
4
5methods: {
sendHello() {
this.x.$emit("hello", this.msg);
},
},
diovue接收端1
2
3
4
5mounted() {
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
8new Vue({
el:'#root',
render:h=>h(App),
beforeCreate(){
// 安装全局事件总线
Vue.prototype.$bus = this
}
})
注意 最好在组件销毁之前解绑bus身上的相关事件 利用beforeDestroy()
1
2
3beforeDestroy() {
this.$bus.$off('hello');
},
总结一图流:
消息订阅与发布
消息的订阅与发布是另外一种组件间通信的方法 一般引入pubsub.js
来调用pubsub对象里面的方法
- 安装pubsub
1
npm i pubsub-js
- 导入pubsub
1
import pubsub from 'pubsub-js'
- 基本使用
消息的订阅 使用到的是subscribe 传入两个参数 一个是订阅的名字 一个是进来的数据
且注意因为每次订阅都会生成不同的id 所以最后取消订阅是要传入一个id的 这里用pubId来演示消息的发布 使用到的是publish1
2
3
4
5
6
7
8mounted() {
this.pubId = pubsub.subscribe('hello',function(msgName,data){
console.log('系内jojo',msgName,data);
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId);
},直接在function里面输出this的话结果是undefined 这时候需要用到箭头函数来将this设置为vc1
2
3
4
5methods: {
sendHello() {
pubsub.publish('hello','欧拉欧拉欧拉欧拉!!!!!!!');
},
},特别注意一点 如果订阅的功能是写在methods里面的 那么需要methods里面的方法的第一个参数用占位符代替 因为它第一个要传一个msgName1
2
3
4
5
6mounted() {
this.pubId = pubsub.subscribe("hello", (msgName, data) => {
console.log(this);
console.log("系内jojo", msgName, data);
});
},
总结一图流:
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
6setTimeout(() => {
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中发送请求的时候跨域的问题
安装axios1
npm i axios
导入axios1
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
10module.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
12methods: {
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 | import Vue from 'vue' |
然后在mainjs里面导入 注意store后面的index不能省略1
2
3
4
5
6
7
8
9
10
11
12
13import 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
3import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex)
是因为cli脚手架它有个代码执行顺序的问题
所以不能在mainjs里面使用vuex再引入store
查看vuex的数据
还有一个案例再下一栏
加减法计算器
jisuanqivue1
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>
indexjs1
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
43import 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
- 因为已经use了vux这个插件 且已经注册了store 所以vc中会出现
$store
并且里面的state中有想要的sum值 可以插值语法直接获取 - vc如果先和action沟通 使用的是dispatch 并且第一个参数要和action里面的key对应
- 如果不需要action的业务逻辑判断 可以使用commit 直接发送给mutation进行加工 这里的加法和减法就是例子
action判断部分
- action在这里接收两个参数 一个是context 上下文对象 这个上下文对象依然也包含state 所以可以拿到sum进行数据过滤 另外一个参数是value 就是传递来的值
- action判断完之后需要传递两个参数 第一个和mutation里面的key相同 建议使用大写区分 第二个是value
mutation部分
- 加工数据 接收两个参数 一个是state 一个是value
- 没有发送
state部分
- 初始化参数
getters配置项
使用getters配置项可以想计算属性一样对state里面的数据进行操作
首先在store下面的indexjs里面定义getters1
2
3
4
5
6const 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 不是state1
<h1>当前求和放大十倍是{{ $store.getters.bigSum }}</h1>
总结一图流
mapState和mapGetters
主要是优化模板中插值语法$store.state.xxx
的冗余写法 应用场景是需要用到vuex里面的数据并且不想写复杂的$store.state.xxx
,用computed进行读取则可以用这种形式优化
首先需要import1
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
11methods: {
//使用这种写法要注意模板里面的函数要传参 值要写成字符串
...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
34const 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
14computed: {
...mapState("countOptions", ["school", "name", "sum"]),
...mapGetters("countOptions", ["bigSum"])
},
methods: {
...mapMutations("countOptions", {
increment: "INCREMENT",
decrement: "DECREMENT",
}),
...mapActions("countOptions", {
incrementOdd: "incrementOdd",
incrementWait: "incrementWait",
}),
},
一图流:
技巧类
iconfont