温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

vue2中组件怎么互相调用

发布时间:2022-08-24 11:00:38 来源:亿速云 阅读:123 作者:iii 栏目:开发技术

这篇文章主要介绍了vue2中组件怎么互相调用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇vue2中组件怎么互相调用文章都会有所收获,下面我们一起来看看吧。

    开始前:

    我打算用全局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)。

    vue2中组件怎么互相调用

    vue2中组件怎么互相调用

    vue2中组件怎么互相调用

    vue2中组件怎么互相调用

    第一步、定义全局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")
                }
              ]
            }
          ]
        }
      }
    };

    关于“vue2中组件怎么互相调用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“vue2中组件怎么互相调用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI