这篇文章是我 19 年的时候写的,如今已是 20 年了,vue-rx 又作了一些更新,笔者水平也提高了一些,因此准备重构(refactor)一下这篇文章,最主要的缘由仍是由于某次面试面试官问笔者这篇文章,笔者竟然懵了,有兴趣的小伙伴能够在 github 上看到这篇文章历史版本(俗称:黑历史)
笔者写源码分析的时只想将核心部分写下来,因此但愿读者读以前先掌握 Vue 中下面几个功能的用法,文章中不会对基础作太多讲解:javascript
Vue.mixin html
Vue.usejava
Vue.directivenode
而且读这篇文章以前读者应该使用过 vue-rx
或者 rxjs
开发过一个以上的项目,不然读这篇文章对读者的意义不是很大。git
本文对应的代码仓库:https://github.com/MonchiLin/...github
vue-rx 代码仓库:https://github.com/vuejs/vue-rx面试
impl-yourself-vue-rx 仓库里 src
都可使用 vue serve xx.vue
来直接启动看效果express
注1:本文会从每一个大函数入手,先根据功能来自行实现一个简单版本,最后逐行(并不)分析源代码。api
注2:下文中 vm.xx = xx
vm 为上文中的 组件的实例),这是 vue
中约定俗成的一个术语。
vue 指令的开发
vue 指令如何与 rxjs 结合
学习一个成熟的 vue 指令是如何开发的
从 Readme 咱们能够看到, vue-rx 在现代 JS 模块( ES6 module )系统中用法为
import Vue from 'vue' import VueRx from 'vue-rx' Vue.use(VueRx)
而后, 根据 Vue.use 文档可知 Vue.use
函数有两种用法:
这时咱们打开 index.js 能够看到下面部分,vue-rx
使用的是第 二 种方法
export default function VueRx (Vue) { install(Vue) Vue.mixin(rxMixin) Vue.directive('stream', streamDirective) Vue.prototype.$watchAsObservable = watchAsObservable Vue.prototype.$fromDOMEvent = fromDOMEvent Vue.prototype.$subscribeTo = subscribeTo Vue.prototype.$eventToObservable = eventToObservable Vue.prototype.$createObservableMethod = createObservableMethod Vue.config.optionMergeStrategies.subscriptions = Vue.config.optionMergeStrategies.data } // auto install if (typeof Vue !== 'undefined') { Vue.use(VueRx) }
能够看到这个文件默认导出一个函数, 而这个函数接受一个 Vue , 这正是咱们上面提到的 Vue.use 第二种使用形式.
而后咱们看最后两行
if (typeof Vue !== 'undefined') { Vue.use(VueRx) }
若是 Vue
不是 undefined
那么就意味着 Vue
是全局变量, 这也就意味着 这里的 Vue 是经过传统的 script 标签引入的,而后调用全局变量的 Vue.use, 而且传入 VueRx
,这也是默认导出 (export default) 的函数为何起名为VueRx
的缘由,
若是执行到这段代码的 if 语句里面, 则表示这里的 Vue
是经过传统的 script 标签引入的, 这样 vue-rx 就能够兼容 现代 JS 模块系统 和 经过 script 标签引入。
接下来咱们来看 VueRx 函数作了什么。
export default function VueRx (Vue) { install(Vue) Vue.mixin(rxMixin) Vue.directive('stream', streamDirective) Vue.prototype.$watchAsObservable = watchAsObservable Vue.prototype.$fromDOMEvent = fromDOMEvent Vue.prototype.$subscribeTo = subscribeTo Vue.prototype.$eventToObservable = eventToObservable Vue.prototype.$createObservableMethod = createObservableMethod Vue.config.optionMergeStrategies.subscriptions = Vue.config.optionMergeStrategies.data }
export let Vue export let warn = function () {} // NOTE(benlesh): the value of this method seems dubious now, but I'm not sure // if this is a Vue convention I'm just not familiar with. Perhaps it would // be better to just import and use Vue directly? export function install (_Vue) { Vue = _Vue warn = Vue.util.warn || warn }
install()
方法为这个文件导出的 Vue 和 warn 对象赋值。
注释的意思是说:这种行为彷佛是 Vue 的一种约定, 可是却不必定好, 或许应该使用 import vue
来代替。
咱们看到, 这个文件 默认导出 了一个对象, 而这个对象拥有两个 vue 的生命周期函数, 这两个函数最终都会被混入 (mixin) 到全部 vue 的实例中.
先来讲说这段代码实现了什么效果吧:
容许经过 domStreams 传入一个字符串数组,而后以字符串的名称为变量名建立一个对应的 Subject
,而后将这个 Subject
挂载在组件实例上(经过 this.subjectName) 访问。
new Vue({ domStreams: ['plus$'] }) // 等价于 vm.plus$ = new Subject()
容许经过 observableMethods 传入一个字符串数组,而后以字符串的名称为变量名建立一个对应的 Observable
。
new Vue({ observableMethods: ['plus$'] }) // 等价于 vm.plus$ = new Observable() // 下面是源代码,咱们能够看出源码作的事情就是在 vm 上挂载开发者传进来的属性,值为 vm.$createObservableMethod 建立的,重点主要在 $createObservableMethod,因此笔者会在 $createObservableMethod 来写出它的实现和源码分析,这里就不赘述了 // 储存传入的 observableMethods const observableMethods = vm.$options.observableMethods if (observableMethods) { if (Array.isArray(observableMethods)) { observableMethods.forEach(methodName => { vm[methodName + '$'] = vm.$createObservableMethod(methodName) }) } else { Object.keys(observableMethods).forEach(methodName => { vm[observableMethods[methodName]] = vm.$createObservableMethod(methodName) }) } }
容许经过 subscriptions 传入一个对象或者一个返回对象的函数,这里先理解成传入一个对象便可,而后把对象的属性挂载在 vm 上面,例以下方代码例子,便可以用经过 this.count
访问,对象的属性的值挂载在 vm.$observables
上面,方便开发者手动访问。
注意,对象属性的值应该为 Observer
相似下面这种结构:
new Vue({ subscriptions: { count: new Subject() .pipp(scan(total, change) => total + change) } }) // 等价于 // 在组件实例上挂载 count 这个属性 vm.count = undefined // 在 subscriptions.count 对应的值触发更新时将值赋值给 vm.count const obs = new Subject() .pipp(scan(total, change) => total + change) obs.subscribe(val => vm.count = val) vm.$observables = {} // 保存 observable vm.$observables["count"] = obs
自动取消订阅,这个功能是和上面那条(第三条)对应的,这段代码量比较少,就不单独在 实现 部分写了。
const subscription = new Subject() .pipp(scan(total, change) => total + change) .subscribe(val => vm.count = val) // subscribe 方法返回一个 subscription 对象,这个对象用于取消订阅。 // 跟着这个思路,如果要实现自动取消订阅就须要一个储存 subscription 对象集的地方 // 例如咱们挂载一个 Subscription 对象放在 vm 上 vm._subscriptions = new Subscription() // 接着利用 Subscription.add 实例方法,可让一个 Subscription 对象储存多个 Subscription 对象 vm._subscriptions.add(subscription) // 最后只须要 Subscription.unsubscribe() 便可 vm._subscriptions.unsubscribe()
domStreams
实现的代码已经在下面了,注释应该也足够完善,若是还有疑问请留言。
笔者将写好的代码放在了代码仓库里,若是小伙伴们本身写的时候发现有问题能够参考这个文件,包含了实现和使用的例子,文件地址
export default { // 在 domStreams: ["plus$", "minus$"], mixins: [ { created() { const vm = this // 获取 domStreams,此处的 domStreams 值为 ["plus$", "minus$"] const domStreams = vm.$options.domStreams domStreams.forEach(key => { // 遍历 domStreams,将 vm[key] 赋值为 new Subject vm[key] = new Subject() }) // 执行完上面这段 vm["plus$"] 和 vm["minus$"] 的值就变成了 new Subject } } ], }
subscriptions
实现的代码已经在下面了,注释应该也足够完善,若是还有疑问请留言。
笔者将写好的代码放在了代码仓库里,若是小伙伴们本身写的时候发现有问题能够参考这个文件,包含了实现和使用的例子,文件地址
<template> <div> <button @click="plus$.next($event)">增长</button> <span> >> {{ counter }} << </span> </div> </template> <script> import { map, scan, startWith } from "rxjs/operators"; import { Subject } from "rxjs"; import Vue from 'vue' const plus$ = new Subject() export default { data() { return { plus$: plus$ } }, // 经过 subscriptions 传入 [counter] subscriptions: { counter: plus$ .pipe( map(() => 1), startWith(0), scan((total, change) => total + change) ) }, mixins: [{ created() { const vm = this // 获取 subscriptions, 这里咱们假设 subscriptions = [{counter: Observable}] const subscriptions = vm.$options.subscriptions vm.$observables = {} Object.keys(subscriptions) .forEach(key => { // 在 vm 上建立一个响应式的属性,这里等价于 Vue.util.defineReactive(vm, "counter", undefined) Vue.util.defineReactive(vm, key, undefined) vm.$observables[key] = subscriptions[key] // 而后咱们订阅 Observable, 而且在发生更新的时候赋值给 vm["counter"] subscriptions[key] .subscribe(e => { vm[key] = e }) }) } }], } </script>
export default { created() { const vm = this // 处理 domStreams, 参考上方文章 const domStreams = vm.$options.domStreams // 作空值处理 if (domStreams) { domStreams.forEach(key => { vm[key] = new Subject() }) } const observableMethods = vm.$options.observableMethods if (observableMethods) { if (Array.isArray(observableMethods)) { observableMethods.forEach(methodName => { vm[methodName + '$'] = vm.$createObservableMethod(methodName) }) } else { Object.keys(observableMethods).forEach(methodName => { vm[observableMethods[methodName]] = vm.$createObservableMethod(methodName) }) } } let obs = vm.$options.subscriptions // 这里判断 obs 若是是函数就先执行下 if (typeof obs === 'function') { obs = obs.call(vm) } // 作空值处理 if (obs) { // 声明 $observables 用于储存 observable vm.$observables = {} // 声明 _subscription 用于储存 subscription 以后方便取消订阅 vm._subscription = new Subscription() Object.keys(obs).forEach(key => { // 使值变成响应式 defineReactive(vm, key, undefined) // 在 $observables 存一份 const ob = vm.$observables[key] = obs[key] // 处理 ob 不是 observable 的状况 if (!isObservable(ob)) { warn( 'Invalid Observable found in subscriptions option with key "' + key + '".', vm ) return } // 保存 subscription vm._subscription.add(obs[key].subscribe(value => { // 在每次流更新时将值赋给 vm[key] vm[key] = value }, (error) => { throw error })) }) } }, // 在组件的 beforeDestroy 取消订阅 beforeDestroy() { if (this._subscription) { this._subscription.unsubscribe() } } }
先来看一下用法
<button v-stream:click="plus$">+</button>
格式:v-stream + 事件名(click) = Subject(plus$)
v-stream
实现的代码已经在下面了,注释应该也足够完善,若是还有疑问请留言。
笔者将写好的代码放在了代码仓库里,若是小伙伴们本身写的时候发现有问题能够参考这个文件,包含了实现和使用的例子
根据基础用法实现
// <button v-stream:click="plus$">+</button> Vue.directive('stream', { bind: function(el, binding, vNode, oldVnode) { // 传入的 subject const subject = binding.value // 事件名称 const eventName = binding.arg // 建立一个 Subscription el._subscription = new Subscription() // 保存 subscibe 返回的 subscription el._subscription.add( fromEvent(el, eventName) .subscribe(e => subject.next(e)) ) }, unbind(el, binding) { // 避免内存泄漏,unbind 生命周期取消订阅 el._subscription.unsubscribe() } })
好,如今已经实现了最基础的功能,让咱们继续实现第二种用法
// <button v-stream:click="{ subject: plus$, data: someData }">+</button> export default { directives: { stream: { bind: function(el, binding, vNode, oldVnode) { // handle = { subject, data } let handle = binding.value const eventName = binding.arg // 放在这里方便读者看,这个函数是以鸭子类型的思想来判断对象是否为 observer function isObserver(subject) { return subject && ( typeof subject.next === 'function' ) } // 处理经过 v-stream="plus$" 传入进来的 subject if (isObserver(handle)) { // 包装一层,将 v-stream="plus$" 和 // v-stream="{ subject: plus$, data: someData }" 两种数据结构统一处理 // 此时 handle 数据结构变成了 { subject, data } handle = { subject: handle } } // 还记得 rxMixin 的功能之一吗? 没错就是在 vm 上面挂载 _subscription 对象 // 这里咱们单独写主要是为了实现 v-stream el._subscription = new Subscription() const subject = handle.subject // 储存 subscription 对象 el._subscription.add( // 将 event 和 data 都传递给开发者 fromEvent(el, eventName) .subscribe(e => subject.next({ event: e, data: handle.data + new Date().getTime() })) ) }, unbind(el, binding) { // 取消订阅 el._subscription.unsubscribe() } } } }
第三种用法,传入额外的 option
给原生的 addEventListener
<button v-stream:click="{ subject: plus$, data: someData, options: { once: true, passive: true, capture: true } }">+</button>
康过上面代码的小伙伴都知道了,咱们使用的是 rxjs
提供的fromEvent
函数,这个函数自己就是依赖 addEventListener
的,从 文档 的 Parameters
(参数) 部分就能够看到,若是传入了三个及以上的参数就会把第三个参数直接传递给 addEventListener
,下面咱们作一些小的改造来让 v-stream
支持这个特性
// 若是 handle 存在 options 则将 options 传给 fromEvent const fromEventArgs = handle.options ? [el, eventName, handle.options] : [el, eventName] // 储存 subscription 对象 el._subscription.add( fromEvent(...fromEventArgs) // 将 event 和 data 都传递给开发者 .subscribe(e => subject.next({ event: e, data: handle.data + new Date().getTime() })) )
好,如今咱们已经实现了本身的 v-stream
,基于上面的核心理念,咱们开始看 vue-rx
是如何实现 v-stream
的 (注意打开源码哦:streamDirective 源码)
import { isObserver, warn, getKey } from '../util' import { fromEvent } from 'rxjs' export default { bind(el, binding, vnode) { let handle = binding.value const event = binding.arg const streamName = binding.expression // 储存修饰符 const modifiers = binding.modifiers if (isObserver(handle)) { handle = { subject: handle } } else if (!handle || !isObserver(handle.subject)) { // 处理传入错误的参数 warn( 'Invalid Subject found in directive with key "' + streamName + '".' + streamName + ' should be an instance of Subject or have the ' + 'type { subject: Subject, data: any }.', vnode.context ) return } // 定义了一个包含 stop 和 prevent 的处理函数,用于处理 v-stram.stop.prevent="plus$" 的状况 const modifiersFuncs = { stop: e => e.stopPropagation(), prevent: e => e.preventDefault() } // 接上面, 若是定义的对象里正好包含了 经过指令传入的 事件属性, 那么就将这个属性储存到一个新对象 // 方便以后对使用者传入的事件选项作处理 var modifiersExists = Object.keys(modifiersFuncs).filter( key => modifiers[key] ) const subject = handle.subject // 这里首先判断 .next 函数是否存在, 若是存在就用 .next 函数, 若是不存在就用 .onNext 函数 // 这里是一种兼容性作法, onNext 是 rxjs 好久好久以前的 next 函数, 在 rxjs6 已经被废弃了 // 因此这里能够无视, 咱们继续往下看, 拿到函数后, 使用 bind 将 this 绑定到 subject, 防止以后使用了错误的 this. const next = (subject.next || subject.onNext).bind(subject) // 若是使用 v-stram.native="plus$" 去监听事件,则会经过 $eventToObservable // 转换成原生事件,参考 $eventToObservable 源码解析部分 // vnode.componentInstance 是使用了这个指令的组件实例,也能够经过 vnode.context 获取 if (!modifiers.native && vnode.componentInstance) { // 将 subscription 对象储存在 handle 对象上 // 注意,这里与笔者的实现是有区别的 handle.subscription = vnode.componentInstance.$eventToObservable(event).subscribe(e => { // 处理事件冒泡和默认行为 modifiersExists.forEach(mod => modifiersFuncs[mod](e)) next({ event: e, data: handle.data }) }) } else { const fromEventArgs = handle.options ? [el, event, handle.options] : [el, event] handle.subscription = fromEvent(...fromEventArgs).subscribe(e => { modifiersExists.forEach(mod => modifiersFuncs[mod](e)) next({ event: e, data: handle.data }) }) } // 源码这里是没有的,单独伶出来给读者看 function getKey(binding) { // 将 事件名称(binding.arg) 和 修饰符(.native .stop)转换成字符串 // 例如 v-stream:click.native="plus$" 会转换成 "click:native" // v-stream:click="plus$" 会转换成 "click" return [binding.arg].concat(Object.keys(binding.modifiers)).join(':') } // 最后将其储存在 _rxHandles 中 // store handle on element with a unique key for identifying // multiple v-stream directives on the same node ; (el._rxHandles || (el._rxHandles = {}))[getKey(binding)] = handle } // 触发指令的 update 生命周期 update(el, binding) { const handle = binding.value const _handle = el._rxHandles && el._rxHandles[getKey(binding)] if (_handle && handle && isObserver(handle.subject)) { // 更新 data,给不记得的小伙伴提个醒,用于更新下面这种方式传进来的 data // <button v-stream:click="{ subject: plus$, data: someData }">+</button> _handle.data = handle.data } }, // 取消订阅 unbind(el, binding) { const key = getKey(binding) const handle = el._rxHandles && el._rxHandles[key] if (handle) { if (handle.subscription) { handle.subscription.unsubscribe() } el._rxHandles[key] = null } } }
源码分析:
import { Observable, Subscription } from 'rxjs' export default function watchAsObservable (expOrFn, options) { const vm = this // 建立一个新的 Observable const obs$ = new Observable(observer => { // 声明一个变量用于取消 watch, 这里还未赋值 let _unwatch // 封装原生的 $watch 函数 const watch = () => { // 参考文档用法: https://cn.vuejs.org/v2/api/#vm-watch // $watch 函数会返回一个用于取消 watch 的函数 _unwatch = vm.$watch(expOrFn, (newValue, oldValue) => { // watch 方法回调时, 调用 observer.next observer.next({ oldValue: oldValue, newValue: newValue }) }, options) } // 这里设计的很巧妙, vm._data 实际上就是 $data, 也就是你声明的 data // 咱们都知道 Vue 在 beforeCreated 生命周期是没法获取到 data 的 // 这就会致使 $watch 没法工做, 因此就等到 created 生命周期去执行 watch 函数 // 因而下面使用了 $once 函数, $once 与 $on 功能很像, 可是 $onec 只会执行一次 // 而且这里使用了 hook:created, 相信聪明的小伙伴从名字就能看出来, 这里是监听 created 生命周期 // 这种用法咱们通常不多用到, 是一种 vue 内部的用法, 官方文档也有提到 // https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E7%A8%8B%E5%BA%8F%E5%8C%96%E7%9A%84%E4%BA%8B%E4%BB%B6%E4%BE%A6%E5%90%AC%E5%99%A8 // if $watchAsObservable is called inside the subscriptions function, // because data hasn't been observed yet, the watcher will not work. // in that case, wait until created hook to watch. if (vm._data) { watch() } else { vm.$once('hook:created', watch) } // 最后返回一个函数用于取消 watch // 注意这里的 _unwatch && _unwatch(), 这意味着若是 _unwatch 为被赋值就不会执行 _unwatch() // 这里插播一个 rxjs 小知识点, 下面是 observer 部分的返回类型 TeardownLogic 的签名 // export type TeardownLogic = Unsubscribable | Function | void; // Unsubscribable: 一个带有 unsubscribe 方法的对象 // Function: 任意函数 // void: 无返回值 // 若是你在 new Observable 的 observer 参数部分手动返回一个 Subscription 对象, 那么调用 // 这个 Observable.subscribe 方法返回的 Subscription 对象的 unsubscribe 方法时将会执行你返回执行你传入的函数 // 若是你在 new Observable 的 observer 参数部分手动返回一个函数, 那么调用 // 这个 Observable.subscribe 方法返回的 Subscription 对象的 unsubscribe 方法时将会执行你返回的函数 // Returns function which disconnects the $watch expression return new Subscription(() => { _unwatch && _unwatch() }) }) // 而后咱们把这个 Observable 返回出去, 就能够实现官方用法中的效果了. return obs$ }
源码解析:
import { Observable, Subscription, NEVER } from 'rxjs' export default function fromDOMEvent (selector, event) { // 处理 window 不存在的状况 if (typeof window === 'undefined') { // TODO(benlesh): I'm not sure if this is really what you want here, // but it's equivalent to what you were doing. You might want EMPTY return NEVER } const vm = this // 获取 dom 的根元素, 也就是咱们写下面这段代码中的 <html></html> // <html> // <body></body> // </html> // doc 变量的做用, 也就是这个函数的工做原理, 为何上面介绍说这个函数能够在 DOM 渲染前生效 // 由于事件监听的 DOM 是 html, 就意味着不管什么时候只要你触发这个事件都会执行传入的 处理事件(下面的 listener) // 能够看出这是一个全局的监听事件, 上面介绍有提到 fromDOMEvent 函数只会影响到当前组件的内部元素, 如何只影响组件内部 // 就要看下面的 listener 函数了 const doc = document.documentElement // 建立一个 Observable const obs$ = new Observable(observer => { function listener (e) { // 首先判断, 若是 vm.$el 不存在就退出函数执行 // 由于 vm.$el 不存在就由于这 vm 已经不被挂载在 dom 上了 if (!vm.$el) return // 这里是处理 selector 参数为 null 的状况, 若是 selector 为 null 而且当前事件的 target 是当前组件实例的 dom // 就把事件对象传入 next 方法 if (selector === null && vm.$el === e.target) return observer.next(e) // 这里从当前组件实例中去 querySelectorAll, 这意味着 selector 只能从当前组件实例中匹配 var els = vm.$el.querySelectorAll(selector) // 取出事件的 target var el = e.target // 循环上面 querySelectorAll 匹配到的 dom for (var i = 0, len = els.length; i < len; i++) { // 若是 dom 匹配则把事件对象传入 next 方法 if (els[i] === el) return observer.next(e) } } // 将上面的 listener 做为事件处理函数传入 addEventListener doc.addEventListener(event, listener) // 参考 watchAsObservable 对相似代码的解释 // Returns function which disconnects the $watch expression return new Subscription(() => { doc.removeEventListener(event, listener) }) }) return obs$ }
源码解读:
import { Subscription } from 'rxjs' // 还记得 rxMixin 中的 _subscription 吗?没错,又是它,咱们使用 vm.$subscribeTo 进行订阅的时候 // 返回的 subscription 对象会被添加到 vm._subscription 从而实现自动取消订阅 export default function subscribeTo (observable, next, error, complete) { // 调用传入的 observable, 以及其参数, 获得 subscription const subscription = observable.subscribe(next, error, complete) // 这里代码避开上去复杂了一点, 其实它的目的就是储存这个 subscription, 而后在某个时机取消订阅 // 这里以 ; 开头实际上是由于 js 解析器解析括号时是不会加分号的, 这也就致使出现下面的状况 const subscription = observable.subscribe(next, error, complete)(this._subscription || (this._subscription = new Subscription())).add(subscription) // 对, 就像上面这样, 将两段代码连在了一块儿, 这个 bug 现代解析器已经修复了, 这里特意提一下也是防止小伙伴们踩坑(才不是强行解释) // ok, 如今来讲代码内容, 首先判断 this._subscription 是否存在, 若是不存在就建立一个 _subscription // 而后将上面的 subscription 对象传入 ;(this._subscription || (this._subscription = new Subscription())).add(subscription) return subscription }
源码解读:
import { Observable } from 'rxjs' export default function eventToObservable (evtName) { const vm = this // 虽然上面的官方用法中没有提到, 但咱们看源码能够发现这个参数也能够接受一个数组, 若是发现 // 接受的参数不是一个数组, 那么就将其转换成一个数组 const evtNames = Array.isArray(evtName) ? evtName : [evtName] // 建立 Observable 对象 const obs$ = new Observable(observer => { // 储存用于取消监听事件的对象 const eventPairs = evtNames.map(name => { // 生成 callback const callback = msg => observer.next({ name, msg }) // 做为参数传入 vm.$on, 这样组件就能够自动监听了 vm.$on(name, callback) return { name, callback } }) return () => { // 取消监听事件 eventPairs.forEach(pair => vm.$off(pair.name, pair.callback)) } }) return obs$ }
功能介绍:
你可使用 observableMethods
选项使代码更加声明式:
new Vue({ observableMethods: { submitHandler: 'submitHandler$' // 或者使用数组简写: ['submitHandler'] } });
上面代码会自动在实例上建立两个东西:
v-on
绑定到模板的 submitHandler
方法;submitHandler
的submitHandler$
observable。笔者将写好的代码放在了代码仓库里,若是小伙伴们本身写的时候发现有问题能够参考这个文件,包含了实现和使用的例子,文件地址
export default { mixins: [{ created() { const vm = this const $createObservableMethod = (methodName) => { // 定义一个 subscriber const subscriber = (observer) => { // 在 vm 上挂载方法 vm["muchMore"] = xx // 调用这个方法时就会触发 observer.next vm[methodName] = (val) => { observer.next(val) } return () => { delete vm[methodName] } } return new Observable(subscriber).pipe(share()) } // 假设咱们传入以下结构 // observableMethods: { // muchMore: 'muchMore$', // minus: 'minus$' // } const observableMethods = vm.$options.observableMethods Object.keys(observableMethods).forEach(key => { // 在 vm 上面挂载 muchMore$,muchMore$ 是一个 Observable // vm["muchMore$"] = $createObservableMethod("muchMore") vm[observableMethods[key]] = $createObservableMethod(key) }) } }], }
export default function createObservableMethod (methodName, passContext) { const vm = this // 处理错误 if (vm[methodName] !== undefined) { warn( 'Potential bug: ' + `Method ${methodName} already defined on vm and has been overwritten by $createObservableMethod.` + String(vm[methodName]), vm ) } const creator = function (observer) { vm[methodName] = function () { // arguments 是 function 声明的函数的特有属性,由实参构成的“类数组” // Array.from(arguments) 将 arguments 转换成数组 const args = Array.from(arguments) if (passContext) { args.push(this) observer.next(args) } else { if (args.length <= 1) { observer.next(args[0]) } else { observer.next(args) } } } // 取消订阅时删除 vm 上的方法 return function () { delete vm[methodName] } } return new Observable(creator).pipe(share()) }
解析:
Vue.config.optionMergeStrategies.subscriptions = Vue.config.optionMergeStrategies.data
咱们看到 subscriptions 的合并策略被赋值了 data 的合并策略, 这意味着咱们只须要搞明白 data 的合并策略就知道 subscriptions 的合并策略了,想了解这部分知识的小伙伴能够能够去读 Vue 源码,若是须要的话笔者也能够单独写一篇文章来说解。
源码在 vue 仓库的 vue/src/core/util/options.js 110行。