二十、mixin(混入选项options)和样式控制
(一)什么是混入?
当我们定义的很多组件中都用到的相同结构,可以以对象的方式定义到一个单独的js文件中,在加载组件时,Vue会将这些文件中和组件中的做个合体,都使用。
(二)混入使用
A、局部混入引用
- 场景设置:一个Student组件,一个School组件,两个组件都实现了点击姓名弹窗显示姓名,都有name属性
- 我们把弹窗函数和挂载函数抽离出来成为单独的js文件
School.vue
<template>
<div>
<span @click="getName">我的名字是{{ name }}</span><br>
<span>我的年龄是{{ address }}</span><br>
</div>
</template>
<script>
import { mixOne } from '../myself';//局部引入混合
export default {
name:'School',
data(){
return{
name: "超能力学院",
address: "M78星云"
}
},
mixins:[mixOne]
}
</script>
<style>
div{
background:orange;
}
</style>
Student.vue
<template>
<div>
<span @click="getName">我的名字是{{ name}}</span><br>
<span>我的年龄是{{ age }}</span><br>
</div>
</template>
<script>
import { mixOne } from '../myself';//局部引入混合
export default {
name:'Student',
data(){
return{
name: "塞文涕泗",
age: 22
}
},
mixins:[mixOne]//调用混合
};
</script>
<style>
div{
background:orange;
}
</style>
APP.vue
<template>
<div>
<student></student>
<hr>
<school></school>
</div>
</template>
<script>
import Student from "./components/Student.vue";//js的模块化语法,这里是引入这两个文件中暴露的结构
import School from "./components/School.vue";
export default {
name: "App",
components:{
Student,
School
},
}
</script>
<style>
</style>
mixin文件内容(我命名为myself.js)
export const mixOne ={
methods:{
getName(){
alert(this.name);
}
},
mounted(){
console.log("我是挂载函数我被调用了");
}
}
最后效果实现
- 有个细节就是,混入中声明的生命周期钩子和组件中声明的钩子不冲突两个都会执行,但是data中的同名的以组件中的为主。
B、全局混入引用。
- 首先我们把之前局部引用删掉,只留myself.js文件,然后在main.js文件中引入mixin文件
- 展示main.js代码片段:
//引入mixin文件
import { mixOne } from './myself'
Vue.config.productionTip = false
//使全局使用混入
Vue.mixin(mixOne);
这样我们就把mixOne加入到了所有结构中,三个组件,一个vm一共执行了四次挂载函数:
二十一、Vue插件的使用
(一)插件的创建(js文件)
export default{
install(Vue,data){
//定义全局指令
Vue.directive("fbind",{
//定义让元素字体变红的指令
inserted(element){
element.style.color = "red";
}
});
//定义全局混入
Vue.mixin({
methods:{
myMethods(){
console.log("这是插件中的混入方法","用户传入的数据是:" + data);
//接收用户传入的数据并展示到控制台
}
}});
//给原型添加方法,vm和vc共用
Vue.prototype.hello=function(){
console.log("我是原型中的方法!");
}
}
}
(二)插件的引入,在main.js中
//引入插件
import Plugin from "./plugin"
//使用插件,和传入自己的数据
Vue.use(Plugin,"谁与争锋");
(三)student组件中调用插件中定义的指令fbind
<span v-fbind>地址是{{ address }}</span><br>
效果:成功
(四)样式控制
- 每个组件默认中间的style是通用的,但是如果我们加上scope这个属性,就会只应用于该组件。
<style scope lang="css">
div{
background:orange;
}
</style>
- 加上lang属性置顶样式书写方式,不加默认是css方式,并且脚手架默认支持css,像less等需要插件
二十二、自定义事件
(一)自定义事件能干什么?
- 就目前的知识来看,它能够将子组件信息传递到父组件中,实现父组件与子组件的信息传递:
A、用props传递函数的方式实现子组件向父组件传递数据,暂时不用自定义事件
- Student是子组件,App是父组件
在父组件的methods声明函数获取子组件的name属性(为更加清晰,全结构粘贴,注释部分是传递过程)
Student.vue
<template>
<div>
<span >我的名字是{{ name}}</span><br>
<!-- 第四步声明子组件的点击事件,用来触发父组件传递的函数,并向父组件传递name属性 -->
<button @click="giveName">点我触发点击函数向父组件传递name值</button>
<span>我的年龄是{{ age }}</span><br>
</div>
</template>
<script>
export default {
name:'Student',
//第三步接收父组件传递的函数
props:["getSonName"],
data(){
return{
name: "塞文涕泗",
age: 22
}
},
methods:{
//第五步声明函数并传递name属性
giveName(){
this.getSonName(this.name);
}
}
};
</script>
<style>
div{
background:orange;
}
</style>
App.vue
<template>
<div>
<!-- 第二步向子组件传递该函数 -->
<student :getSonName="getSonName"></student>
<hr>
<school></school>
</div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
components:{
Student,
School
},
methods:{
//第一步声明要获取子组件的函数
getSonName(name){
console.log("获取到的子组件的name值是" + name);
}
}
}
</script>
<style>
</style>
最后实现了点击子组件按钮向父组件传递数据,输出到了控制台。
B、我们再使用自定义函数的方式实现。
- 区别点主要是
- 原来是向子组件传递函数,现在是直接通过在子组件标签上定义自定义函数的方式,直接在vc(组件实例)上绑定事件
- 因为不是传递数据,所以子组件不再需要接收函数并调用,只需要调用自己的api$emit触发自定义事件实现对getSonName函数的调用。
- 具体代码见下,注释是步骤:
App.student
<template>
<div>
<!-- 第二步向子组件实例绑定自定义事件,事件会调用该函数函数 -->
<student @myEvent="getSonName"></student>
<hr>
<school></school>
</div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
components:{
Student,
School
},
methods:{
//第一步声明要获取子组件的函数
getSonName(name){
console.log("获取到的子组件的name值是" + name);
}
}
}
</script>
<style>
</style>
Student.vue
<template>
<div>
<span >我的名字是{{ name}}</span><br>
<!-- 第三步声明子组件的点击事件,用来触发自身的自定义事件myEvent从而调用获取name函数 -->
<button @click="giveName">点我触发点击函数向父组件传递name值</button>
<span>我的年龄是{{ age }}</span><br>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return{
name: "塞文涕泗",
age: 22
}
},
methods:{
//第四步在函数中触发自定义事件myEvent
giveName(){
//用来触发定义事件的函数,第一个参数为事件名,第二个为其他参数
this.$emit("myEvent",this.name);
}
}
};
</script>
<style>
div{
background:orange;
}
</style>
C、当然上面代码还可以改进
- 为何改进,如何改进?
- 为何改进?因为第二种在绑定自定义事件的时候是自动就将事件绑定到vc(组件实例)上了,不能在实现灵活绑定。
- 如何改进?可以利用声明周期的知识,在挂载的时候手动给子组件挂载自定义事件,同时要利用ref独特身份标识(仅展示App.vue变动)因为Student没变化。
<template>
<div>
<!-- 第二部用ref添加独特标识,用于寻找vc -->
<student ref="student"></student>
<hr>
<school></school>
</div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
components:{
Student,
School
},
methods:{
//第一步声明要获取子组件的函数
getSonName(name){
console.log("获取到的子组件的name值是" + name);
}
},
//第三步声明挂载后函数,给子组件绑定事件,并延时绑定
mounted(){
//获取vc实例并绑定,参数第一个为自定义事件名,第二个是事件绑定的函数
this.$refs.student.$on("myEvent",this.getSonName);
}
}
</script>
<style>
</style>
- 一些小细节:
- 如果我们想要这些事件只绑定一次应该怎么做?
如果是第二种绑定方式: @自定义事件名.once="函数"
如果是第二种绑定方式:this.$refs.myEvent.$once("自定义事件名",绑定的函数);
2.如果我们把App.vue中的mounted中自定义绑定的回调函数直接声明在函数中,this指向会发生改变
原来的方式,this指的是自己,即App组件的实例
变换方式:
mounted(){ //获取vc实例并绑定,参数第一个为自定义事件名,第二个是事件绑定的函数 this.$refs.student.$on("myEvent",function(name){ this.Appname = name; }); }
这样的话这当中的this就指的是student组件对象了,改用箭头函数指向App组件实例对象一致,在methods中声明也指向App组件实例。
3.自定义事件用于子给父传递数据,所以事件绑定要在父中进行,保证事件绑定的函数是自己的函数,不然就收不到数据了
4.解绑方法
组件对象实例.$off("事件名");
//多事件解绑
组件对象实例.$off(["事件名","事件名"]);
//全部解绑
组件对象实例.$off();
二十三、消息总线(组件之间的信息传递)
(一)消息总线的思想
- 利用中间人的思想,让一个自定义的vm原型中的对象作为中间的数据中介($bus)
- 消息接收者调用$bus的$on方法为$bus绑定自定义事件,因为在这里给$bus绑定事件就可以调用自己的方法。
- 消息发送者,再调用$bus的自定义事件,触发事件绑定的接收者身上的函数,实现数据发送
- 那么这样这个中间件就需要三大特征:a、每个组件和中间件都能看到 b、自己能调用$on和$emitApi
(二)消息总线中bus的实现三大原则的最优实现方案分析
- 为什么不能在组件的构造函数VueComponent中添加$bus呢?
- 因为组件是在挂载的时候用组件标签得到实例化的,并且每个组件实例化出的实例对象都是不同的,所以不仅仅有一个$bus.
- 利用vc和vue和vm都能看到的Vue的原型对象中的数据(参考十六(五)),所以声明在vm中是最理想的。
- 那么如何实现在vm的原型中添加$bus?
- 需要利用vm的生命周期钩子,beforeCreate(),这时的vm已经创建成功,this就是指代vm对象。
- 最完美的是$on,$emit,这些方法都在vm的原型对象上
(三)消息总线的使用(代码仅为局部)
vm(main.js)
new Vue({
el:"#app",
render:function(createElement){ return createElement(App)},
beforeCreate(){
//第一步在vm生成后,页面挂载以及数据代理都没进行时,给Vue身上添加总线(类型是vm)
Vue.prototype.$bus = this;
}
})
消息获取方(student.vue)
export default {
name:'Student',
data(){
return{
name: "塞文涕泗",
age: 22
}
},
methods:{
getMimi(mimi){
//收到数据后直接展示数据
console.log("数据接收方接收到了数据:" + mimi);
}
},
mounted(){
//第二步,数据获取方给$bus绑定自定义事件
this.$bus.$on("getData",this.getMimi);
},
beforeDestroy(){
//当相关组件销毁时,及时将中间件的自定义事件解绑
this.$bus.$off("getData");
}
};
消息发送方(school.vue)
export default {
name:'School',
data(){
return{
name: "超能力学院",
address: "M78星云",
mimi: "我爱你"
}
},
mounted(){
//第三步数据发送方调用自定义事件并发送数据,因为$bus的回调函数在获取方身上,
//所以收到数据的不是$bus而是获取方
this.$bus.$emit("getData",this.mimi);
}
}
最终控制台输出:
(四)利用发布订阅模式实现消息传递(需要pubsub-js插件不推荐)
- 首先用npm下载插件
sudo npm i pubsub-js
然后改变上例中mounted中的逻辑即可
消息接收者(student.vue)
//将插件库引入,其本质就是一个对象
import PubSub from "pubsub-js"
export default {
name:'Student',
data(){
return{
name: "塞文涕泗",
age: 22
}
},
methods:{
getMimi(EventName,mimi){
//收到数据后直接展示数据
console.log("数据接收方接收到了数据:" + mimi);
}
},
mounted(){
//返回订阅的订阅号,用于取消订阅
const pubId = PubSub.subscribe("getData",this.getMimi);
},
beforeDestroy() {
PubSub.unsubscribe(pubId);
},
}
消息发布者:
//引入插件
import PubSub from "pubsub-js";
export default {
name:'School',
data(){
return{
name: "超能力学院",
address: "M78星云",
mimi: "我爱你"
}
},
mounted(){
//发布消息
PubSub.publish("getData",this.mimi);
}
}
二十四、Vue实现动画效果
(一)简单的动画效果实现:
- 实现当元素显示和消失时,渐入的动画效果
模版结构:
<template>
<div>
<transition>
<div v-show="isShow" class="mydiv">我是元素,我会动</div>
</transition><br>
<button @click="isShow = !isShow">点我让元素消失或显现</button>
</div>
</template>
首先我们要定义动画,即关键帧,这里跟js动画一致
@keyframes myframes{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0);
}
然后我们就需要将这个关键帧绑定到元素身上,本来直接在要实现动画的元素身上声明animation,现在Vue做了中间人,我们要在使用动画的元素外包上<transition>标签
<transition>标签上声明name属性,我们就可以通过固定的语言找到要变化的元素,并通过独特的类名选择器找到元素动画的不同阶段。
.name属性-enter-active{
animation: ....
}//这里规定动画元素进入时的动画效果
.name属性-leave-active{
animation:......
}//这里规定动画元素离开时的动画效果。
最后我们通过一个按钮控制元素的显示和消失,为动画创造基本条件:
完整结构如下:
<template>
<div>
<transition>
<div v-show="isShow" class="mydiv">我是元素,我会动</div>
</transition><br>
<button @click="isShow = !isShow">点我让元素消失或显现</button>
</div>
</template>
<script>
export default {
name: "MyText",
data(){
return{
isShow: true,
}
}
}
</script>
<style>
/* 一些div的样式 */
.mydiv{
background: red ;
}
/* 动画效果的实现 */
.v-enter-active{
animation: myframes 0.8s linear;
}
.v-leave-active{
animation: myframes 0.8s linear reverse;
}
@keyframes myframes{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0);
}
}
</style>
- transition标签上还可以加上appear,控制动画元素最开始就显现,name为动画元素添加表识
(二)用过渡的方式实现
- 首先声明元素各种状态时的效果(me是给transition的name值)
/* 元素开始的进入时的过渡效果
定义元素结束离开时的状态 */
.me-enter,.me-leave-to{
transform: translateX(-100%);
}
/* 元素进入结束时的状态
定义元素开始离开时的状态*/
.me-enter-to,.me-leave{
transform: translateX(0);
}
然后在要使用的元素身上再声明变换的时间和时间函数;
.mydiv{
background: red ;
/* 给过渡定义事间 */
transition: 0.8s linear;
}
结构如下:
<div>
<transition name="me">
<div v-show="isShow" class="mydiv">我是元素,我会动</div>
</transition><br>
<button @click="isShow = !isShow">点我让元素消失或显现</button>
</div>
(三)多个元素的过渡
- <transition>标签下只能包一个元素,那么当我们要使两个元素同时带上动画效果要怎么办呢?
- 情况一:两个元素都带有同样的效果:
- 放在一个div中,再被<transition>包起来,问题解决。
- 放在<transition-group>标签中,但是每个元素都要带上key值。
<transition-group name="me">
<div v-show="isShow" class="mydiv" key="1">我是元素,我会动</div>
<div v-show="isShow" class="mydiv" key="2">我是元素,我会动</div>
</transition-group>
(四)使用第三方库实现动画效果(第三方库官网,这里使用的是animate.css )
A、官网连接如下Animate.css | A cross-browser library of CSS animations.
B、首先安装该库
npm install animate.css
C、然后再组件中导入并使用(下载的文件存在了node_modules中)
import 'animate.css'
D、给<transition>或<transition-group>标签加上指定的class
name="animate__animated animate__bounce"
E、最后根据官网,在元素中添加属性值对就可以引用对应的动画效果了
enter-active-class="animate__swing"
leave-active-class="animate__wobble"
二十五、Vue代理服务器实现跨域访问,获取数据
(一)了解axios
特·、下载axios要用 sudo npm i axios 指令
A、js中的原生方法XMLHttpRequest()、open()、和send()也可一实现http协议向外发送get和post请求
B、axios封装了这些方法,使get请求发送以及返回数据的处理变得更加方便,下面展示案例代码中axios的使用
axios.get("http://localhost:8080/myhp/traver/myself.txt").then(
response => { console.log("获得了文件,文件内容是" + response.data),
error => {console.log("没有获得成功,原因是:" + error.message)}}
)
可以看到,其中的get方法,第二和第三个参数就可以传入对返回结果的处理函数,以及出错的处理函数
(二)理解get请求发送与数据获取
A、如果我们直接通过本网页向其他互联网上的服务器发送get请求,数据会被浏览器拦下,因为浏览器不允许跨域访问,实行了跨域截断。
B、Vue直接就给我们提供了一种在配置文件添加代理的方式,这样就能实现跨域访问数据,配置文件如下:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
/*关闭语法检查*/
lintOnSave:false,
/*开启服务器代理,向外部服务器获取数据*/
devServer:{
proxy:{
"/myhp":{
target: "http://qhecshui.com:8080",
pathRewrite:{'^/myhp':''},
ws: true,
changeOrigin: true
}
}
}
}
)
我们只需要关注devServer这个配置项即可。
- 其中的"proxy"可以配置多个,意为当请求发送前缀为"/myhp"时,请求就会被代理。
- "target"中指定如果代理那么请求的地址是谁
- "pathRewrite"重写目标地址,将判断代理的前缀"/myhp"删掉,以保证地址的正确性。
(三)在组件中利用事件发送请求。
<template>
<div>
<button @click="getInfo">点我获取其他网站信息</button>
</div>
</template>
<script>
//引入axios
import axios from "axios";
export default {
name: "MyText",
data(){
return{
isShow: true,
}
},
methods:{
getInfo(){
//文件中的地址是发送到代理的地址,还加了指定前缀触发代理,同时代理会转到真正的目标
axios.get("http://localhost:8080/myhp/traver/myself.txt").then(
response => { console.log("获得了文件,文件内容是" + response.data),
error => {console.log("没有获得成功,原因是:" + error.message)}}
)
}
}
}
</script>
<style>
/* 一些div的样式 */
.mydiv{
background: red ;
}
</style>
最终我们在控制台输出了从我自己的服务器http://qhecshui.com:8080/traver/myself.txt
获取到的信息:
二十六、插槽
(一)插槽能干什么?
能够在父组件中给子组件添加新的结构,也是父子组件数据交流的另一种方式
(二)三种插槽的使用
A、单一插槽
- 结构三个列表:
子组件结构与样式
<template>
<!-- 该组件是一个列表组件,目的是将列表数据展示到列表中 -->
<div class="MyUl">
<!-- 列表的标题 -->
<h3>标题:{{ title }}</h3>
<slot></slot><!--标明父元素插入组件中元素的位置-->
<ul>
<li v-for="(value,index) in infos" :key="index" >{{value}}</li>
</ul>
</div>
</template>
.MyUl{
background: yellow;
text-align: center;
width: 30%;
height: 100%;
flex-direction: column;/*规定弹性元素中元素的排布方向沿纵轴排布*/
}
h3{
height: 8%;
}
div ul{
height: 50%;
list-style: none;/*去除掉li前面的标签*/
padding: 0px; /*去除默认内边距,让li文本居中*/
}
父组件结构与样式
<template>
<div>
<!-- 向子组件传递数据 -->
<MyUl :infos="infos[0]" :title="title[0]">
<!-- 向子组件插入图片到指定位置 -->
<img src="https://t10.baidu.com/it/u=1356697368,201154584&fm=30&app=106&f=JPEG?w=640&h=1267&s=580440941C1272D45989851C030050E3"
width="100%">
</MyUl>
<MyUl :infos="infos[1]" :title="title[1]"></MyUl>
<MyUl :infos="infos[2]" :title="title[2]" ></MyUl>
</div>
</template>
<style scoped>
/* 设计三个列表的样式 */
div {
display: flex;
width: 100%;
height: 800px;
justify-content: space-around;/*规定里面元素的排列为平分剩余,周围留空隙*/
}
</style>
B、多插槽的使用:
- 我们再在第一个位置加上两个超链接
父组件结构
<MyUl :infos="infos[0]" :title="title[0]">
<!-- 向子组件插入图片到指定位置 -->
<img slot="myImg" src="https://t10.baidu.com/it/u=1356697368,201154584&fm=30&app=106&f=JPEG?w=640&h=1267&s=580440941C1272D45989851C030050E3"
width="100%">
<div slot="myItems">
<a href="">查看细节</a><a href="">查看趋势</a>
</div>
</MyUl>
子组件结构
<div class="MyUl">
<!-- 列表的标题 -->
<h3>标题:{{ title }}</h3>
<slot name="myImg"></slot>
<ul>
<li v-for="(value,index) in infos" :key="index" >{{value}}</li>
</ul>
<slot name="myItems"></slot>
</div>
多个slot需要标注name,父组件插入结构,要标注slot属性,用于寻找在子组件位置
C、作用域插槽
- 作用域插槽能够实现数据从子组件传递给父组件
- 首先数据在子组件中,我们要在子组件中定义插槽。
<!-- 子组件插槽定义位置 -->
<slot name="myTable" :my="infos[0]"></slot>
:my 是我们将子组件数据传到父组件,穿过去是一个以my为键,数组为值的对象。
数据结构:
data(){
return{
title:["动漫","电视剧","童年"],
infos:[["《斗破苍穹年番》","《完美世界》","《不良人》"],
["《魔幻手机》","《楚乔传》","《步步惊心》"],
["《点击小子》","《巴啦啦小魔仙》","《变形金刚》"]]
}
},
2.在父组件中声明要在插槽中插入的内容
<ul slot="myTable" slot-scope="myme">
<li v-for="(value,index) in myme.my" :key="index" >{{value}}</li>
</ul>
slot-scope用来接收数据。
二十六、Vuex
(特)学习用到的例子
- 不用Vuex实现数字的各种加减操作
<template>
<div>
<span>现在的数是{{ sum }}</span><br>
<!-- 选项框,让用户选择要加或减的数值大小 -->
<select v-model.number="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="addToNum">+</button>
<button @click="cutToNum">-</button>
<button @click="ifAdd">当为奇数时再加</button>
<button @click="waitAdd">延时加载</button>
</div>
</template>
<script>
export default {
name: "VxTest",
//接收父组件数据
data(){
return{
n: 1,
sum: 0
}
},
methods:{
addToNum(){
this.sum += this.n;
},
cutToNum(){
this.sum -= this.n;
},
ifAdd(){
if(this.sum % 2 != 0){
this.sum += this.n;
}
},
waitAdd(){
//延时三秒
setTimeout(()=>{this.sum += this.n},3000);
}
}
}
</script>
<style scoped>
</style>
(一)什么是Vuex
- 它的出现是为了实现对所有组件状态(data)的管理。
- 适用场景:当很多组件都需要同一个数据时(这个数据被需求少的情况下,可以用消息总线实现),就需要用到Vuex。
- Vuex的实现思想:
这些组件都需要x,就可以利用Vuex实现了。
(二)Vuex的结构和流程
- 结构图:(来自官网)
- 解释
在将数据存入state前有两个步骤,先通过store.dispatch("给数据的命名",数据),将数据命名传递给Actions,Actions接收到之后会选择性进行一些操作(从服务器获取数据等),然后用以数据名命名的函数在其中再调用store.commit()方法将数据传递给mutations,Mutations在其中会进行真正的给state赋值和修改操作。
(三)Vuex的使用
A、安装和引入Vuex插件
- 安装:(注意:Vuex3支持Vue2,Vuex4支持Vue3)
npm i vuex@3
- Vuex中的store作为一个特殊的组件,Vue团队给出的方案是放在src文件夹的index.js文件来生成store实例:
index.js文件结构
//引入Vue
import Vue from "vue";
//引入Vuex
import Vuex from "vuex";
//Vue使用vuex,应用后,Vue中就会多出来了$store属性,但未赋值
Vue.use(Vuex);
//创造Vuex所必需的三个对象
const actions = {
};
const mutations = {
};
const state = {
sum:0,
n:0,
};
//创建Vuex的store实例,用来放入Vue中的$store属性,创建方式跟vm很像
export default new Vuex.Store({
actions,
mutations,//当要赋的变量名,跟选项名一样时可以缩为一个
state,
});
- 这里我们仅需要生成Vuex的store对象,为什么还要引入Vue来运用Vuex呢?为什么不在mai n.js中使用呢?
- Vue 在解析文件时,import的文件会优先加载,如果放在main.js中会先有store对象生成,然后才有Vue中store选项的生成。
- Vue规定,Vue.use(vuex)必须在store对象生成之前。
B、将(特)中的例子里的sum改到store中,理解数据修改与获取的细节
从第一个方法实现分析过程
- 从组件到actions
组件
data(){
return{
n: 1,//sum值被放到了Store的state中
}
},
methods:{
addToNum(){
//第一步将组件中的this.n用dispatch方法传入actions。
//参数一是对下一步要调用的actions中函数的命名
//参数二是自己传入的数值
this.$store.dispatch("addNum",this.n);
},
actions对象内容:
const actions = {
//第二步,actions会增加一个方法跟数据同名
//参数解析:context中同样含有dispatch和commit方法,用于发送到mutation
// 后面的参数是我们传入的数据this.n
addNum(context,value){
//这里如果要和服务器有些互动操作可以进行,但这里没有
//这里的参数跟dispatch一致
context.commit("addNum",value);
}
};
actions中的context参数包含state,getter数值和commit,dispatch这些函数
dispatch方法可以将数据交给actions中其他同级方法处理相关逻辑。
- 从actions到mutations
const mutations = {
//根据已知知识,这里会传入两个参数,一个是state对象,另可以是传入的值
addNum(state,value){
//这里实现真正的对公共值sum的处理,实现了对sum的修改。
state.sum += value;
}
};
- 插值语法中同步state中数据
{{ $store.state.sum }}
- 注意点:
- 上面过程是对state中共享内容的修改操作。
- 在actions中,上面例子并没有其他操作,可以直接在组件方法中调用commit方法,略过actions,直接转到mutation
addToNum(){
//第一步将组件中的this.n用dispatch方法传入actions。
this.$store.commit("addNum",this.n);
}
C、将(特)中例子全部改为Vuex实现。
组件内容
<template>
<div>
<span>现在的数是{{ $store.state.sum }}</span><br>
<!-- 选项框,让用户选择要加或减的数值大小 -->
<select v-model.number="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="addToNum">+</button>
<button @click="cutToNum">-</button>
<button @click="ifAdd">当为奇数时再加</button>
<button @click="waitAdd">延时加载</button>
</div>
</template>
<script>
export default {
name: "VxTest",
//接收父组件数据
data(){
return{
n: 1,//sum值被放到了Store的state中
}
},
methods:{
addToNum(){
//第一步将组件中的this.n用dispatch方法传入actions。
this.$store.commit("addNum",this.n);
},
cutToNum(){
this.$store.commit("cutNum",this.n);
},
ifAdd(){
this.$store.commit("ifAdd",this.n);
},
waitAdd(){
this.$store.commit("waitAdd",this.n);
}
},
}
</script>
<style scoped>
</style>
index.js内容
//引入Vue
import Vue from "vue";
//引入Vuex
import Vuex from "vuex";
//Vue使用vuex,应用后,Vue中就会多出来了$store属性,但未赋值
Vue.use(Vuex);
//创造Vuex所必需的三个对象
const actions = {
//第二步,actions会增加一个方法跟数据同名
//参数解析:context中同样含有dispatch和commit方法,用于发送到mutation
// 后面的参数是我们传入的数据this.n
};
const mutations = {
//根据已知知识,这里会传入两个参数,一个是state对象,另可以是传入的值
addNum(state,value){
//这里实现真正的对公共值sum的处理,实现了对sum的修改。
state.sum += value;
},
cutNum(state,value){
state.sum -= value;
},
ifAdd(state,value){
if(state.sum % 2 != 0 ){
state.sum += value;
}
},
waitAdd(state,value){
setTimeout(()=>{state.sum += value},3000);
}
};
const state = {
sum:0,
};
//创建Vuex的store实例,用来放入Vue中的$store属性,创建方式跟vm很像
export default new Vuex.Store({
actions,
mutations,//当要赋的变量名,跟选项名一样时可以缩为一个
state,
});
main.js内容
//引入vue,从库中
import Vue from 'vue';
//引入管理组件
import App from './App.vue'
//将创建的Store实例引入
import store from "./store";
Vue.config.productionTip = false
new Vue({
el:"#app",
render:function(createElement){ return createElement(App)},
store,
})
D、Vuex的getter配置项
- 为什么要有该属性,跟计算属性很像,这里针对在state中储存的数据,如果想要根据state数据变化适时展示另一属性就可以用到getter,应该如何使用?
首先要在store结构中增加该结构;
var getters = {
extendNum(state){
//将数据扩大二十倍,该属性名是extendNum
return state.sum * 20;
}
}
//创建Vuex的store实例,用来放入Vue中的$store属性,创建方式跟vm很像
export default new Vuex.Store({
actions,
getters,
mutations,//当要赋的变量名,跟选项名一样时可以缩为一个
state,
});
然后我们就可以将数据通过标签的插值语法显示在界面上
<span>扩大20倍后的数是{{ $store.getters.extendNum }}</span><br>
(四)模版插值语法中同步state中数据同步数据精简化
A、原来调用
<span>扩大20倍后的数是{{ $store.getters.extendNum }}</span><br>
B、在计算属性中返沪然后精简
计算属性:
computed:{
endNum(){
return this.$store.getters.extendNum;
}
},
调用:
<span>扩大20倍后的数是{{ endNum }}</span><br>
C、一个疑问:这样虽然模版符合精简化原则,但每次调用不同数据都需要一个计算属性返回,导致程序员工作增加,如何解决?我们将会在后面再优化。
(五)Vuex提供的四个原生方法实现对Vuex操作的简化
A、mapMutations和mapActions
- 首先我们回顾下之前再方法中调用中的结构:
相同颜色的框框起来的结构都是重复的。
- 改进一:使用对象传参的方式(方法名和第一个参数不一致)
首先引入mapMutations
import {mapMutations} from "vuex";
使用
methods:{
//...是es6的展开语法,意味将方法的返回值(对象)中的属性和值分别加入到结构中
...mapMutations({addToNum:"addNum",cutToNum:"cutNum",ifAdd:"ifAdd",waitAdd:"waitAdd"}),
},
注、mapMutations方法返回值:
这样的方式没法在方法中传入自己的参数,要提前在事件中声明好!
中间的n参数,会被加到commit()函数的参数位置。
- 改进二:数组传参、当方法名和第一个参数值一样时,可以直接用数组代替
...mapMutations(["addNum","cutNum","ifAdd","waitAdd"])
-
mapActions是dispatch方法的简写,方式跟上一致
C、mapState和mapGetter
- 这两个方法是对Vuex中数据获取方式的简写
- 例子前置条件:state中有name和lover属性、两个getter:extendNum、extendNumTwo
- 不用这两个函数获取数据,大量重复数据
- 优化:
- 如果计算属性的名字和getter名或者state名字一致,都可以简化为数组形式。
...mapGetters(["extendNum","extendNumTwo"]),
...mapState(["name","lover"}),
最终的数据展示
<div>
<span>现在的数是{{ $store.state.sum }}</span><br>
<span>扩大20倍后的数是{{ endNum }}</span><br>
<span>我的名字是:{{ endName }}</span><br>
<span>扩大10倍的数是{{ endNumTwo }}</span><br>
<span>我喜欢的人是:{{ endLover }}</span><br>
<!-- 选项框,让用户选择要加或减的数值大小 -->
<select v-model.number="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="addToNum(n)">+</button>
<button @click="cutToNum(n)">-</button>
<button @click="ifAdd(n)">当为奇数时再加</button>
<button @click="waitAdd(n)">延时加载</button>
</div>
二十七、Vuex的模块化(module)
(一)为什么要有模块化。
A、当一个项目很多组件分成两块,一块中的组件都需要一部分数据,另一块中需要另一部分数据,这是就需要store来个“分身”,而模块化就是这样的思想。
B、模块化使操作业务时条理更加清晰。
(二)将例子中的store模块化。
A、我们将VxTest组件需要的数据都放到模块testAbout,同时增加新的模块peopleAbout,用来储存新添组件people的数据和操作。结构如下。
里面包括people对数据的一些操作,暂时不用关心,只看结构。
里面包括了模块在store中的注册。
//引入Vue
import Vue from "vue";
//引入Vuex
import Vuex from "vuex";
//Vue使用vuex,应用后,Vue中就会多出来了$store属性,但未赋值
Vue.use(Vuex);
//创建Vxtext需要的数据的store的模块
const testAbout ={
actions : {
},
mutations: {
addNum(state,value){
state.sum += value;
},
cutNum(state,value){
state.sum -= value;
},
ifAdd(state,value){
if(state.sum % 2 != 0 ){
state.sum += value;
}
},
waitAdd(state,value){
setTimeout(()=>{state.sum += value},3000);
}
},
state : {
sum:0,
name:'立花泷',
lover: "宫水三叶"
},
getters :{
extendNum(state){
return state.sum * 20;
},
extendNumTwo(){
return state.sum * 10;
}
}
};
const peopleAbout ={
actions:{
},
mutations:{
addPeople(state,value){
//将数据添加到list
state.peopleList.push(value);
}
},
state:{
//给people准备的人员数组
peopleList:[],
},
getters:{
peopleCount(state){
return state.peopleList.length;
}
}
}
//将两个模块注册进store
export default new Vuex.Store({
modules:{
peopleAbout,
testAbout
}
});
(三)分析$store结构的变化
A、因为我们在store中加了一层,所以难以避免,对自己模块中的数据操作和调用队徽变化。
B、都有哪些变化呢?
现在的$store结构,我们在挂载钩子中将$store输出到控制台
一、getter结构,虽然来自不同的module但是,Vuex并未区分,全部还是以本来名字存到$store中
二、mutations和actions全部也都在一块,并未区分。
总结:依然可以用本来的方式操作数据和获取getter(前提,模块中命名空间没有开启)
三、state有变化,加上了模块化
可见都在不同模块化外面包上了模块名的对象,所以调用不同数据就变了
原来方法:
this.$store.state.peopleList;
现在方法:
this.$store.state.peopleAbout.peopleList;
C、思考:这样设计的分析
- 这样只是将不同组件群体的不同需求数据都分了块,但是操作还是混到一块的。
- 但是确实也实现了不同模块有不同的数据,增加了条例性。
- 那么如何将actions,mutations、getter也作分类呢?这就需要给每个模块加上namespaced属性了。
(四)分析开启了命名空间后,$store结构的变化
A、开启模块的命名空间,在index.js中组件结构中加上namespaced配置项。
const testAbout ={
namespaced: true,
actions : {
},
mutations: {
addNum(state,value){
state.sum += value;
},
cutNum(state,value){
state.sum -= value;
},
ifAdd(state,value){
if(state.sum % 2 != 0 ){
state.sum += value;
}
},
waitAdd(state,value){
setTimeout(()=>{state.sum += value},3000);
}
},
state : {
sum:0,
name:'立花泷',
lover: "宫水三叶"
},
getters :{
extendNum(state){
return state.sum * 20;
},
extendNumTwo(state){
return state.sum * 10;
}
}
};
同时给peopleAbout也开启命名空间。
B、查看结构变化:
state没变化,跟模块化开启后结构一致。
actions和mutation以及getters
可见方法名前面都加上了“组件名/”
- 那么对应的通信方法和获取方法都要发生变化了
//本来原来获取
this.$store.getters["peopleCount"];
this.$store.commit("addPeople",this.peopleName);
this.$store.dispatch("actios中的方法名",this.peopleName);
//模块化开启命名空间之后
this.$store.getters["peopleAbout/peopleCount"];
this.$store.commit("peopleAbout/addPeople",this.peopleName);
this.$store.dispatch("peopleAbout/actios中的方法名",this.peopleName);
(五)开启命名空间后,Vuex提供方法的简便调用改变。
//修改后的VxTest内容,
computed:{
...mapGetters("testAbout",{endNum:"extendNum",endNumTwo:"extendNumTwo"}),
...mapState("testAbout",{endName:"name",endLover:"lover"}),
},
methods:{
//...是es6的展开语法,意味将方法的返回值(对象)中的属性和值分别加入到结构中
...mapMutations("testAbout",{addToNum:"addNum",cutToNum:"cutNum",ifAdd:"ifAdd",waitAdd:"waitAdd"}),
},
mounted(){
var me = mapMutations("testAbout",{addToNum:"addNum",cutToNum:"cutNum",ifAdd:"ifAdd",waitAdd:"waitAdd"});
console.log(me);
}
在前面加上一个参数,等价于,帮你调用对应的模块中的方法和数据,这些方法都在后台帮你做了。