Last active
July 15, 2019 05:07
-
-
Save AimWhy/dffbc1e03d85436fe8a76d7a5e9f9e03 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { value, onUnmounted, onMounted } from '../utils/vue-function-api' | |
export function useMouse() { | |
const x = value(0) | |
const y = value(0) | |
const update = e => { | |
x.value = e.pageX | |
y.value = e.pageY | |
} | |
onMounted(() => { | |
window.addEventListener('mousemove', update) | |
}) | |
onUnmounted(() => { | |
window.removeEventListener('mousemove', update) | |
}) | |
return { x, y } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
<pre> | |
count is ValueWrap 	 	 countWrap is Object 		 vCountWrap is ValueWrap 		 sCountWrap is State | |
</pre> <br /> | |
<div>countWrap 内的 count 值为: {{countWrap.count}}</div><br /> | |
<div>vCountWrap 内的 count 值为: {{vCountWrap.count}}</div><br /> | |
<div>sCountWrap 内的 count 值为: {{sCountWrap.count}}</div><br /> | |
<div>count 值为: {{ count }}</div><br /> | |
<div>plusOne is {{ plusOne }}</div><br /> | |
{{x}}, {{y}} | |
<button @click="increment">count++</button> | |
</div> | |
</template> | |
<script> | |
import { value, computed, watch, onMounted, state } from '../utils/vue-function-api' | |
import { useMouse } from '../utils/test' | |
/** | |
* 在console中进行如下测试: | |
* vCountWrap.value.count = 10 | |
* vCountWrap.value.count | |
* sCountWrap.count = 100 | |
* sCountWrap.count | |
* count.value = 99 | |
* count.value | |
*/ | |
export default { | |
setup (props, ctx) { | |
const count = value(0); | |
const sCount = state({ count2: 1 }); | |
const vCountWrap = value({ count }) | |
const sCountWrap = state({ count }) | |
const plusOne = computed(() => count.value + 1, v => { count.value = v -1 }); | |
const increment = () => { | |
count.value++; | |
sCount.count2 = sCount.count2 + 4 | |
} | |
const { x, y } = useMouse() | |
window.why = ctx.vm | |
window.sCount = sCount | |
window.count = count | |
window.vCountWrap = vCountWrap | |
window.sCountWrap = sCountWrap | |
watch(() => count.value * 2, val => console.log(`count * 2 is ${val}`)); | |
watch(plusOne, value => { console.log('plusOne is: ', value) }) | |
watch([ () => sCount.count2, count ], ([a, b], [prevA, prevB], setClean) => { | |
setClean(function () { | |
window.clearTimeout(window.timerId) | |
}) | |
window.timerId = window.setTimeout(function () { | |
console.log(`a is: ${prevA} -> ${a}`) | |
console.log(`b is: ${prevB} -> ${b}`) | |
}, 1000) | |
}) | |
onMounted(() => { console.log(`mounted`); }); | |
return { | |
count, | |
countWrap: { count }, | |
vCountWrap, | |
sCountWrap, | |
plusOne, | |
increment, | |
x, | |
y | |
}; | |
// return function (props, slots, attrs) { | |
// return ctx.vm.$createElement('button', { on: { click: increment } }, count.value ) | |
// } | |
}, | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var noopFn = _ => _; | |
var toString = x => Object.prototype.toString.call(x); | |
var isArray = x => toString(x) === `[object Array]`; | |
var isPlainObject = x => toString(x) === `[object Object]`; | |
var hasOwn = (obj, key) => !!obj && Object.prototype.hasOwnProperty.call(obj, key); | |
function assert(condition, msg) { | |
if (!condition) { | |
throw new Error(`[vue-function-api] ${msg}`); | |
} | |
} | |
function proxy(target, source, targetKey, sourceKey = targetKey) { | |
Object.defineProperty(target, targetKey, { | |
enumerable: true, | |
configurable: false, | |
get: function proxyGetter() { return source[sourceKey]; }, | |
set: function proxySetter(val) { source[sourceKey] = val; } | |
}); | |
} | |
var currentVue = null; | |
function getCurrentVue() { | |
assert(currentVue, `must call Vue.use(plugin) before using any function.`); | |
return currentVue; | |
} | |
function setCurrentVue(vue) { | |
currentVue = vue; | |
} | |
function vueWarn(msg, vm) { | |
getCurrentVue().util.warn(msg, vm); | |
} | |
var currentVM = null; | |
function getCurrentVM() { | |
return currentVM; | |
} | |
function setCurrentVM(vm) { | |
currentVM = vm; | |
} | |
function ensureCurrentVMInFn(hook) { | |
var vm = getCurrentVM(); | |
assert(vm, `"${hook}" get called outside of "setup()"`); | |
return vm; | |
} | |
// createComponent | |
export function createComponent(compOpts) { | |
return typeof compOpts === `function` ? { setup: compOpts } : compOpts; | |
} | |
// For state / value / state | |
function ValueWrapper(v) { | |
this.observe = v; | |
} | |
Object.defineProperty(ValueWrapper.prototype, `value`, { | |
enumerable: true, | |
configurable: true, | |
get() { return this.observe.$$value; }, | |
set(v) { this.observe.$$value = v; } | |
}); | |
function isValueWrapper(obj) { | |
return obj instanceof ValueWrapper; | |
} | |
function unProxy(obj) { | |
if (obj) { | |
var keys = Object.keys(obj); | |
for (var index = 0; index < keys.length; index++) { | |
var key = keys[index]; | |
var value = obj[key]; | |
if (isValueWrapper(value)) { | |
proxy(obj, value.observe, key, `$$value`); | |
} else if ((isPlainObject(value) || isArray(value)) && !hasOwn(value, `__ob__`)) { | |
obj[key] = unProxy(value); | |
} | |
} | |
} | |
return obj; | |
} | |
function observable(obj) { | |
var Vue = getCurrentVue(); | |
if (Vue.observable) { | |
return Vue.observable(obj); | |
} else { | |
var silent = Vue.config.silent; | |
Vue.config.silent = true; | |
var vm = new Vue({ data: { $$state: obj } }); | |
Vue.config.silent = silent; | |
return vm._data.$$state; | |
} | |
} | |
// state | |
export function state(value) { | |
return observable( | |
isArray(value) || isPlainObject(value) ? unProxy(value) : value | |
); | |
} | |
// value | |
export function value(value) { | |
return new ValueWrapper( | |
observable( | |
{ $$value: isArray(value) || isPlainObject(value) ? unProxy(value) : value } | |
) | |
); | |
} | |
// computed | |
function compoundComputed(computed) { | |
var Vue = getCurrentVue(); | |
var silent = Vue.config.silent; | |
Vue.config.silent = true; | |
var reactive = new Vue({ computed: computed }); | |
Vue.config.silent = silent; | |
return reactive; | |
} | |
export function computed(getter, setter) { | |
var computedHost = compoundComputed({ | |
$$value: { get: getter, set: setter } | |
}); | |
return new ValueWrapper(computedHost); | |
} | |
// lifeCycle | |
var genName = function(name) { | |
return `on${name[0].toUpperCase()}${name.slice(1)}`; | |
}; | |
function createLifeCycle(lifeCycleHook) { | |
return function(callback) { | |
var vm = ensureCurrentVMInFn(genName(lifeCycleHook)); | |
vm.$on(`hook:${lifeCycleHook}`, callback); | |
}; | |
} | |
function createLifeCycles(lifeCycleHooks, name) { | |
return function(callback) { | |
var vm = ensureCurrentVMInFn(genName(name)); | |
lifeCycleHooks.forEach(function(lifeCycleHook) { | |
return vm.$on(`hook:${lifeCycleHook}`, callback); | |
}); | |
}; | |
} | |
export const onCreated = createLifeCycle(`created`); | |
export const onBeforeMount = createLifeCycle(`beforeMount`); | |
export const onMounted = createLifeCycle(`mounted`); | |
export const onBeforeUpdate = createLifeCycle(`beforeUpdate`); | |
export const onUpdated = createLifeCycle(`updated`); | |
export const onActivated = createLifeCycle(`activated`); | |
export const onDeactivated = createLifeCycle(`deactivated`); | |
export const onBeforeDestroy = createLifeCycle(`beforeDestroy`); | |
export const onDestroyed = createLifeCycle(`destroyed`); | |
export const onErrorCaptured = createLifeCycle(`errorCaptured`); | |
export const onUnmounted = createLifeCycles([`destroyed`, `deactivated`], `unmounted`); | |
// watch | |
var WatcherPreFlushQueueKey = `vfa.key.preFlushQueue`; | |
var WatcherPostFlushQueueKey = `vfa.key.postFlushQueue`; | |
var fallbackVM; | |
function installWatchEnv(vm) { | |
if (!vm[WatcherPreFlushQueueKey]) { | |
vm[WatcherPreFlushQueueKey] = []; | |
vm[WatcherPostFlushQueueKey] = []; | |
vm.$on(`hook:beforeUpdate`, createFlusher(WatcherPreFlushQueueKey)); | |
vm.$on(`hook:updated`, createFlusher(WatcherPostFlushQueueKey)); | |
} | |
} | |
function createFlusher(key) { | |
return function flushQueueWrap() { flushQueue(this, key); }; | |
} | |
function flushQueue(vm, key) { | |
var queue = vm[key]; | |
for (var index = 0; index < queue.length; index++) { | |
queue[index](); | |
} | |
queue.length = 0; | |
} | |
function fallbackFlush(vm) { | |
vm.$nextTick(function() { | |
if (vm[WatcherPreFlushQueueKey].length) { | |
flushQueue(vm, WatcherPreFlushQueueKey); | |
} | |
if (vm[WatcherPostFlushQueueKey].length) { | |
flushQueue(vm, WatcherPostFlushQueueKey); | |
} | |
}); | |
} | |
function flushWatcherCallback(vm, fn, mode) { | |
switch (mode) { | |
case `pre`: | |
fallbackFlush(vm); | |
return vm[WatcherPreFlushQueueKey].push(fn); | |
case `post`: | |
fallbackFlush(vm); | |
return vm[WatcherPostFlushQueueKey].push(fn); | |
case `sync`: | |
return fn(); | |
default: | |
return assert(false, `flush must be one of ["post", "pre", "sync"], but got ${mode}`); | |
} | |
} | |
function createSingleWatcher(vm, source, cb, options) { | |
var getter = isValueWrapper(source) ? _ => source.observe.$$value : source; | |
let cleanUp = noopFn; | |
let cbWrap = function(n, o) { | |
cleanUp(); | |
cb(n, o, function(v) { cleanUp = v; }); | |
}; | |
var callbackRef = function(n, o) { | |
callbackRef = flush; | |
return !options.lazy ? cbWrap(n, o) : flush(n, o); | |
}; | |
var flush = function(n, o) { | |
flushWatcherCallback(vm, _ => cbWrap(n, o), options.flush); | |
}; | |
var unwatch = vm.$watch(getter, callbackRef, { | |
immediate: !options.lazy, | |
deep: options.deep, | |
sync: options.flush === `sync` | |
}); | |
return function stop() { | |
cleanUp(); | |
unwatch(); | |
}; | |
} | |
function createMultiWatcher(vm, sources, cb, options) { | |
var pre = Array(sources.length); | |
var cur = Array(sources.length); | |
let cleanUp = noopFn; | |
let cbWrap = function(n, o) { | |
cleanUp(); | |
cb(n, o, function(v) { cleanUp = v; }); | |
}; | |
var unwatchArr = sources.map(function(source, i) { | |
return (function(_source, _i) { | |
return createSingleWatcher(vm, _source, function(n, v) { | |
if (cur[_i] !== n) { | |
pre[_i] = v; | |
cur[_i] = n; | |
cbWrap(cur, pre); | |
} | |
}, options); | |
})(source, i); | |
}); | |
return function stop() { | |
cleanUp(); | |
unwatchArr.forEach(v => v()); | |
}; | |
} | |
export function watch(source, cb, options = {}) { | |
var opts = Object.assign({ lazy: false, deep: false, flush: `post` }, options); | |
var vm = getCurrentVM(); | |
if (!vm) { | |
if (!fallbackVM) { | |
var Vue_1 = getCurrentVue(); | |
var silent = Vue_1.config.silent; | |
Vue_1.config.silent = true; | |
fallbackVM = new Vue_1(); | |
Vue_1.config.silent = silent; | |
} | |
vm = fallbackVM; | |
opts.flush = `sync`; | |
} | |
installWatchEnv(vm); | |
return (isArray(source) ? createMultiWatcher : createSingleWatcher)(vm, source, cb, opts); | |
} | |
// provide | |
export function provide(provideOpts) { | |
if (provideOpts) { | |
var vm = ensureCurrentVMInFn(`provide`); | |
vm._provided = typeof provideOpts === `function` ? provideOpts.call(vm) : provideOpts; | |
} | |
} | |
// inject | |
export function inject(injectKey) { | |
if (injectKey) { | |
var vm = ensureCurrentVMInFn(`inject`); | |
var source = vm; | |
while (source) { | |
if (source._provided && hasOwn(source._provided, injectKey)) { | |
return source._provided[injectKey]; | |
} | |
source = source.$parent; | |
} | |
vueWarn(`Injection "${injectKey}" not found`, vm); | |
} | |
} | |
// plugin | |
function _install(Vue, mixin) { | |
if (currentVue && currentVue === Vue) { | |
return assert(false, `already installed. Vue.use(plugin) should be called only once`); | |
} | |
Vue.config.optionMergeStrategies.setup = Vue.config.optionMergeStrategies.data; | |
setCurrentVue(Vue); | |
mixin(Vue); | |
} | |
function checkData(vm, propName) { | |
var props = vm.$options.props; | |
var methods = vm.$options.methods; | |
var computed = vm.$options.computed; | |
var msgPrefix = `The setup binding property "${propName}" is already declared`; | |
var msgSuffix = `.`; | |
if (hasOwn(vm.$data, propName)) { | |
msgSuffix = `as a data.`; | |
} else if (props && hasOwn(props, propName)) { | |
msgSuffix = `as a prop.`; | |
} else if (methods && hasOwn(methods, propName)) { | |
msgSuffix = `as a method.`; | |
} else if (computed && hasOwn(computed, propName)) { | |
msgSuffix = `as a computed.`; | |
} | |
if (msgSuffix !== `.`) { | |
vueWarn(msgPrefix + msgSuffix, vm); | |
} | |
} | |
function mixin(Vue) { | |
Vue.mixin({ created: setupMix }); | |
function setupMix() { | |
var vm = this; | |
var setup = vm.$options.setup; | |
if (!setup) { | |
return; | |
} | |
if (typeof setup !== `function`) { | |
return vueWarn(`The "setup" should be a function`, vm); | |
} | |
var binding; | |
var ctx = createContext(vm); | |
setCurrentVM(vm); | |
try { | |
binding = setup(vm.$props || {}, ctx); | |
} catch (err) { | |
vueWarn(`there is an error occuring in "setup"`, vm); | |
console.log(err); | |
} finally { | |
setCurrentVM(null); | |
} | |
if (!binding) { | |
return; | |
} | |
if (typeof binding === `function`) { | |
return vm.$options.render = function(h) { | |
return binding(ctx.props, ctx.slots, ctx.attrs); | |
}; | |
} | |
if(!isPlainObject(binding)) { | |
return assert(false, `"setup" must return a "Object", get "${toString(binding)}"`); | |
} | |
Object.keys(binding).forEach(name => checkData(vm, name)); | |
vm._data2 = observable(unProxy(binding)); | |
Object.keys(binding).forEach(key => proxy(vm, vm._data2, key)); | |
} | |
function createContext(vm) { | |
var ctx = { vm }; | |
var props = [`props`, `parent`, `root`, `refs`, `slots`, `attrs`]; | |
var methodWithoutReturn = [`emit`]; | |
props.forEach(function(key) { | |
Object.defineProperty(ctx, key, { | |
enumerable: true, | |
configurable: true, | |
get: function() { return vm[`$${key}`]; }, | |
set: function() { vueWarn(`Cannot assign for read-only property "${key}"`, vm); } | |
}); | |
}); | |
methodWithoutReturn.forEach(function(key) { | |
Object.defineProperty(ctx, key, { | |
enumerable: true, | |
configurable: true, | |
get: function() { | |
return function() { | |
var args = []; | |
for (var _i = 0; _i < arguments.length; _i++) { | |
args[_i] = arguments[_i]; | |
} | |
vm[`$${key}`].apply(vm, args); | |
}; | |
}, | |
set: noopFn | |
}); | |
}); | |
return ctx; | |
} | |
} | |
export const plugin = { | |
install: function install(Vue) { | |
return _install(Vue, mixin); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment