Detailed Explanation of Vue Custom Instructions - Example of Button Level Permissions

Table of contents

First briefly introduce the custom command

Built-in directives and custom directives

Just give an example

Write a Permission command

Common permission granularity

The common schemes are

Compared with v-if, the advantages are as follows

How to achieve

How to use

Effect

Strengthen the function

Consumer

instruction

checkPermission check permission

look at the effect

Supplement other auxiliary methods

share


First briefly introduce the custom command

Before introducing the Permission command, let's briefly understand what a custom command is.

Vue's custom directives Vue.directiveare created through methods.

  • bind: Called when the directive is bound to the element for the first time. A one-time initial setup can be performed here.

  • inserted: Called when the bound element is inserted into the parent element. Note that the parent element may not yet exist, so DOM manipulation cannot be performed.

  • update: Called when the component where the bound element is located is updated, but may occur before its child components are updated. You can compare the values ​​before and after the update and perform corresponding operations.

  • componentUpdated: Called after the component where the bound element is located and its subcomponents are all updated. Actions can be performed, such as updating the DOM.

  • unbind: Called when the directive is unbound from the element. Cleanup operations can be performed.

In addition to the above hook functions, other properties and methods can also be defined in the instruction object:

  • name: The name of the directive, used to bind the directive in the template.

  • value: The binding value of the directive, which can be an expression or a variable.

  • arg: The parameter of the command, used to pass additional information.

  • modifiers: Modifier object, which can be used to pass additional tag information.

  • el: The element to which the directive is bound.

  • vm: The Vue instance to which the directive belongs.

  • expression: The expression of the instruction.

  • context: The Vue instance of the context where the directive is located.

Here is an example of a simple custom directive:

 
 
Vue.directive('my-directive', {
  bind(el, binding, vnode) {
    // 初始化设置
  },
  inserted(el, binding, vnode) {
    // 元素插入父元素时调用
  },
  update(el, binding, vnode) {
    // 组件更新时调用
  },
  componentUpdated(el, binding, vnode) {
    // 组件及子组件更新后调用
  },
  unbind(el, binding, vnode) {
    // 解绑时调用
  }
});

Use custom directives in templates:

 
 
<div v-my-directive="value"></div>

Built-in directives and custom directives

The commonly used built-in instructions are  v-if, v-show, v-bind, v-foretc., which have been used by everyone, so I won’t go into details here.

ask a question first

Q: When is the Vue custom instruction executed?

A: It is parsed and executed at runtime

What exactly happened between the built-in and the custom directive?

It may be because I didn't have a deep understanding before, and I always thought that both built-in instructions and custom instructions were resolved at compile time.

From the figure below, it can be seen 编译时that the built-in instruction  Render has been executed at that time, but 运行时the parsing of the custom instruction starts after that.

Just give an example

Frequently encountered interview questions, "What's the difference between v-if and v-show", the difference is not a simple one. The former is to directly delete the DOM, and the latter is to change the display attribute.

v-ifVNode It has been judged in the template compilation stage before   generation  , and v-show it is also parsed at compile time, but  v-if the rendering elements are determined at compile time, and v-showdisplayed and hidden according to conditions at runtime.

15.1.1 中 v-if 指令的原理概述 The following is an excerpt of part of the code in "Introduction to Vue.js in simple terms" 

v-if

 
 
<!-- 模板 -->
<li v-if="has">if</li>
<li v-else>else</li>
 
 
// 编译后
(has)
    ? _c('li',[_v("if")])
    : _c('li',[_v("else")])

 When has is true, the first li element will be created  VNode.

  • _c Refers to createElement, which is used to create component nodes.

  • _v Is used to create text VNode

v-show

Let's take a look again  v-show, it is obviously an instruction, which render is the same as when we write instructions in a function, but in this way, v-if must be a syntactic sugar, because it is not a real instruction.

image.png

That is to say, the timing of custom instructions cannot be parsed before the built-in instructions (of course, if you directly modify the compile time or other tricky operations through vue-template-compiler, just take it for granted).

Interested friends can go to  vue-template-explorer.netlify.app/  to try

After we really understand the execution timing of the built-in instructions, we will write a custom instruction next.

Write a Permission command

Common permission granularity

Before implementing the Permission command, we need to understand the common granularity of permissions. Below are some common scenarios.

The common schemes are

  • Encapsulate the button into a component, and then pass parameters through props to control whether it is displayed or operable

  • Encapsulate a component, pass the button through the slot form, and then pass the value control through the prop

  • Write a custom instruction to control whether the element is displayed by value

Compared with v-if, the advantages are as follows

  1. The permission control function can be easily extended.

  2. Support multiple permission codes, support asynchronous access to permissions, etc.

  3. The semantic effect is more obvious.

How to achieve

Write a simple display and hide control first, which can be controlled by responsive data.

 
 
// 全局自定义指令
Vue.directive("permission", {
  // 在元素被插入到 DOM 中时触发
  inserted(el, binding) {
    // 如果绑定值为 false,则从父节点中移除元素
    if (!binding.value) {
      el.parentNode.removeChild(el); // 移除元素
    }
  },
  // 在元素更新时触发
  update(el, binding) {
    // 如果绑定值为 true
    if (binding.value) {
      // 如果元素没有父节点(即之前被移除了)
      if (!el.parentNode) {
        // 将元素插入到原来的位置
        el.__v_originalParent.insertBefore(el, el.__v_anchor || null);
      }
    } else {
      // 如果元素有父节点
      if (el.parentNode) {
        // 创建一个注释节点作为替换元素的占位符
        el.__v_anchor = document.createComment("");
        el.__v_originalParent = el.parentNode;
        // 用注释节点替换原来的元素
        el.parentNode.replaceChild(el.__v_anchor, el); 
      }
    }
  },
});

In  inserted the hook, when the element is inserted into the DOM, according to the state of the binding value, if yes  false, remove the element from the parent node to achieve the hidden effect.

In  update the hook, when the element is updated, according to the state of the binding value, if it is  trueand the element was removed before (no parent node), then insert the element to the original position to achieve the display effect. If the binding value is  falseand the element currently has a parent node, create a comment node as a placeholder for the replacement element, and replace the original element with the comment node to achieve the hidden effect.

How to use

The way to use is very simple, just pass in the required value in the command.

 
 
<template>
  <div id="app">
    isVisible: {
   
   { isVisible }}
    <button v-permission="isVisible">自定义指令</button>
    <button @click="isVisible = !isVisible">Toggle</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isVisible: true,
    };
  },
};
</script>

Effect

It can be seen that the effect is basically the  v-if same as that of . Of course, this is a runtime operation, and there are still differences in principle.

But if it's just a simple variable, ours  v-permission doesn't make much sense. It can really be controlled directly  v-if , because we need asynchronous requests and unified management. This command will be strengthened next.

Strengthen the function

There are more than 4 buttons on the table page in the common middle and background, as shown in the figure below, including adding, deleting, editing, and querying. From the business, and out of the business.

Generally, it is necessary to perform permission control under a certain module, function, or certain route with add| delete| update| query, or some other operations.

So our button permission should be  页面::新增, and then we need a checkPermission method to provide support.

Assume that all button-level permissions can be obtained in a certain API, and the format is consistent with the format agreed by the server-side classmates.

 
 
{
    "orders": [
        "add",
        "update",
        "delete",
        "query",
        "detail",
        "enable",
        "disable"
    ]
}

In the above JSON, orders is used as the order page, and the corresponding Actions that can be operated are placed in the array. If it does not exist, it is considered that there is no such permission.

Consumer

The input from the consumer orders::update means that it needs to obtain订单的编辑权限

 
 
<button v-permission="'orders::update'">Update</button>

instruction

In the instruction area, we need to change the hook of the instruction (the example here is only adjusted in inserted) to asynchronous waiting. When we first enter the  insertedhook, we need to  display set it to  none, or  bind do this in the hook, otherwise there will be a flash effect.

checkPermissionThen call the incoming value  here binding.value , that is  orders::update , wait for the return value here  checkPermission , and then determine whether this element needs to be deleted.

 
 
Vue.directive("permission", {
  async inserted(el, binding) {
    // 默认先改为 none,等数据回来之后再进行操作,你通过class来控制
    el.style.display = "none";
    const hasPermission = await checkPermission(binding.value);
    el.style.display = "";
    if (!hasPermission) {
      el.parentNode?.removeChild(el); // 移除元素
    }
  },
  ...
});

checkPermission check permission

Receive  orders::update, by splitPermissionStringdividing into ordersand update.

Then send the request, but if there are multiple calls to a page at the same time v-permission, there may be a problem of repeated requests. 

So use  controller.hasRequested and  controller.task to control the repeatability of the request. This is necessary, and there is currently JavaScriptno similar (lock mechanism) found in and Javain wait/notify. So it's just a lock mechanism that imitates Java 

 
 
const controller = {
  // 是否发过请求
  hasRequested: false,
  // 权限集合
  permissionList: [],
  // 真正的请求任务
  task: null,
};

const checkPermission = async (value = null) => {
  // 截取对应的模块和操作
  const [module = null, operate = null] = splitPermissionString(value) ?? [];

  // 判断模块和操作是否存在
  if (!module || !operate) return false;

  // 判断是否发送过请求
  if (!controller.hasRequested) {
    controller.hasRequested = true;
    controller.task = getPermissionsApi();
  }

  // 获取权限数据,进行赋值
  controller.permissionList = await controller.task ?? [];

  // 判断是否有权限
  return controller.permissionList[module]?.includes(operate) ?? false;
};

look at the effect

It can be seen that it is hidden by default before sending the request, and then waits for the interface data to come back, re-determines which ones do not have permission, and then controls the display or deletes the element node.

Supplement other auxiliary methods

 
 
// 模拟请求
const getPermissionsApi = async () =>
  fetch(`/api.json?t=${new Date().getTime()}`).then((res) => res.json());

// 分割字符串
const splitPermissionString = (str) => {
  try {
    if (typeof str === "string" && str.includes("::")) {
      const [firstPart, secondPart] = str.split("::");
      return [firstPart.trim(), secondPart.trim()];
    } else {
      throw new Error("Invalid permission string or delimiter not found");
    }
  } catch (error) {
    console.error(error.message);
    return [];
  }
};

share

Finally, I would like to recommend a platform for learning the front end, and you can watch all the videos after opening the black gold card! It only costs 39 yuan.

There is contact information below

 

 

Guess you like

Origin blog.csdn.net/m0_68036862/article/details/131917182