Skip to content

Instantly share code, notes, and snippets.

@AimWhy
Last active July 15, 2019 05:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AimWhy/dffbc1e03d85436fe8a76d7a5e9f9e03 to your computer and use it in GitHub Desktop.
Save AimWhy/dffbc1e03d85436fe8a76d7a5e9f9e03 to your computer and use it in GitHub Desktop.
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 }
}
<template>
<div>
<pre>
count is ValueWrap &#9; &#9; countWrap is Object &#9;&#9; vCountWrap is ValueWrap &#9;&#9; 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>
'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