index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import _get from 'lodash/get'
  2. import _set from 'lodash/set'
  3. import _isEmpty from 'lodash/isEmpty'
  4. import _isEqual from 'lodash/isEqual'
  5. import _forEach from 'lodash/forEach'
  6. import _isUndefined from 'lodash/isUndefined'
  7. import _flattenDeep from 'lodash/flattenDeep'
  8. import _toString from 'lodash/toString'
  9. import _has from 'lodash/has'
  10. import _includes from 'lodash/includes'
  11. import _keys from 'lodash/keys'
  12. import _isFunction from 'lodash/isFunction'
  13. import _uniq from 'lodash/uniq'
  14. import _flatMap from 'lodash/flatMap'
  15. import _union from 'lodash/union'
  16. /**
  17. * @desc
  18. * Vue 跨组件通信插件
  19. */
  20. let unicomIdName = ''
  21. let unicomGroupName = ''
  22. // vm容器、分组、事件、命名 唯一、推迟触发的事件
  23. const [vmMap, groupForVm, events, idForVm, sendDefer] = [new Map(), {}, {}, {}, []]
  24. /**
  25. * @desc 触发执行事件
  26. * @param {string} method - 指令的名称
  27. * @param {string} toKey - 目标组件的 unicomId 或者 unicomGroup
  28. * @param {string} aim - 标识 (#)
  29. * @param {Array} args - 参数
  30. */
  31. const emitEvent = function (method, toKey, aim, args) {
  32. const evs = _get(events, method, [])
  33. let evLen = 0
  34. const len = evs.length
  35. // 循环已经注册的指令
  36. for (evLen; evLen < len; evLen++) {
  37. // 存储的数据
  38. const { fn, scope } = evs[evLen]
  39. if (_isEqual(aim, '#')) {
  40. // id
  41. if (scope[unicomIdName] !== toKey) {
  42. // 目标不存在
  43. continue
  44. }
  45. } else if (_isEqual(aim, '@')) {
  46. // 分组
  47. const group = _get(vmMap.get(scope), 'group', [])
  48. if (_isEmpty(group) || _isEqual(_includes(group, toKey), false)) {
  49. // 目标不存在
  50. continue
  51. }
  52. }
  53. _isEqual(_isUndefined(scope), false) && fn.apply(scope, args)
  54. }
  55. // 返回被触发的指令
  56. return evLen
  57. }
  58. /**
  59. * @desc 发送容器 或者 获得 目标容器
  60. * @param {string} query - 指令名称 (~instruct1#id1)
  61. * @param {...any} args - 可变参数
  62. */
  63. const unicomQuery = function (query, ...args) {
  64. let [toKey, aim, defer, eventIndex] = ['', '', false, -1]
  65. // query=instruct1#id1
  66. const method = query
  67. .replace(/^([`~])/, function (s0, s1) {
  68. // query=~instruct1#id1
  69. if (_isEqual(s1, '~')) {
  70. defer = true
  71. }
  72. return ''
  73. })
  74. .replace(/([@#])([^@#]*)$/, function (s0, s1, s2) {
  75. // query=instruct1@child-a
  76. // s0=@child-a s1=@ s2=child-a
  77. toKey = s2
  78. aim = s1
  79. return ''
  80. })
  81. // method=instruct1
  82. if (defer) {
  83. sendDefer.push([method, toKey, aim, args, this])
  84. return this
  85. }
  86. if (_isEqual(_isEqual(method, ''), false)) {
  87. args.unshift(this)
  88. eventIndex = emitEvent(method, toKey, aim, args)
  89. }
  90. // 获取目标 vm
  91. switch (aim) {
  92. case '#':
  93. return _get(idForVm, toKey, null)
  94. case '@':
  95. return _get(groupForVm, toKey, [])
  96. }
  97. return eventIndex
  98. }
  99. /**
  100. * @desc 更新分组
  101. * @param {Object} scope - 组件的实例
  102. * @param {string[]} nv - 指令 unicom-group 传入的组数据 (新)
  103. * @param {string[]} [ov] - 指令 unicom-group 传入的组数据 (旧)
  104. */
  105. const updateName = function (scope, nv, ov) {
  106. // 实例上设置分组
  107. const vmData = vmMap.get(scope) || {}
  108. const group = _get(vmData, 'group', [])
  109. // 删除旧的 vm
  110. if (_isEqual(_isUndefined(ov), false)) {
  111. _uniq(_flattenDeep(_flatMap(ov))).forEach(function (key) {
  112. if (_includes(group, key)) {
  113. const vms = _get(groupForVm, key, [])
  114. _isEqual(_isEmpty(vms), false) && vms.splice(vms.indexOf(scope), 1)
  115. if (_isEmpty(vms)) {
  116. group.splice(group.indexOf(key), 1)
  117. Reflect.deleteProperty(groupForVm, key)
  118. }
  119. }
  120. })
  121. }
  122. // 增加新的
  123. if (_isEqual(_isUndefined(nv), false)) {
  124. _uniq(_flattenDeep(_flatMap(nv))).forEach(function (key) {
  125. const vms = _get(groupForVm, key, [])
  126. if (_isEmpty(vms)) {
  127. _set(groupForVm, key, vms)
  128. }
  129. // 新添加 组件 到组里面
  130. if (_isEqual(_includes(vms, scope), false)) {
  131. vms.push(scope)
  132. }
  133. if (_isEqual(_includes(group, key), false)) {
  134. group.push(key)
  135. }
  136. })
  137. }
  138. }
  139. /**
  140. * @desc 更新 unicomId
  141. * @param {Object} scope - 组件的实例
  142. * @param {string} newValue - 指令 unicom-id 传入的id数据 (新)
  143. * @param {string} [oldValue] - 指令 unicom-id 传入的id数据 (旧)
  144. */
  145. const updateId = function (scope, newValue, oldValue) {
  146. if (_isEqual(newValue, oldValue)) {
  147. return
  148. }
  149. if (_isEqual(_isUndefined(oldValue), false) && _isEqual(_get(idForVm, oldValue), scope)) {
  150. // watch 监测值修改需要删除,组件销毁时需要删除
  151. Reflect.deleteProperty(idForVm, oldValue)
  152. }
  153. if (_isEqual(_isUndefined(newValue), false) && _isEqual(_has(idForVm, newValue), false)) {
  154. _set(idForVm, newValue, scope)
  155. } else if (_isEqual(_isUndefined(newValue), false) && _has(idForVm, newValue)) {
  156. console.warn(`${unicomIdName}='${newValue}'的组件已经定义并存在。`)
  157. }
  158. }
  159. /**
  160. * @desc 添加事件
  161. * @param {string} method - 指令的名称
  162. * @param {function} fn - 指令名称对应的函数
  163. * @param {Object} scope - 指令名称对应函数所运行的作用域
  164. */
  165. const appendEvent = function (method, fn, scope) {
  166. if (_isEqual(_has(events, method), false)) {
  167. events[method] = []
  168. }
  169. if (_isFunction(fn)) {
  170. events[method].push({ fn, scope, method })
  171. }
  172. }
  173. /**
  174. * @desc 移除事件
  175. * @param {string} method - 指令的名称
  176. * @param {Object} scope - 指令名称对应函数所运行的作用域
  177. */
  178. const removeEvent = function (method, scope) {
  179. const evs = _get(events, method, [])
  180. if (_isEqual(_isEmpty(evs), false)) {
  181. for (let i = 0; i < evs.length; i++) {
  182. if (_isEqual(scope, evs[i].scope)) {
  183. evs.splice(i, 1)
  184. }
  185. }
  186. }
  187. }
  188. export default {
  189. install (Vue, { name = 'unicom', idName = `${name}Id`, groupName = `${name}Group` } = {}) {
  190. // 添加原型方法 (unicomQuery 函数放在 install 外部不然无法获取调用函数的 this 对象)
  191. Vue.prototype['$' + name] = unicomQuery
  192. // unicom-id
  193. unicomIdName = idName
  194. // 分组 unicom-group
  195. unicomGroupName = groupName
  196. // 全局混入
  197. Vue.mixin({
  198. props: {
  199. // 命名 unicom-id="id1"
  200. [idName]: {
  201. type: String,
  202. default: ''
  203. },
  204. // 分组(纯字符串类数组不能是真实的数组) unicom-group="['child-a', 'child-b']"
  205. [groupName]: {
  206. type: Array,
  207. default: () => []
  208. }
  209. },
  210. watch: {
  211. [idName] (nv, ov) {
  212. updateId(this, nv, ov)
  213. },
  214. [groupName]: {
  215. deep: true,
  216. handler (nv, ov) {
  217. updateName(this, nv, ov);
  218. }
  219. }
  220. },
  221. // 创建的时候加入事件机制
  222. beforeCreate () {
  223. const opt = this.$options
  224. const vmData = {}
  225. const [us, uni, group] = [
  226. _get(opt, name, {}),
  227. (vmData.uni = {}),
  228. (vmData.group = [])
  229. ]
  230. if (_isEqual(_isEmpty(us), false)) {
  231. _forEach(us, opt => {
  232. if (_isEmpty(opt)) {
  233. return true
  234. }
  235. _forEach(opt, (handler, key) => {
  236. if (_isEqual(_isUndefined(handler), false)) {
  237. _isUndefined(_get(uni, key)) && _set(uni, key, [])
  238. uni[key].push(handler)
  239. // 添加事件
  240. appendEvent(key, handler, this)
  241. }
  242. })
  243. })
  244. }
  245. // 命名分组
  246. const groupNameOpt = _get(opt, groupName, [])
  247. if (_isEqual(_isEmpty(groupNameOpt), false)) {
  248. _forEach(_uniq(_flattenDeep(_flatMap(groupNameOpt))), item => {
  249. const key = _toString(item)
  250. _isUndefined(_get(groupForVm, key)) && (groupForVm[key] = [])
  251. group.push(key)
  252. groupForVm[key].push(this)
  253. })
  254. }
  255. if (_isEqual(_isEmpty(group), false) || _isEqual(_isEmpty(uni), false)) {
  256. vmMap.set(this, vmData)
  257. }
  258. },
  259. created () {
  260. // 实例命名 唯一
  261. const uId = this[idName]
  262. const uGroupName = this[groupName]
  263. _isEqual(_isEqual(uId, ''), false) && updateId(this, uId)
  264. _isEqual(_isEmpty(uGroupName), false) && updateName(this, uGroupName)
  265. // 实例上设置分组
  266. const vmData = vmMap.get(this) || {}
  267. for (let i = 0; i < sendDefer.length; i++) {
  268. const [method, toKey, aim, args, scope] = sendDefer[i]
  269. let flag = false
  270. if (_isEqual(aim, '#')) {
  271. if (_isEqual(toKey, uId)) {
  272. flag = true
  273. }
  274. } else if (_isEqual(aim, '@')) {
  275. if (
  276. _isEqual(_isUndefined(_get(vmData, 'group')), false) &&
  277. _includes(_get(vmData, 'group'), toKey)
  278. ) {
  279. flag = true
  280. }
  281. } else {
  282. if (
  283. _isEqual(method, '') ||
  284. _includes(_keys(_get(vmData, 'uni', {})), method)
  285. ) {
  286. flag = true
  287. }
  288. }
  289. if (flag) {
  290. // 延后,并且方法为空
  291. if (_isEqual(method, '')) {
  292. /**
  293. * @desc 监听组件事件
  294. * @example
  295. * this.$unicom('~#id1', function (childScope) {// unicomId=id1的组件创建完成})
  296. */
  297. if (_isFunction(args[0])) {
  298. args[0](this)
  299. }
  300. } else {
  301. sendDefer.splice(i--, 1)
  302. args.unshift(scope)
  303. emitEvent(method, toKey, aim, args)
  304. }
  305. }
  306. }
  307. },
  308. // 全局混合,销毁实例的时候销毁事件
  309. destroyed () {
  310. // 移除唯一ID
  311. const uId = this[idName]
  312. if (_isEqual(_isEqual(uId, ''), false)) {
  313. updateId(this, undefined, uId)
  314. }
  315. // 移除 命名分组、实例命名
  316. const uGroupName = _get(this, groupName, [])
  317. const optGroupName = _get(this.$options, groupName, [])
  318. if (_isEqual(_isEmpty(uGroupName), false) || _isEqual(_isEmpty(optGroupName), false)) {
  319. updateName(this, undefined, _union(uGroupName, optGroupName))
  320. }
  321. const vmData = vmMap.get(this)
  322. if (_isUndefined(vmData)) {
  323. return
  324. }
  325. vmMap.delete(this)
  326. const uni = _get(vmData, 'uni', {})
  327. // 移除事件
  328. for (const key in uni) {
  329. removeEvent(key, this)
  330. }
  331. // 分组,一对多, 单个vm可以多个分组名称 组件命名
  332. const group = _get(vmData, 'group', [])
  333. _forEach(group, function (name) {
  334. const gs = groupForVm[name]
  335. if (_isEqual(_isUndefined(gs), false)) {
  336. const index = gs.indexOf(this)
  337. if (index > -1) {
  338. gs.splice(index, 1)
  339. }
  340. if (gs.length === 0) {
  341. delete groupForVm[name]
  342. }
  343. }
  344. })
  345. // 监控销毁 method为空
  346. for (let i = 0; i < sendDefer.length;) {
  347. const pms = sendDefer[i]
  348. if (_isEqual(pms[0], '') && _isEqual(pms[4], this)) {
  349. sendDefer.splice(i, 1)
  350. } else {
  351. i += 1
  352. }
  353. }
  354. }
  355. })
  356. // 自定义属性合并策略
  357. // 混入(mixins)时不是简单的覆盖而是追加
  358. const merge = _get(Vue, 'config.optionMergeStrategies', {})
  359. merge[name] = merge[unicomGroupName] = function (parentVal, childVal) {
  360. const p = parentVal || []
  361. if (_isEqual(_isUndefined(childVal), false)) {
  362. p.push(childVal)
  363. }
  364. return p
  365. }
  366. }
  367. }