vue2中组件互相调用实例methods中的方法实现详解_vue.js

来源:脚本之家  责任编辑:小易  
目录
前言:开始前:使用第一步、定义全局mixin第二步添加代码提示d.ts声明文件在项目下新建jsconfig.json添加shims-vue.d.ts声明文件添加event-keys.d.ts声明文件第三步编写webpack插件新建global-dispatch.js 自动生成event-keys.d.ts添加vue-path-loader.js webpack loader文件配置vue.config.js后记仓库地址总结

前言:

大家都知道在vue2中相互调用组件的方法是很麻烦的一件事。比如说要调用子孙组件的的方法,需要refs,refs一层一层往下面找;调用上层组件的可以在组件上挂载,一层可以用v-on 和 emit解决,多层可以用provide和inject,要是多个兄弟组件间呢? 唯一方便点的就是eventBus,bus每次on事件后,都要记得在beforeDestory里面on事件后,都要记得在beforeDestory里面on事件后,都要记得在beforeDestory里面off事件,不然会多次on事件,在on事件,在on事件,在emit触发的时候就会执行多次,导致bug,另外在项目里面bus使用的多了,$emit时具体调用的是在调用的哪一个,在出现重名事件时就会让人非常头疼了,于是我就试着自己实现解决这个问题。

开始前:

我打算用全局mixin来做这个功能。本来打算在每个组件里面定义name来绑定methods的,考虑到这样做每个vue组件里面都要自己手动定义name,而且也容易存在重名的情况,于是我就打算用vue组件所在的路径来做,我发现vue组件实例上$options的prototype下有个__file属性记录了当前文件的路径,当时生产环境下就没有了,于是我想到了写个weboack插件来实现,另外吐槽下webpack的钩子真的多,示例不清晰。vue2项目大多数都是使用的js,代码提示用jsconfig.json结合types, js代码里面用注释jsdoc语法添加代码提示。

使用

直接在组件里面调用globalDispatch方法,有代码提示的哦,考虑到一个组件可能同时调用了多次,所有可以多传一个eKey 进行精确emit。在组件上可以进行eKey绑定(也可以写e-key)。

第一步、定义全局mixin

import Vue from "vue";

const DEFAULT_E_KEY = "__default";
/**
 * 方法合集
 * @type {Record<string, {eKey: string; handler: function}[]>}
 */
const events = {};

/**
 * 全局调用event的mixin
 * @type {Vue & import("vue").ComponentOptions}
 */
const globalDispatch = {
  created() {
    const attrs = this.$attrs;
    const eKey = attrs.eKey ?? attrs["e-key"];
    const filePath = this.$options.__file ?? this.$options.__filePath;
    filePath && addEvents(filePath, this, eKey);
  },
  destroyed() {
    const filePath = this.$options.__file ?? this.$options.__filePath;
    filePath && removeEvents(filePath, this);
  }
};

/**
 * 监听方法
 * @param {string} filePath 获取到的路径
 * @param {Vue} vm vue组件实例
 * @param {string=} eKey event key
 */
function addEvents(filePath, vm, eKey = DEFAULT_E_KEY) {
  const methods = vm.$options.methods;
  if (methods) {
    Object.entries(methods).forEach(([key, handler]) => {
      handler = handler.bind(vm);
      handler.vm = vm;
      const eventKey = `${filePath}:${key}`;
      const event = { eKey, handler };

      if (events[eventKey] && events[eventKey].length) {
        events[eventKey].push(event);
      } else {
        events[eventKey] = [event];
      }
    });
  }
}

/**
 * 移除方法
 * @param {string} filePath 获取到的路径
 * @param {Vue} vm vue组件实例
 */
function removeEvents(filePath, vm) {
  Object.keys(events).forEach(key => {
    if (key.startsWith(filePath)) {
      events[key] = events[key].filter(v => v.handler.vm !== vm);
    }
  });
}

/**
 *
 * @param {import("../../types/event-keys").EventKeys | import("../../types/shims-vue").EventParams} params
 * @param  {...any} args
 * @returns
 */
Vue.prototype.globalDispatch = function dispatch(params, ...args) {
  let eventKey,
    eKey = DEFAULT_E_KEY;
  if (typeof params === "string") {
    eventKey = params;
  } else if (typeof params === "object") {
    eventKey = params.target;
    eKey = params.eKey ?? DEFAULT_E_KEY;
  }

  const eKeyMsg = eKey !== DEFAULT_E_KEY ? `eKey:${eKey},` : "";

  if (
    !eventKey ||
    typeof eventKey !== "string" ||
    !/^[^:]*:[^:](.*){1}$/.test(eventKey)
  ) {
    throw new Error(`${eKeyMsg}eventKey:${eventKey}, 参数不正确!`);
  }

  const handlers = events[eventKey]?.filter(v => v.eKey === eKey);
  if (handlers && handlers.length) {
    const results = handlers.map(v => v.handler(...args));
    if (results.length === 1) return results[0];
    return results.map(result => ({ eKey, result }));
  }

  const method = eventKey.split(":")[1];
  throw new Error(`${eKeyMsg}method:${method},该方法未找到!`);
};

export default globalDispatch;

这个文件主要添加所有的组件的methods到events里面,在Vue.prototype上挂载globalDispatch 方法,方便在vue组件上使用。

第二步添加代码提示d.ts声明文件

在项目下新建jsconfig.json

我用的时vue2.7版本写的,主要时include把types文件夹的文件加进来

{
  "compilerOptions": {
    "moduleResolution": "node",
    "target": "esnext",
    "baseUrl": ".",
    "allowJs": true,
    "sourceMap": false,
    "strict": true,
    "jsx": "preserve",
    "module": "ESNext",
    "paths": {
      "@/*": ["./src/*"]
    },
    "lib": ["DOM", "ESNext"]
  },
  "vueCompilerOptions": {
    "target": 2.7
  },
  "exclude": ["node_modules", "dist"],
  "include": ["src/**/*.js", "src/**/*.vue", "types/**/*.ts", "types/**/*.d.ts"]
}

添加shims-vue.d.ts声明文件

在types文件夹下新建shims-vue.d.ts, 因为globalDispatch需要支持两种传参形式,所以使用重载

import Vue from "vue";
import { EventKeys } from "./event-keys";

export type EventParams = { target: EventKeys; eKey: string };

function globalDispatch(eventKey: EventKeys, ...args: any[]): any;
function globalDispatch(eventParams: EventParams, ...args: any[]): any;

declare module "vue/types/vue" {
  interface Vue {
    /**
     * 全局互相调用event的dispatch
     */
    globalDispatch: typeof globalDispatch;
  }
}

添加event-keys.d.ts声明文件

在types文件夹下新建event-keys.d.ts, 这个文件是用来给globalDispatch的第一个参数做代码提示的,手动写可以,写个webpack插件自动读取vue文件的路径和方法自动生成更好,下面会贴出来。

export type EventKeys = "src/App.vue:onClick" | "src/views/IndexView.vue:test";

第三步编写webpack插件

在项目根目录下新建plugins文件夹

新建global-dispatch.js 自动生成event-keys.d.ts

开发者模式下才需要生成event-keys.d.ts,先递归找出所有的vue文件的路径,然后读取文件,用acorn库解析,找出文件的methods里的所有方法名,用prettier格式化后写入到event-keys.d.ts,在项目启动和文件变化后都会执行,在添加methos里新方法或删除后,会执行写入。

const fs = require("fs");
const path = require("path");
const acorn = require("acorn");
const prettier = require("prettier");
const prettierConfig = require("../prettier.config");

/**
 * @typedef {import("webpack/lib/Compiler")} Compiler
 */

const PLUGIN_NAME = "global-dispatch";
const KEYS_PATH = path.resolve(__dirname, "../types/event-keys.d.ts");

class TransformFilePathPlugin {
  /**
   * @param {Compiler} compiler
   * @returns {void}
   */
  apply(compiler) {
    compiler.hooks.done.tap(PLUGIN_NAME, () => {
      process.env.NODE_ENV === "development" && writeEventKeys();
    });
  }
}

function writeEventKeys() {
  const vueFilePaths = getFilePath();
  writeVueKeyPaths(vueFilePaths);
}

/**
 * 缓存内容,防止重复写入
 */
let keysContentCache = fs.readFileSync(KEYS_PATH, "utf-8");

/**
 * 写入__filePath到type Key文件
 * @param {string[]} paths 路径集合
 */
function writeVueKeyPaths(paths) {
  let keysContent = "export type EventKeys =";
  const keys = [];

  paths.forEach(p => {
    let content = fs.readFileSync(getsrcPath(p), "utf-8");
    const scriptMatch = content.match(/<script/g);
    if (!scriptMatch) return;

    const startIndex = content.indexOf("export default");
    if (startIndex < 0) return;

    const endIndex = content.indexOf("</script>", startIndex);
    content = content.substring(startIndex, endIndex);

    const ast = acorn.parse(content, { sourceType: "module" });
    const defaultExportAst = ast.body.find(
      v => v.type === "ExportDefaultDeclaration"
    );

    let properties;
    if (defaultExportAst.declaration.type === "CallExpression") {
      properties = defaultExportAst.declaration.arguments[0].properties;
    }
    if (
      defaultExportAst.declaration.type === "ObjectExpression" &&
      Array.isArray(defaultExportAst.declaration.properties)
    ) {
      properties = defaultExportAst.declaration.properties;
    }

    const methods = properties.find(v => v.key.name === "methods");
    if (!methods) return;

    if (methods.value.properties.length) {
      const methodNames = methods.value.properties.map(
        v => `${p}:${v.key.name}`
      );
      keys.push(...methodNames);
    }
  });

  keysContent += keys.map(v => `'${v}'`).join("|") || "string";

  keysContent = prettier.format(keysContent, {
    ...prettierConfig,
    parser: "typescript"
  });

  if (keysContentCache !== keysContent) {
    keysContentCache = keysContent;
    fs.writeFileSync(KEYS_PATH, keysContent);
  }
}

/**
 *
 * @param {string=} p 路径
 * @returns {string[]} 路径集合
 */
function getFilePath(p = "src") {
  const paths = fs.readdirSync(getsrcPath(p), "utf-8");
  const vueFiles = getVueFiles(paths, p);
  const dirs = getDirs(paths, p);

  if (dirs.length) {
    dirs.forEach(dir => {
      vueFiles.push(...getFilePath(dir));
    });
  }
  return vueFiles;
}

function getDirs(paths, path) {
  return paths
    .map(v => `${path}/${v}`)
    .filter(v => fs.statSync(v).isDirectory());
}

function getVueFiles(paths, path) {
  return paths.filter(v => v.endsWith(".vue")).map(v => `${path}/${v}`);
}

function getsrcPath(p) {
  return path.resolve(__dirname, "../" + p);
}

module.exports = { TransformFilePathPlugin };

添加vue-path-loader.js webpack loader文件

这个文件是用来在vue实例上添加__filePath属性的,本来是想写在上面的插件一起的,无奈没有在webpack文档等地方找到在plugins里添加loader的方法,在vue-loader源码里也没有好的体现。 在开发者环境下vue的$options下有__file可以用,所以只需要生产环境启用

module.exports = function(content) {
  if (process.env.NODE_ENV === "development") return content;

  const filePath = this.resourcePath
    .replace(/\\/g, "/")
    .replace(/(.*)?src/, "src");

  const reg = /export default.*?{/;
  content = content.replace(reg, $0 => `${$0} __filePath: "${filePath}",`);
  return content;
};

配置vue.config.js

添加configureWebpack里的即可

const path = require("path");
const { TransformFilePathPlugin } = require("./plugins/global-dispatch");
/**
 * @type {import('@vue/cli-service').ProjectOptions}
 */
module.exports = {
  lintOnSave: false,
  productionSourceMap: false,
  configureWebpack: {
    plugins: [new TransformFilePathPlugin()],
    module: {
      rules: [
        {
          test: /\.vue$/,
          use: [
            {
              loader: path.resolve(__dirname, "./plugins/vue-path-loader.js")
            }
          ]
        }
      ]
    }
  }
};

后记

后续有时间可以弄成npm包,npm install 使用找下weboack loader 写进webpack plugin 的方法目前还没找不破坏vue源码的情况下自定组件自定义props的类型,让在组件上 有eKey的单词提示,在vue/type/jsx.d.ts的ReservedProps下添加eKey?: string;才能实现功能。

仓库地址

ywenhao/vue2-global-dispatch (github.com)

总结

到此这篇关于vue2中组件互相调用实例methods中的方法实现的文章就介绍到这了,更多相关vue2组件互相调用methods中方法内容请搜索真格学网以前的文章或继续浏览下面的相关文章希望大家以后多多支持真格学网!

您可能感兴趣的文章:vue中实现在外部调用methods的方法(推荐)vue中实现methods一个方法调用另外一个方法对vue中methods互相调用的方法详解

  • 本文相关:
  • vue 全局环境切换问题
  • vue中正确使用jsx语法的姿势分享
  • 基于vue通用表单解决方案的思考与分析
  • 解决使用vue.js路由后失效的问题
  • vue监听scroll的坑的解决方法
  • vue 使用jade模板写html,stylus写css的方法
  • vuejs点击class变化的实例
  • vue路由跳转的4种方式小结
  • vuejs第十三篇之组件——杂项
  • vue.js自定义指令学习使用详解
  • vue组件调用格式?
  • vue.js组件怎么调用父实例中的方法
  • vue父组件中调用子组件函数的方法?
  • vue如何调用动态组件的内部方法?
  • vue如何多次调用调用自身组件,只是数据不一样?
  • Vue.js中兄弟组件之间互相传值实例
  • Vue $emit $refs子父组件间方法的调用实例
  • 详解Vue2中组件间通信的解决全方案
  • 如何在Vue2中实现组件props双向绑定
  • 如何在Vue2中实现组件props双向绑定
  • 如何在Vue2中实现组件props双向绑定
  • vue 封装组件,调用多次是怎么区别
  • vue 父组件中调用子组件函数的方法
  • Vue父子组件之间的通信实例详解
  • vue父组件调用子组件的子组件
  • vue一个组件调用另一个组件,为什么显示不出来
  • Vue父组件调用子组件事件方法
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全yui.ext相关prototypejqueryangularjsjsonlib_jsjs面向对象extjsmootoolsseajsdojovue.jsbackbone.jsreact其它首页javascriptjavascript类库vue.jsvue中实现在外部调用methods的方法(推荐)vue中实现methods一个方法调用另外一个方法对vue中methods互相调用的方法详解vue 全局环境切换问题vue中正确使用jsx语法的姿势分享基于vue通用表单解决方案的思考与分析解决使用vue.js路由后失效的问题vue监听scroll的坑的解决方法vue 使用jade模板写html,stylus写css的方法vuejs点击class变化的实例vue路由跳转的4种方式小结vuejs第十三篇之组件——杂项vue.js自定义指令学习使用详解vue引用js文件的多种方式(推荐)vue跳转页面的几种方法(推荐)详解vue 路由跳转四种方式 (带参数)vue项目刷新当前页面的三种方法vue之父子组件间通信实例讲解(props、$ref、$emvue props用法详解(小结)vue.js中created方法作用element-ui中select组件绑定值改变,触发chavue实现文件上传功能简单理解vue中props属性使用 vue 实现灭霸打响指英雄消失的效果附demovue ssr 实现方式(学习笔记)详解vue 实例方法和数据基于vue实现简单的学生信息管理系统vue-cli3跨域配置的简单方法详解vue.js3.0 组件是如何渲染为dom的vue.js中使用微信扫一扫解决invalid signature问题(完美vue调试工具vue-devtools安装及使用方法利用vue.js框架实现火车票查询系统(附源码)vue 事件的$event参数=事件的值案例
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved