<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
大家都知道在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)。
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元件上使用。
我用的時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"] }
在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; } }
在types資料夾下新建event-keys.d.ts, 這個檔案是用來給globalDispatch的第一個引數做程式碼提示的,手動寫可以,寫個webpack外掛自動讀取vue檔案的路徑和方法自動生成更好,下面會貼出來。
export type EventKeys = "src/App.vue:onClick" | "src/views/IndexView.vue:test";
在專案根目錄下新建plugins資料夾
開發者模式下才需要生成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範例上新增__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; };
新增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") } ] } ] } } };
ywenhao/vue2-global-dispatch (github.com)
到此這篇關於vue2中元件互相呼叫範例methods中的方法實現的文章就介紹到這了,更多相關vue2元件互相呼叫methods中方法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45