全局事件总线的大致使用流程:
注意:
一定要记得解绑全局事件
//创建一个全局事件总线 、、在main.js入口文件中挂载
new Vue({
beforCreate(){
Vue.prototype.$bus=this;
}
})
//需要 接收 数据的组件
{
//绑定全局事件总线上的 自定义事件
mounted(){
this.$bus.$on("需要绑定的事件",""绑定事件后需要触发的函数"")
}
//解绑全局事件总线上的 自定义事件
beforeDestroy(){
this.$bus.$off("需要解绑的事件")
}
}
//需要 发送 数据的组件
{
this.$bus.$emit("需要触发的自定义事件",触发事件时需要传入的参数)
}
bus 事件demo
// app 组件中
import Vue from 'vue'
export default {
name: 'app',
beforeCreate() {
Vue.prototype.$bus = this
},
}
// 监听事件的组件
methods: {
bustestHandle(){
console.log('全局事件总线,bus孙组件成功调用爷组件中的事件')
}
},
mounted() {
this.$bus.$on('random', (data) => { // random 这个名字可以随意起,但是必须和 $emit 触发事件的名字相一致.
this.bustestHandle()
console.log(data,'参入的参数')
})
},
beforeDestroy(){
this.$bus.$off("random")
}
// 触发事件的组件
<el-button @click="busHandle">触发爷组件事件</el-button>
busHandle(){
this.$bus.$emit('random','天生我材必有用') // random 这个名字可以随意起,但是必须和 $on 监听事件的名字相一致.
}
provide inject 使用注意事项
provide 用于
为后代组件提供数据
inject 用于接收 顶层组件提供的数据
注意: 如果在
后代组件中使用 provide
,在顶层组件中使用 inject
,顶层组件会拿不到
后代组件提供的数据
.
必须是 父组件提供(provide),子组件注入(inject)!!!
provide 使用注意事项
provide 对象式使用
官方文档@provide方法文档
对象式
使用时,无法访问
的指向 vue 实例的this
。
export default {
provide: {
message: "hello!",
},
};
provide 函数式使用
函数式
使用时,可以访问
到指向 vue 势力的this
。
使用
函数式provide
提供一个函数时,需要注意.
- 如果提供的函数表达式是
message:function(){}
(函数字面量),则访问不到
指向 vue 实例的this
.- 如果提供的函数表达式是
messsage:()=>{}
(箭头函数),则可以访问
到指向 vue 实例的this
.
export default {
data() {
return {
message: "hello!",
};
},
provide() {
// 使用函数的形式,可以访问到 `this`
return {
message: this.message,
selectUserChange: () => {
console.log(this); // 箭头函数方式,可以访问到 this
},
selectUserChangeFunction: function () {
console.log(this); // function 字面量方式 ,访问不到 this
},
// 上面的声明方式等同于
selectUserChangeFunction() {
console.log(this); // function 字面量方式 ,访问不到 this
},
};
},
};
$attrs
和 $listeners
使用
$attrs 的使用:
- 在爷组件(index.vue)中,类似 props 传值,将需要传递的值绑定在父组件上。
- 在父组件中,也是类似 props 传值,但是这里传递的就不是值了,而是$attrs。
- 在孙组件中,接收 props,这样就可以在孙组件中使用这个数据了。(需要注意的是父组件中不需要接收 props,只要在孙组件中接收就可以。)
$listeners 的使用:
- 在爷组件(index.vue)中,绑定事件。
- 在父组件中,也是类似绑定事件,但是这里绑定的不是具体的事件,而是 v-on=“$listeners”。
- 在孙组件中,需要的时候触发($emit)这个事件即可。 然后上代码:
一:正常 props 爷孙组件传值
//爷组件
<grand-father>
<FatherComponent :message="message"></FatherComponent>
</grand-father>
// 父组件
<FatherComponent >
<SonComponent :message="message"></SonComponent>
</FatherComponent>
props:['message']
//子组件
<SonComponent >
{{message}}
</SonComponent>
props:['message']
二: 使用 $attrs 和 $listener
//爷组件
<grand-father>
<FatherComponent :message="message"></FatherComponent>
</grand-father>
// 父组件
<FatherComponent >
<SonComponent v-bind="$attrs"></SonComponent>
</FatherComponent>
//子组件
<SonComponent >
{{message}}
</SonComponent>
props:['message']
keep-alive 组件使用
- 默认情况下,动态切换组件时,消失的组件会被销毁,出现的组件会被重新创建.可能会出现组件重复创建销毁的情况,如果在创建组件时,不希望组件被重复的销毁可以使用keepalive 内置组件,将某些自定义组件缓存起来,组件消失时就不在被销毁了.
- 被keep-alive 缓存的组件,会在原有声明周期函数的基础上,在增加两个钩子函数,activated激活时触发:deactivated组件隐藏时触发.
html 结构中
<keep-alive exclude="组件名">
//include:要缓存的组件name属性值:
//exclude: 要排除缓存的组件name;
<component :is="组件结构名">
</keep-alive>
script 结构中:
被keep-alive 缓存的组件,会增减两个钩子函数, activated(激活时触发) deactivated(组件隐藏时触发)
activated(){
console.log('组件被激活,重新显示')
},
deactivated(){
console.log('组件失去激活状态,重新隐藏了。')
}
vue中的事件修饰符
1、prevent:阻止默认事件(常用)
2、stop:阻止事件冒泡(常用)
3、once:事件只触发一次(常用);
4、capture:使用事件的捕获模式;
5、self:只有event.target 是当前操作的元素时,才触发事件。
6、passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
//阻止事件默认行为
<button @click.prevent="info()"></button>
//阻止事件冒泡
<button @click.stop="info()"></button>
//事件只触发一次
<button @click.once="info()"></button>
//使用事件的捕获模式
<button @click.capture="info()"></button>
//e.target 当前操作元素。
<button @click.self="info()"></button>
//passvie 事件的默认行为立即执行,无需等待事件回调执行完毕。
<button @click.passive="info()"></button>
v-model 的修饰符
- .lazy修饰符
- .number修饰符
- .trim修饰符
<!-- lazy修饰符:使得用户在输入数据之后,当数据失去焦点或点击回车时,才会进行数据的更新-->
<input type="text" v-model.lazy="message">
<h2>用户输入的内容: {{message}}</h2>
<!-- number修饰符:将用户输入的类型由string型转为number型-->
<input type="number" v-model.number="num">
<h2>{{num}}: {{typeof num}}</h2>
<!-- trim修饰符:将用户输入的文本信息中开头的空格,结尾的空格都删除-->
<input type="text" v-model.trim="name">
<h2>用户输入的内容: {{name}}</h2>
vue 路由守卫
全局路由守卫
- 前置全局路由守卫:router.beforeEach
- 后置全局路由守卫:router.afterEach
独享路由守卫(只有前置独享路由守卫)
- 前置独享路由守卫:beforeEnter
组件内路由守卫
- beforeRouteEnter,进入该组件时调用;
- beforeRouteLeave,离开该组件时调用;
//配置路由
//全局路由守卫,和独享路由守卫配置再 router.js文件中;
const router=[
{
path:"/",
component:App,
//前置独享路由守卫
beforeEnter:(to,from,next)=>{
}
},
]
//全局前置路由守卫
router.beforeEach((to,from,next)=>{
//这里可以写 鉴定权限的逻辑
//鉴定权限的逻辑内可以写,token验证的逻辑
})
//全局后置路由守卫
router.beforeAfter((to,from,next)=>{
//全局后置路由守卫,这里面可以写 路由跳转后 页签的改变;
})
vue中router-link 自带的active-class类名属性
主要实现的功能
- 点击实现高亮状态
<style>
._active{
background-color : red;
}
</style>
<p>
<router-link v-bind:to = "{ path: '/route1'}" active-class = "_active">Router Link 1</router-link>
<router-link v-bind:to = "{ path: '/route2'}" tag = "span">Router Link 2</router-link>
</p>
active-class 属性的使用
- 在 router-link 上绑定 active-class 属性
- 可能会出现 两个 路由同时高亮的情况;
- 解决: 在 “/” 路径 的 router-link 添加一个 exact 属性
html代码如下:
<router-link to="/" active-class="_active" exact >Test</router-link> |
<router-link to="/Test2" active-class="_active">Test2</router-link> |
<router-link to="/Test3" active-class="_active">Test3</router-link> |
<router-link to="/Test4" active-class="_active">Test4</router-link> |
css代码如下:
> a {
font-weight: bold;
color: #2c3e50;
}
> ._active {
color: #42b983;
}
vue中存入一个对象。
- router路由编程式传参,一般两种方式。一种是query传参,另外一种则是params传参。由于params传参刷新页面,会导致数据丢失。所以采用query传参方式比较多,但当使用query传递对象,数组时,刷新页面会报[object Object],数据无法使用
思路 :
- 先使用JSON.stringify()转为字符串,字符串为单条数据,刷新页面数据就不会丢失,再将字符串通过JSON.parse()转回来就可以。
handelDetails(row) {
this.$router.push({
path: "details",
query: {
row: JSON.stringify(row),//先转为字符串
sataus: "Editor",
},
});
},
created() {
// 接受传递的参数
console.log(this.$route.query.sataus, '单条数据')
console.log(JSON.parse(this.$route.query.row), '对象数据');//转为对象
},
vue 中关于 $emit的使用
参考文章@榴莲不好吃
大概思路:
- 在子组件上自定义一个事件,然后子组件通过触发这个事件来 修改父组件的值.(或者说是 父组件接收子组件中的值.)
子组件:
<template>
<div class="train-city">
<h3>父组件传给子组件的toCity:{{sendData}}</h3>
<br/><button @click='select(`大连`)'>点击此处将‘大连’发射给父组件</button>
</div>
</template>
<script>
export default {
name:'trainCity',
props:['sendData'], // 用来接收父组件传给子组件的数据
methods:{
select(val) {
let data = {
cityname: val
};
this.$emit('showCityName',data);//select事件触发后,自动触发showCityName事件
}
}
}
</script>
父组件:
<template>
<div>
<div>父组件的toCity{{toCity}}</div>
<train-city @showCityName="updateCity" :sendData="toCity"></train-city>
</div>
<template>
<script>
import TrainCity from "./train-city";
export default {
name:'index',
components: {TrainCity},
data () {
return {
toCity:"北京"
}
},
methods:{
updateCity(data){//触发子组件城市选择-选择城市的事件
this.toCity = data.cityname;//改变了父组件的值
console.log('toCity:'+this.toCity)
}
}
}
</script>
vue 中点击路由跳转一个新页面
参考文章@就像风1样
参考文章@浊清。。。
注意:
- resolve页面跳转可用新页面打开
- 2.1.0版本后,使用路由对象的resolve方法解析路由,可以得到location、router、href等目标路由的信息。得到href就可以使用window.open开新窗口了
方法一:给 router-link 标签设置 tag="a" 和 target="_blank" 两个属性.
<router-link tag="a" target="_blank" :to="{name:'detail',query:{goodsId:'1111'}}">热门好货</router-link>
方法二:使用resolve方法将路径转换出来,window来打开
const new = this.$router.resolve({name: '/detail', params: {id: e}})
window.open(new.href,'_blank')
const {href} = this.$router.resolve({
name: 'foo',
query: {
bar
}
})
window.open(href, '_blank')
vue中 动态路由挂载
2.
vue中 v-on监听多个方法
参考文章@Billow_lamb
大致思路:
- 写成一个对象形式: v-on:{ click:fn1() , foucs:fn2() }
例一:
<el-input
v-model="value"
v-on="{
input:onInput,
focus:onFocus,
blur:onBlur
}"
>
</el-input>
例二:
<el-button v-on="{mouseenter: onEnter,mouseleave: onLeave}">鼠标进来1</el-button>
vue中 npm run build 打包后静态资源的配置 vue.config.js的配置
- cli3中bathUrl 已经替换为 publicPath
vue中 vue.config.js 文件的相关配置项
vue中 如何引入自定义字体
参考文章-1@如光不息丶
参考文章-2@汤圆真的好可爱
参考文章-3@昨夜太平长安888注意 :
- 在vue项目引入 .ttf 字体文件的时候 ,一点要点双击该文件 进行 本地的安装…
- 在需要添加该字体的类名下 添加 该属性.
- 全局引入该字体时需要将原有的字体样式清除.如下图
1.首先你要先下载你需要的字体
2. 在你的项目 src 文件下创建一个文件夹
3. 其中 css 文件里面设置一下样式
@font-face {
font-family: "fzqkbysjt";
src: url('FZQKBYSJW.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
4. 在main.js中引入字体样式
// 引入字体样式
import './common/fonts/font.css';
5. 完成以上步骤就可以在需要用到的样式中直接用啦
方法一:在需要用到的样式中直接用
.fongt{
//fzqkbysjt为上面CSS文件中自定义的字体名
font-family: fzqkbysjt;
}
方法二:在 app.vue 文件中全局引入
<style>
@import "./font-style/font.css" ;
body {
font-family: PingFang;
}
</style>
vue中 如何使用 slot 插槽
参考文章@107098
参考文章@前端不释卷leo
(1)匿名插槽:没有名字的插槽
(2)具名插槽:有名字(name属性)的插槽
(3)作用域插槽:子组件中的数据只能子组件访问到,但是我们的插槽往往定义在父组件中,这个时候就需要作用域插槽来打通他们之间的关系,然后就能在父组件中愉快的使用子组件的数据了
- 注意: v-slot 只能添加在 template上 (只有一种例外情况),这一点和已
经废弃的 slot attribute 不同。
//将插槽放在 子组件中。(占个坑位)
<template>
<div>
<slot name="content"></slot>
</div>
</template>
//在父组件中使用插槽
<template>
<div>
<slot-components>
<template #content>
<h3>再组件标签中放入插槽内容</h3>
</template>
第二种写法:
<template v-slot:content>
<h3>再组件标签中放入插槽内容</h3>
</template>
</slot-components>
</div>
</template>
注意:v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已
经废弃的 slot attribute 不同。
插槽传参:
插槽组件:
<template>
<div>
<slot name="content" :info="info"></slot>
</div>
</template>
父组件取参数:
<template>
<div>
<slot-components>
<template #content="slotProps">
<h3>再组件标签中放入插槽内容</h3>
<h3>{{slotProps.info}}</h3>
</template>
第二种写法:
<template v-slot:content="slotProps">
<h3>再组件标签中放入插槽内容</h3>
<h3>{{slotProps.info}}</h3>
</template>
</slot-components>
</div>
</template>
vue中 打包文件后如何打开
- 首先需要再 vue.config.js 文件中配置 下面的配置
- publicPath:’ . / ’
- 再 dist 目录下找到 index.html 文件 双击打开即可.
打包完成后,路由跳转可能会出现空白页,这有可能是路由模式造成的问题.
- 我们只需要将 路由模式改为 hash 模式即可,
- 再项目开发完成后,再将路由改为 history 模式 进行打包.
参考视频@前端程序员来了
vue 中 父组件给子组件绑定 v-model
- 父组件在子组件上添加 v-model 属性
- 子组件可以通过 prop 接收 value 来获取.
- 父组件的model 值改变的时候, 子组件通过 接收的value 值也会发生改变
参考文章@荣光无限
父组件:
<input type="text" v-model="testValue" />
<children v-model="testValue"></children>
子组件:
props: ["value"],
vue .sync 修饰符
- 主要用于父子组件传递 参数.
- 子组件可以 使用 $emit(‘update:属性名’,参数) 来实现子组件改变父组件状态.
- 参考视频@前端小野森森
- 参考文章@王+V
- 技巧:
- 使用 .sync 修饰符时,变量计量使用 ‘ 驼峰 ’ 写法。
- 不使用 .sync 修饰符时, 变量尽量使用 ‘ 短横杠 ’ 写法。
父组件:
<children :testValue.sync="testValue"></children>
子组件触发:
testHandle() {
this.$emit("update:testValue", "新标题");
},
不使用.sync 时,需要这样写.
<children @update:testValue="testValue = $event"></children>
//$event 就是子组件修改过后的参数.
子组件触发:
testHandle() {
this.$emit("update:testValue", "新标题");
},
使用.sync 传递一个对象的时:
<children v-bind.sync="formInline"></children>
//传递对象的时候,不是把formInline整个对象传给子组件,而是把
//formInlin对象里的每一个属性给传给子组件,
//子组件接收的时候,需要分别接收.
Vue 把 this 赋值给 that 操作.
在javascript中,this用以指代当前对象,但是this却不是固定不变的. 在axios 的回调函数中,this 指向axios 本身 , 会出现 this.data 未定义的情况. 我们只需要在 请求外的函数中 把 this 赋值给 that 即可解决这种问题.
参考文章@起个名字好难__
methods:{
searchMusic() {
var that=this;
axios.get("url" + this.query).then(
function (res) {
this.musicList=res.data.result.songs; //undefind
that.musicList=res.data.result.songs; // [ ... ]
}
).catch(function (err) {
console.log(err);
})
}
vue 路由传参的方式
- params 传参
- 路由属性配置传参
- query 传参
注意:
params
传参,刷新页面时,参数 ’会
’ 丢失,query
传参,刷新页面时,参数 ’不会
’ 丢失.params
不能与path
一起使用- !!!
path
路由跳转, 只 支持query
传参.- !!!
name
路由跳转, 同时支持query
传参 和params
传参
// 方式一:params 传参
toDetail(){
this.$router.push({
name:"AntMemu",//值是在配置路由规则时给路由的命名,相当于别名
params:{menu:this.leftMenu}//传递过去的参数
})
}
//配置路由规则
{
path: '/AntMemu',
name: 'AntMemu',//命名路由
component: AntMemu,
props:true//子组件开启props传参
}
//获取路由的参数 (刷新页面,数据会丢失)
menu:this.$route.params.menu//写在data函数中即可
//方式二:路由属性配置传参
toDetail(){
this.$router.push({
path:`/AntMemu/${this.id}`,//值是在配置路由规则时给路由的命名,相当于别名
})
}
//获取路由的参数 (刷新页面 数据不会丢失)
id:this.$route.params.id
//方式三:query 传参
this.$router.push({
name:"AntMemu",//值是在配置路由规则时给路由的命名,相当于别名
query:{id:this.id}
})
//获取路由的参数 (刷新页面,参数不会丢失)
id:this.$route.query.id
vue 项目中如何引入图片
方式一:在data 函数中通过 require 来引入图片。
icon: require('@/assets/images/modules-a5.png'),
方式二:在style 中 引入图片。
background: url(../../assets/images/entry-icon.png) 0 0 no-repeat;
方式三:在html 通过 src 属性引入图片。
<img src="@/assets/images/a-1.png" alt="">
vue 使用 $parent 方法失效。
问题描述:
- 在子组件中使用 $parent 方法 获取父组件中的属性时 , 是一个 undefined.
解决办法:
- 检查 子组件标签外是否 包裹封装过的 组件标签.
- 如果包裹了一层 组件标签就 多一个 .$parent ,以此类推.
- 参考文章@韩大璐
//父组件在调用子组件的时候,在子组件的外层包裹了一层UI组件的某个组件
//子组件外层还包裹了一个iview的Modal组件
<template>
<Modal
v-model="applyStatus"
title="扩容申请"
width="1200"
@on-ok="expansionSubmit">
<children v-if="applyStatus" ref="childApply"></children>
</Modal>
</template>
//此时获取父组件中的属性.
this.$parent.$parent.数据
!!!以此类推,外层包裹了几个组件就需要几个$parent
vue $nextTick 的使用场景
案例一:点击修改dom元素的样式
<div>
<div :id="myid" class="" ref="str">123456789</div>
<button @click="changeColor()">修改dom元素</button>
</div>
methods: {
changeColor(){
this.myid = 'color'
this.$nextTick( () =>{
if (this.$refs.str.id == 'color') {
this.$refs.str.className = 'background'
}
})
}
}
案例二:点击编辑自动获取光标
<div>
<!-- :disabled='isDisabled'最初设置禁止输入属性 -->
<input ref="inputTitle" type="text" value='' :disabled='isDisabled' placeholder="请输入内容">
<button @click="getedit()">编辑</button>
</div>
methods: {
getedit(){
this.isDisabled = false
this.$nextTick(function(){
this.$refs.inputTitle.focus() //获取焦点
})
}
}
案例三:在created阶段vue实例已经创建,但是还不能获取DOM元素。所以在这个阶段要修改dom元素,一般都是要放在this.$nextTick()中
<template>
<div><input ref="str" type="text" :value="string"></div>
</template>
<script>
export default {
data() {
return {
string:'苹果',
};
},
created() {
this.$nextTick(()=>{
this.$refs.str.value = '西瓜'
}
)
},
mounted(){
console.log(this.$refs.str.value);
}
}
</script>
或者使用定时器:
created() {
setTimeout(()=>{
this.$refs.str.value = '西瓜'
},10)
},
Vue 自定义指令v-loadmore实现列表下拉加载 触底加载事件
需求:
- 需要给 el-table 添加触底加载事件
解决办法:- 自定义一个指令 , 然后给需要 触底加载的元素添加 style=“height:230px;overflow:scroll;”
- 参考文章@二熊不是熊二
- main.js 同级创建一个 directive.js文件用于编写 自定义指令
import Vue from 'vue'
Vue.directive('loadmore', {
bind(el, binding) {
var p = 0;
var t = 0;
var down = true;
el.addEventListener('scroll', function() {
//判断是否向下滚动
p = this.scrollTop;
if(t < p){
down = true;
}else{
down = false;
}
t = p;
const scrollDistance = this.scrollHeight - this.scrollTop - this.clientHeight;
if (scrollDistance <= 0 && down) {
// 滚动到底部后触发事件
binding.value()
}
});
}
})
- 在main.js中引入 使用 directives
import * as directives from './directives'
Vue.use(directives)
- 在需要触底加载的元素上使用 自定义指令
- v-loadmore 后加下拉后需要触发的事件loadMore(根据业务需求自行定义)
- 样式中明确该元素的固定高度,设置scroll
- 注意:
必须要在行内样式中 注明元素的高度,同时添加 overflow: scroll 属性
<el-table
v-loadmore="loadMore"
:data="tableData"
style="width: 100%; height: 360px; overflow: scroll"
>
<el-table-column prop="date" label="日期" width="180"> </el-table-column>
<el-table-column prop="name" label="姓名" width="180"> </el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
</el-table>
vue 在 data 中调用 methods 中的方法
思路: 在 data(){} 中保存 this 指向。
data() {
let self = this // 加上这一句就OK了
return {
swiperOption: {
on: {
reachEnd(swiper, event){
self.getRmlp() // getRmlp是vue项目里methods中的函数
}
}
}
}
vue model 属性( 和 data methods 同级的 model 属性)
允许一个自定义组件在使用 v-model 时定制 prop 和 event。
参考文章@小菜菜~~~
实现效果 类似于 v-bind.sync
// 父组件
<followUpModal ref="followUpModal" v-model="followUpModalVisible"></followUpModal>
// 子组件
<el-dialog
:title="modalTitle"
:visible.sync="followUpModalVisible"
width="50%"
center
:modal="false"
class="tool-dialog"
@close="onclose"
destroy-on-closecloseBriefModal
:close-on-click-modal="false">
<div>test</div>
</el-dialog>
model: {
prop: 'followUpModalVisible',
event: 'changeVisible',
},
props: {
followUpModalVisible: {
type: Boolean,
default() {
return false;
},
},
},
methods: {
// 点击关闭抽屉层
onclose() {
// 修改model中的值
this.$emit('changeVisible', false);image.png
},
},
父组件:
<children :testValue.sync="testValue"></children>
子组件触发:
testHandle() {
this.$emit("update:testValue", "新标题");
},
Vue 里给 v-html 元素添加样式
问题: 使用 v-html 时,给 v-html 内容标签设置 css 样式不生效。
解决办法:
- scoped 属性导致 css 仅对当前组件生效(用 css3 的属性选择器+生成的随机属性实现的),而 html 绑定渲染出的内容可以理解为是子组件的内容,子组件不会被加上对应的属性,所以不会应用 css.解决的话把 scoped 属性去掉就行了
- 而如果在组件中使用了 v-html,要为 v-html 中的标签添加 CSS 样式,我们需要在写样式的时候添加>>>:
<style scoped>
>>> p {
font-size: 14px;
line-height: 28px;
text-align: left;
color: rgb(238, 238, 238);
color: #585858;
text-indent: 2em;
}
</style>
Mixin 混入使用
Mixin 的作用
将组件的公共逻辑或者配置提取出来,哪个组件需要用到时,直接将提取的这部分混入到组件内部即可。这样既可以减少代码冗余度,也可以让后期维护起来更加容易。
注意:
Mixin中的数据和方法都是独立的,组件之间使用后是互相不影响的。
- 生命周期执行顺序:先执行 mixin 中的生命周期函数,然后执行组件内的生命周期函数
使用步骤
- 新建一个 mixin.js 文件,用于存放公共逻辑和相关公用配置
全局混入
或者局部混入
,- 最后即可使用 mixin 文件中的相关方法和配置。
// mixin.js 文件
let mixinUtils = {
data() {
return {
message: "我是 mixin 工具",
};
},
methods: {
mixinHandler() {
alert("我是 mixin 方法执行");
},
},
};
export default mixinUtils;
局部混入
// 需要混入 mixin 的组件
import mixinUtils from "@/utils/mixin.js";
export default {
mixins: [mixinUtils],
mounted() {},
data() {
return {};
},
methods: {
doSomething() {
//!! 使用: 调用 mixin 中封装的方法
this.mixinHandler();
},
},
};
全局混入
// main.js 文件
import mixinUtils from "@/utils/mixin.js";
Vue.mixin(mixins);
异步请求的情况
当混合里面包含异步请求函数,而我们又需要在组件中使用异步请求函数的返回值时,我们会取不到此返回值
// mixin.js 文件
export const myMixins = {
components: {},
data() {
return {
num: 1,
};
},
methods: {
getDate1() {
new Promise((resolve, reject) => {
let a = 1;
setTimeout(() => {
resolve(a);
}, 500);
}).then((res) => {
console.log(res, "res");
return res;
});
},
},
};
使用方法文件
// todo.vue
import { myMixins } from "./myMixins.js";
export default {
mixins: [myMixins],
data() {
return {};
},
mounted() {
console.log(this.getDate1, "template1-func_one"); // undefined "template1-func_one"
},
};
解决办法
不要返回结果而是直接返回异步函数
// myMixins.js
export const myMixins = {
components: {},
data() {
return {
num: 1,
};
},
methods: {
async getDate1() {
let result = await new Promise((resolve, reject) => {
let a = 1;
setTimeout(() => {
resolve(1);
}, 500);
});
return result;
},
},
};
Vue 中使用节流和防抖函数
正确使用
<el-button type="primary" @click="saveHandler">保存</el-button>
<script>
import { deepCopy, debounce } from '@/utils/wheelUtils.js'
methods:{
saveHandler: debounce(function () {
this.tableData[this.tableIndex] = this.inputFormList.form
let bodyData = {
...this.formList.form,
busnissKey: this.businessKey,
taskId: this.taskId,
handleUserCode: this.currentUserInfo.userCode,
deliverablesList: this.tableData
}
API.deliverablesSubmitEdit(bodyData).then((res) => {
this.inputDialogVisible = false
this.deliverablesSubmitSearchFn()
this.$message({ type: 'success', message: '操作成功' })
})
}, 1000),
}
</script>
错误使用
错误1:在点击事件中 又声明一个函数 debounce
<el-button type="primary" @click="saveHandler">保存</el-button>
<script>
import { deepCopy, debounce } from '@/utils/wheelUtils.js'
methods:{
saveHandler()=>{
debounce(fn=>{},dealy)
},
}
</script>
错误2:直接在点击事件上 使用debounce
<el-button type="primary" @click="debounce(saveHandler,1000)">保存</el-button>
<script>
import { deepCopy, debounce } from '@/utils/wheelUtils.js'
methods:{
saveHandler()=>{
this.tableData[this.tableIndex] = this.inputFormList.form
let bodyData = {
...this.formList.form,
busnissKey: this.businessKey,
taskId: this.taskId,
handleUserCode: this.currentUserInfo.userCode,
deliverablesList: this.tableData
}
API.deliverablesSubmitEdit(bodyData).then((res) => {
this.inputDialogVisible = false
this.deliverablesSubmitSearchFn()
this.$message({ type: 'success', message: '操作成功' })
})
},
}
</script>
Vue watch 对象/数组 无法比较新旧值
问题: vue 中使用 watch 监听对象/数组,无法比较新旧值
原因: 对象和数组是引用数据类型, new old 指向同一个内存地址.
解决办法: 借助 计算属性,返回一个新对象,监听该计算属性,即可获取前后值的变化
--------------------------watch配置项--------------------------
projectTableDataCp: {
deep: true,
handler(val, old) {
console.log(val, old)
}
}
--------------------------data配置项--------------------------
data(){
return {
projectTableData: [],
}
}
--------------------------computed 计算属性配置项--------------------------
computed: {
projectTableDataCp() {
return JSON.parse(JSON.stringify(this.projectTableData))
}
},
Vue 自定义指令相关使用(directive
)
自定义指令对象 钩子函数
-
bind
: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 -
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 -
unbind
:只调用一次,指令与元素解绑时调用。
钩子函数参数
-
el
:指令所绑定的元素,可以用来直接操作 DOM。 -
binding
:一个对象,包含以下属性:-
name
:指令名,不包括 v- 前缀。 -
value
:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。 -
oldValue
:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 -
expression
:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。 -
arg
:传给指令的参数,例如 v-my-directive:foo 中,参数为 “foo”。 -
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true,bar:true} -
vnode
:Vue 编译生成的虚拟节点。 -
oldVnode
:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
-
注意
:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
自定义指令封装
- 可以创建一个
directives
文件夹,将需要封装的自定义指令文件都放在该文文件夹中. - 在
directives
文件夹中,创建一个index.js
文件,将所有的自定义指令都放在该文件中。 - 在
main.js
中引入该文件,并注册自定义指令。
具体操作
-----------------permission.js 文件-----------------
// 指令封装
import store from '@/store'
function checkPermission(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}
-----------------index.js 文件-----------------
import permission from './permission'
import longpress from './longpress'
// 自定义指令
const directives = {
permission,
longpress,
}
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
// 全局注册的自定义指令
Vue.directive(key, directives[key])
})
},
}
-----------------main.js 文件-----------------
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)
常用自定义指令封装
v-copy 复制粘贴指令
文件封装
const copy = {
bind(el, { value }) {
el.$value = value;
el.handler = () => {
if (!el.$value) {
// 值为空的时候,给出提示。可根据项目UI仔细设计
console.log("无复制内容");
return;
}
// 动态创建 textarea 标签
const textarea = document.createElement("textarea");
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = "readonly";
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value;
// 将 textarea 插入到 body 中
document.body.appendChild(textarea);
// 选中值并复制
textarea.select();
const result = document.execCommand("Copy");
if (result) {
console.log("复制成功"); // 可根据项目UI仔细设计
}
document.body.removeChild(textarea);
};
// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener("click", el.handler);
},
// 当传进来的值更新的时候触发
componentUpdated(el, { value }) {
el.$value = value;
},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {
el.removeEventListener("click", el.handler);
},
};
export default copy;
使用
<template>
<button v-copy="copyText">复制</button>
</template>
<script>
export default {
data() {
return {
copyText: "a copy directives",
};
},
};
</script>
v-longpress 长按触发
文件封装
const longpress = {
bind: function (el, binding, vNode) {
if (typeof binding.value !== "function") {
throw "callback must be a function";
}
// 定义变量
let pressTimer = null;
// 创建计时器( 2秒后执行函数 )
let start = (e) => {
if (e.type === "click" && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler();
}, 2000);
}
};
// 取消计时器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
// 运行函数
const handler = (e) => {
binding.value(e);
};
// 添加事件监听器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
// 取消计时器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
},
// 当传进来的值更新的时候触发
componentUpdated(el, { value }) {
el.$value = value;
},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {
el.removeEventListener("click", el.handler);
},
};
export default longpress;
使用
<template>
<button v-longpress="longpress">长按</button>
</template>
<script>
export default {
methods: {
longpress () {
alert('长按指令生效')
}
}
}
</script>
v-debounce 防抖
文件封装
const debounce = {
inserted: function (el, binding) {
let timer;
el.addEventListener("click", () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
binding.value();
}, 1000);
});
},
};
export default debounce;
使用
<template>
<button v-debounce="debounceClick">防抖</button>
</template>
<script>
export default {
methods: {
debounceClick() {
console.log("只触发一次");
},
},
};
</script>
v-permission 权限指令
文件封装
function checkArray(key) {
let arr = ["1", "2", "3", "4"];
let index = arr.indexOf(key);
if (index > -1) {
return true; // 有权限
} else {
return false; // 无权限
}
}
const permission = {
inserted: function (el, binding) {
let permission = binding.value; // 获取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission);
if (!hasPermission) {
// 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el);
}
}
},
};
export default permission;
使用
<div class="btns">
<!-- 显示 -->
<button v-permission="'1'">权限按钮1</button>
<!-- 不显示 -->
<button v-permission="'10'">权限按钮2</button>
</div>
v-waterMarker 水印指令
文件封装
function addWaterMarker(str, parentNode, font, textColor) {
// 水印文字,父元素,字体,文字颜色
var can = document.createElement("canvas");
parentNode.appendChild(can);
can.width = 200;
can.height = 150;
can.style.display = "none";
var cans = can.getContext("2d");
cans.rotate((-20 * Math.PI) / 180);
cans.font = font || "16px Microsoft JhengHei";
cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)";
cans.textAlign = "left";
cans.textBaseline = "Middle";
cans.fillText(str, can.width / 10, can.height / 2);
parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
}
const waterMarker = {
bind: function (el, binding) {
addWaterMarker(
binding.value.text,
el,
binding.value.font,
binding.value.textColor
);
},
};
export default waterMarker;
使用
<template>
<div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
</template>