广告位联系
返回顶部
分享到

vue2中组件互相调用实例methods中的方法实现

JavaScript 来源:互联网 作者:佚名 发布时间:2022-08-21 19:01:06 人浏览
摘要

大家都知道在vue2中相互调用组件的方法是很麻烦的一件事。比如说要调用子孙组件的的方法,需要refs,refs一层一层往下面找;调用上层组件的可以在组件上挂载,一层可以用v-on 和 em

大家都知道在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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

import Vue from "vue";

 

const DEFAULT_E_KEY = "__default";

/**

 * 方法合集

 * @type {Record}

 */

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文件夹的文件加进来

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

{

  "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需要支持两种传参形式,所以使用重载

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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文件的路径和方法自动生成更好,下面会贴出来。

1

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里新方法或删除后,会执行写入。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

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(/", 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可以用,所以只需要生产环境启用

1

2

3

4

5

6

7

8

9

10

11

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里的即可

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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) https://github.com/ywenhao/vue2-global-dispatch


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7134206156917964808
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计