开端
最近在恶补vue底层和高级用法相关的知识,找的视频跟着老师学习,本篇文章主要是记录在学习过程中都学习到了哪些内容,理清一下自己的思路。
1 mixins混入
混入(mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
- 类似于<script>中的内容,包含data、钩子函数、computed等
- 当mixins中的data、methods与页面(组件)中的属性名或方法冲突时,以页面(组件)中的属性或者方法为准。
- 当mixins中的钩子函数与页面(组件)中钩子函数同时存在时,同名钩子函数合并,合并后先执行mixins中的钩子函数再去执行页面(组件)中的,若钩子中函数方法有冲突以组件函数方法为主。
1.1 局部引入
Mixins的封装
slider.js 在Mixins文件夹下建一个slider.js文件
// 封装的接口
import { getBannerList } from '@/api/navi'
export default {
data() {
return {
slideTimer: false,
imgList: [],
sliderIndex: 0
}
},
created() {},
methods: {
getBanner(type) {
let param = {
keyword: "",
currentPage: 1,
pageSize: 20,
sort: "desc",
type,
status:1
}
getBannerList(param).then(res=>{
const {list} = res.data
this.imgList = list
list.length>1?this.setTimer(4000):''
})
}
},
beforeDestroy() {
this.slideTimer ? clearInterval(this.slideTimer) : ''
}
}
页面(组件)引用mixins
<template>
<div>11</div>
</template>
<script>
import slider from "@/mixins/slider";
export default {
name: 'Search',
mixins: [slider],
data() {
return {}
},
mounted() {
// 直接使用slide中的方法
this.getBanner('9')
},
methods: {}
}
</script>
1.2 全局引入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
第一种方法:
main.js文件:在Vue.mixin中书写你要封装的mixin方法
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
第二种方法(建议使用):
在main.js中全局引入
index.js文件夹下封装要混入的钩子函数或数据对象
1.3 自定义选项合并策略
optionMergeStrategies 主要用于 mixin 以及 Vue.extend() 方法时对于子组件和父组件如果有相同的属性(option)时的合并策略。Vue.config.optionMergeStrategies是Vue实例的所有方法
自定义选项将使用默认策略,即简单地覆盖已有值。如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies 添加一个函数:
mounted() {
// 接收一下optionMergeStrategies
const myOptionMergeStrategies = Vue.config.optionMergeStrategies
// 使用created这个生命周期函数作为我要合并的对象,并定义了一个 notInCurrentWindow 方法
myOptionMergeStrategies.notInCurrentWindow = myOptionMergeStrategies.created
// 自定义一个方法
const fn = (name, vm) => {
const lifeCycles = vm.$options[name]
if (lifeCycles && lifeCycles.length) {
lifeCycles.forEach(lc => lc.call(vm));
}
// 子组件同样可以使用当前的自定义选项合并
const children = vm.$children
if (children && children.length) {
children.forEach(child => fn(name, child))
}
}
const bind = vm => {
// 监听一下window窗口的进入和离开事件
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
fn('notInCurrentWindow', vm)
}
})
}
const vm = new Vue({
el: document.querySelector('#root'),
template: `
<h1>hello world !</h1>
`,
notInCurrentWindow() {
// todo
alert('notInCurrentWindow 离开当前页面')
},
created() {
// todo
alert('created 离开当前页面')
},
})
// 把自定义选项合并注入到Vue实例
bind(vm)
},
小结:
自定义合并策略本质就是继承了Vue内置的这些api的同时,扩展了自定义的用法
参考文档:
浅谈一下Vue.config.optionMergeStrategies 的自定义选项合并策略 - 掘金
Vue 的自定义选项合并策略,我觉得你一定需要_vue.config.optionmergestrategies-CSDN博客
2 内置组件
前言
Vue中的组件分为两种,一种是自定义组件,另外一种就是内置组件。内置组件是Vue已经封装好的组件,Vue2中共五个内置组件(component、transition、transition-group、keep-alive、slot
),Vue3又新加了两个组件(teleport、suspense
)
2.1 过渡动画动效transition
Vue 提供了 transition
的封装组件,可以给任何元素和组件添加进入/离开过渡。任何元素和组件在下列四种情形中,都可以添加进入或离开过渡动画
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
2.1.1 css过渡
当插入或删除包含在
transition
组件中的元素时,Vue 将会做以下处理:
自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的
nextTick
概念不同)
<template>
<div id="app">
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
show:false
}
},
created() {
},
methods: {
}
}
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>
注:
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的
<transition>
,则v-
是这些类名的默认前缀。如果你使用了<transition name="my-transition">
,那么v-enter
会替换为my-transition-enter
。你的动画样式就可以写在中:
- .
my-transition-enter
.my-transition-enter-active
.my-transition-leave
.my-transition-leave-active
css过渡与动画的区别
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter
类名在节点插入 DOM 后不会立即删除,而是在 animationend
事件触发时删除。
/*css过渡 —— transition*/
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
/* css动画 animation */
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
2.1.2 过渡类名
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。
v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
自定义过渡类名
自定义过渡类名的优先级比普通类名高
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
<div id="example-3">
<button @click="show = !show">
Toggle render
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
同时使用过渡与动画
Vue 为了知道过渡的完成,必须设置相应的事件监听器。它可以是 transitionend
或 animationend
,这取决于给元素应用的 CSS 规则。如果你使用其中任何一种,Vue 能自动识别类型并设置监听。
但是,在一些场景中,你需要给同一个元素同时设置两种过渡动效(需要指定Vue的监听类型),比如 animation
很快的被触发并完成了,而 transition
效果还没结束。在这种情况中,你就需要使用 type
attribute 并设置 animation
或 transition
来明确声明你需要 Vue 监听的类型。l
<transition
name="custom-classes-transition"
type="animation"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
2.1.3 JavaScript钩子函数
1、当只用 JavaScript 过渡的时候,在
enter
和leave
中必须使用done
进行回调。否则,它们将被同步调用,过渡会立即完成。2、推荐对于仅使用 JavaScript 过渡的元素添加
v-bind:css="false"
,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
2.1.4 appear初始渲染过渡
appear属性与appear钩子的属性是一样的,都会生成初始渲染过渡
appear属性
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class" (2.1.8+)
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>
appear钩子
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>
3 插槽(组件中的)
1、v-slot:header 等于#header
2、v-slot:header="header" 将header插槽中的数据绑定到header变量中,可在父组件中直接使用
#header 无法实现将header插槽中的数据绑定到变量中
index.vue
<template>
<div style="height: 90vh;text-align: center;margin-top: 50px;">
<Comp>
<!-- v-slot:header 等于#header -->
<!--
v-slot:header="header" 将header插槽中的数据绑定到header变量中
#header 无法实现将header插槽中的数据绑定到变量中
-->
<template v-slot:header="header">
<div>调用组件头部 ----------- {{ header.msg}}</div>
</template>
<!-- 默认绑定 相当于v-slot:default -->
<div>
{{message}}
</div>
<template v-slot:footer="footer">
<div>调用组件尾部 ----------- {{ JSON.stringify(footer)}}</div>
</template>
</Comp>
</div>
</template>
<script>
import Comp from './comp.vue'
export default {
components:{Comp},
data(){
return{
message:'内部组件'
}
},
medthods:{
}
}
</script>
comp.vue 组件
<template>
<div>
<div class="header">
<!-- 组件内默认数据 -->
<!-- :msg 等于v-bind:msg 效果类似将参数绑定到组件中header下的msg变量上 -->
<slot name="header" :msg="msg">组件头部</slot>
</div>
<div class="content">
内容区域
</div>
<div class="footer">
<!-- 组件内默认数据 -->
<!-- msg-footer 绑定默认值 效果类似将参数绑定到组件中header下的msgFooter变量上 -->
<slot name="footer" msg-footer="组件尾部数据">组件尾部</slot>
</div>
</div>
</template>
<script>
export default {
name:'Comp',
data(){
return{
msg:'组件内部头部数据'
}
},
medthods:{
}
}
</script>
<style>
.header,.footer{
color: red;
}
</style>
4 插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
-
添加全局方法或者 property。如:vue-custom-element
-
添加全局资源:指令/过滤器/过渡等。如 vue-touch
-
通过全局混入来添加一些组件选项。如 vue-router
-
添加 Vue 实例方法,通过把它们添加到
Vue.prototype
上实现。 -
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
4.1 使用插件
全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成:
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
也可以传入一个可选的选项对象:
Vue.use(MyPlugin, { someOption: true })
注:
1、Vue.use() 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。
2、Vue.js 官方提供的一些插件 (例如
vue-router
) 在检测到Vue
是可访问的全局变量时会自动调用Vue.use()
。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用Vue.use()
:
CommonJS :
一个文件就是一个模块。
另外本文中的示例代码需要在node.js环境中方可正常运行,否则将出现错误。事实上ES6已经出现了模块规范,如果使用ES6的模块规范是无需node.js环境的。因此,需要将commonJS规范和ES6的模块规范区分开来。
CommonJS规范其实就一句话:模块加载机制被称为CommonJS规范。
在这个规范下,每个.js文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,hello.js和main.js都申明了全局变量var s = 'xxx',但互不影响。
awesome-vue 集合了大量由社区贡献的插件和库。
4.2 开发插件
Vue.js 的插件应该暴露一个 install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
Vue 构造器
options 可选的选项对象
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法(全局方法)
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
// 注册MyPlugin插件
Vue.use(MyPlugin, { someOption: true })
5 过滤器
5.1 定义与引入过滤器
5.1.1 全局引入
import Vue from 'vue'
import App from './App.vue'
import * as filters from './filters/index'
// 注册过滤器
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
使用过滤器
<template>
<div id="app">
<div id="demo">
<div>{{ 'hello' | toUppercaseFilter}}</div>
</div>
</div>
</template>
5.1.2 局部引入
第一种方法:在组件中引入外部封装的filter.js
第二种方法:直接在组件中的filters中定义要用的filters方法
<template>
<div id="app">
<div id="demo">
<div>{{ 'hello' | toUppercaseFilter }}</div>
<div>{{ 'hello' | capitalize}}</div>
</div>
</div>
</template>
<script>
import { toUppercaseFilter } from '@/filters/index';
export default {
name: 'App',
filters: {
toUppercaseFilter,
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
}
</script>
5.2 过滤器使用
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind
表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
5.2.1 过滤器参数
这里,filterA
被定义为接收三个参数的过滤器函数。其中 message
的值作为第一个参数,普通字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数。
默认管道符'|'前的message变量为过滤器的第一个参数,过滤器传递的参数从第二个开始
<div>{{ 'hello' | toUppercaseFilter('打招呼') }}</div>
5.2.2 多个过滤器一起使用
{{ message | filterA('arg1', arg2) }}
这里,filterA
被定义为接收三个参数的过滤器函数。其中 message
的值作为第一个参数,普通字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数。
{{ message | filterA | filterB }}
在这个例子中,filterA
被定义为接收单个参数的过滤器函数,表达式 message
的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB
,将 filterA
的结果传递到 filterB
中。
6 自定义指令 (directives)
什么情况下使用自定义指令?
在实际开发中,开发需求各种各种系统提供的指令(v-on、v-bind、v-show的等)不能百分百的满足实际的需求,这种时候开发人员就可以使用自定义指令开发符合需求的指令来使用。
6.1 自定义指令
自定义指令分为全局指令和局部指令
系统指令在设计过程中是需要操作dom,在开发自定义指令的过程中也是操作DOM元素
6.1.1 全局指令
main.js文件
import Vue from 'vue'
import App from './App.vue'
// 如果你只需要执行绑定的 bind 和 update 两个事件,vue指令定义也配置了简写方式.
Vue.directive('my-name',(el) => {
el.style.color = 'pink'
el.style.backgroundColor = 'yellow'
})
new Vue({
render: h => h(App)
}).$mount('#app')
6.1.2 局部指令
指令有两种写法:函数式和对象式
**函数式**
写法上比较精简,但是细节上不好处理,功能相当于对象式中的bind与update
函数什么调用调用
1.指令与元素成功绑定时(一上来) 。
2.指令所在的模板被重新解析时(n值被修改时)
<template>
<div>改变后的n值:<span>{{n}}</span></div>
<div>改变后的n值扩大十倍:<span v-big="n"></span></div>
<button @click="n++">n++</button>
</template>
<script>
export default {
data(){
return {
name:'',
}
},
// 注册自定义组件
directives:{
// 自定义函数何时会被调用?
// 1.指令与元素成功绑定时(一上来) 。
// 2.指令所在的模板被重新解析时
// 将值扩大10倍后,填写到控件中
big(el, binding) {
console.log(el,binding)
// el获取当前控件 binding.value可以取到绑定的值
// 初始化时把绑定的值赋值给控件
el.innerText = binding.value *10
}
}
}
</script>
**对象式**
写法上比较繁琐,但可以很方便的处理细节
现在有一个需求:要求实时更改输入框的值,初始化时默认聚焦
用函数式写
初始化时元素可赋值可绑定,但是没有聚焦效果,这因为vue在初始化时元素还没有渲染到页面上,这个时候我们去聚焦就会导致失败
<div>改变后的n值:<span>{{n}}</span></div>
<div>改变后的n值扩大十倍:<span v-big="n"></span></div>
<button @click="n++">n++</button>
<input type="text" placeholder="" v-nfocus="n" v-model="n" />
nfocus(el, binding) {
el.value = binding.value
el.focus(); // 元素聚焦
},
用对象式解决此问题
在元素插入页面时,去设置聚焦
<div>改变后的n值:<span>{{n}}</span></div>
<div>改变后的n值扩大十倍:<span v-big="n"></span></div>
<button @click="n++">n++</button>
<div>
<input class="inputClass" type type="text" placeholder="" v-nfocus="n" v-model="n" />
</div>
// 实时更改输入框的值,初始化时默认聚焦
nfocus: {
// 指令和控件绑定时(初始化渲染时)
bind(el, binding) {
el.value = binding.value;
},
// 指令所在元素被插入页面时
inserted(el, binding) {
el.focus()
},
//指令所在的模板被重新解析时
update(el, binding) {
el.value = binding.value;
},
}
6.2 指令的钩子函数
6.2.1 钩子函数
自定义指令一共有五个钩子:
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置(刷新页面会调用)。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入document中)(刷新会调用,但在bind函数之后调用)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新(更新) -
componentUpdated
:被绑定元素所在模板完成一次更新周期时调用(更新完成)。 -
unbind
:只调用一次,指令与元素解绑时调用
首次渲染调用的钩子函数:
6.2.2 钩子函数中的参数
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 编译生成的虚拟节点。移步 [dataset
](cn.vuejs.org/v2/api/>VNo… API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用
directives:{
myName:{
bind() {
console.log('---bind--');
},
inserted() {
console.log('inserted');
},
update(el,binding,vnode,oldVnode) {
console.log('update');
console.log('---el--',el);
console.log('---binding--',binding);
console.log('---vnode--',vnode);
console.log('---oldVnode--',oldVnode);
},
componentUpdated() {
console.log('componentUpdated');
},
unbind() {
console.log('unbind');
},
},
},
总结:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。例如
v-big-number,函数或对象用引号"big-number"(el,binding){},"big-number":{}
3.函数式和对象式中的this都指向windows
6.3 应用与实现
在实际开发中,常用的自定义指令v-copy复制,v-longpress
长按指令, v-premission权限校验,v-draggable拖拽等
6.3.1 权限校验v-premission
在js中封装权限校验
directive文件夹下的index.js中的代码
import permission from './permission'
const install = function(Vue) {
// 监听v-has
Vue.directive('has', permission)
}
if (window.Vue) {
window['has'] = permission
Vue.use(install); // eslint-disable-line
}
permission.install = install
export default permission
directive文件夹下的ipermission.js中的代码
export default {
inserted(el, bindling) {
// bindling.value为指令绑定值 也就是v-has绑定的值
let perVal = bindling.value;
if (bindling.value) {
// 当按钮是btn_add,btn_delete时可以有权限
let pers = ['btn_add', 'btn_delete'];
// hasPer:true为有权限 false为无权限
let hasPer = pers.some(item => {
return item == perVal
});
// 无权限隐藏此元素
if (!hasPer) {
el.style.display = "none"
}
}
}
}
main.js中引入权限控制
在index.vue中配置v-has按钮权限
<template>
<div id="app">
<div id="demo">
<!-- 自定义权限 -->
<div>
<!-- 若有动态的权限配置 v-has也可以绑定动态值来控制权限 -->
<button v-has="'btn_add'">添加</button>
<button v-has="'btn_edit'">编辑</button>
<button v-has="'btn_delete'">编辑</button>
</div>
</div>
</div>
</template>
实现效果:编辑无权限就不在展示
参考资料:
03.VUE:自定义指令(directives )选项的用法 - 掘金
7 VueX与pinia
VueX与pinia都是Vue.js提供的状态管理工具,VueX是Vue2使用的,而Vue3推出了pinia管理工具。Vue 2 的终止支持时间是 2023 年 12 月 31 日 ,仍可继续使用但是Vue2不会再迭代新的版本了,Vue3将成为vue的主流版本,因此pinia状态管理器往后也将替代VueX成为主流。
7.1 搞懂VueX
7.1.1 VueX是什么?
官方给的定义是:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
VueX通俗来说就是状态的集中管理,当要使用的数据嵌套比较深不好取用时,我们就可以把这些参数放到store中集中的进行管理,修改与取用也直接的去store中找参数。
如下示例:组件间传值常用props与emit(当然父子组件通信方式还有好几种,本文就不在赘述了)来实现,但是当一个组件套一个组件,父组件如果想取用子孙组件的参数就需要子组件取子孙组件参数、父组件取子组件参数,参数交互比较麻烦,这个时候就可以用到VueX来进行状态管理
什么情况下使用VueX
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
7.1.2 安装
1、npm
npm install vuex@next --save
2、直接下载或者引入
3、yarn
yarn add vuex@next --save
7.1.3 核心概念
VueX的工作原理图:
围绕上图流程,简要说明一下各核心流程点的主要功能
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
- commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
- state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法,该方法类似于计算属性computed。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
默认规则:
1、提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。actions不能直接修改state
2、应用层级的状态应该集中到单个 store 对象中。
3、异步逻辑都应该封装到 action 里面。
store
vuex 中最关键的是store对象,这是vuex的核心。可以说,vuex这个插件其实就是一个store对象,每个vue应用仅且仅有一个store对象。
store对象包含state、actions、mutations、modules、getter
创建一个store对象:
const store = new Vuex.Store({...});
一个完整的store的结构是这样的(如果项目比较简单,我们可以直接这样写 )
const store = new Vuex.Store({
state: {
// 存放状态
},
getters: {
// state的计算属性
},
mutations: {
// 更改state中状态的逻辑,同步操作
},
actions: {
// 提交mutation,异步操作
},
// 如果将store分成一个个的模块的话,则需要用到modules。
//然后在每一个module中写state, getters, mutations, actions等。
modules: {
a: moduleA,
b: moduleB,
// ...
}
});
复杂或简单项目我们可以采用以下结构:
store文件夹下的index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions';
Vue.use(Vuex)
// 读取modules文件夹下的文件
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {});
const store = new Vuex.Store({
modules,
getters,
actions
})
export default store
参考资料: