8000 fix: unwrapp value on accessing instead of creating · dung13890/vue-function-api@9fdfe2d · GitHub
[go: up one dir, main page]

Skip to content

Commit 9fdfe2d

Browse files
committed
fix: unwrapp value on accessing instead of creating
1 parent a2318ee commit 9fdfe2d

17 files changed

+159
-81
lines changed

src/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ declare module 'vue/types/vue' {
1212
}
1313

1414
interface VueConstructor {
15+
observable<T>(x: any): T;
1516
util: {
1617
warn(msg: string, vm?: Vue);
1718
defineReactive(

src/functions/inject.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { VueConstructor } from 'vue';
1+
import Vue from 'vue';
22
import { getCurrentVue } from '../runtimeContext';
33
import { state } from '../functions/state';
4-
import { Wrapper, ComputedWrapper } from '../wrappers';
5-
import { ensureCurrentVMInFn, isWrapper } from '../helper';
4+
import { isWrapper, Wrapper, ComputedWrapper } from '../wrappers';
5+
import { ensureCurrentVMInFn } from '../helper';
66
import { hasOwn } from '../utils';
77

88
const UNRESOLVED_INJECT = {};
99
export interface Key<T> extends Symbol {}
1010

11-
function resolveInject(provideKey: Key<any>, vm: InstanceType<VueConstructor>): any {
11+
function resolveInject(provideKey: Key<any>, vm: Vue): any {
1212
let source = vm;
1313
while (source) {
1414
// @ts-ignore

src/functions/state.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { Wrapper, ValueWrapper } from '../wrappers';
2-
import { isArray, isPlainObject } from '../utils';
3-
import { observable, upWrapping } from '../helper';
2+
import { observable } from '../reactivity';
43

54
export function state<T>(value: T): T {
6-
return observable(isArray(value) || isPlainObject(value) ? upWrapping(value) : value);
5+
return observable(value);
76
}
87

98
export function value<T>(value: T): Wrapper<T> {
10-
return new ValueWrapper(
11-
observable({ $$state: isArray(value) || isPlainObject(value) ? upWrapping(value) : value })
12-
);
9+
return new ValueWrapper(state({ $$state: value }));
1310
}

src/functions/watch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Vue, { VueConstructor } from 'vue';
22
import { Wrapper } from '../wrappers';
33
import { isArray, assert } from '../utils';
4-
import { isWrapper } from '../helper';
4+
import { isWrapper } from '../wrappers';
55
import { getCurrentVM, getCurrentVue } from '../runtimeContext';
66
import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey } from '../symbols';
77

src/helper.ts

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,6 @@
11
import { VueConstructor } from 'vue';
22
import { getCurrentVue, getCurrentVM } from './runtimeContext';
3-
import { assert, isPlainObject, isArray, proxy } from './utils';
4-
import { AbstractWrapper } from './wrappers';
5-
6-
export function upWrapping(obj: any) {
7-
if (!obj) {
8-
return obj;
9-
}
10-
11-
const result: Record<string, any> = {};
12-
const keys = Object.keys(obj);
13-
for (let index = 0; index < keys.length; index++) {
14-
const key = keys[index];
15-
const value = obj[key];
16-
if (isWrapper(value)) {
17-
proxy(
18-
result,
19-
key,
20-
() => value.value,
21-
(val: any) => {
22-
value.value = val;
23-
}
24-
);
25-
} else if (isPlainObject(value) || isArray(value)) {
26-
result[key] = upWrapping(value);
27-
} else {
28-
result[key] = value;
29-
}
30-
}
31-
32-
return result;
33-
}
34-
35-
export function isWrapper<T>(obj: any): obj is AbstractWrapper<T> {
36-
return obj instanceof AbstractWrapper;
37-
}
3+
import { assert } from './utils';
384

395
export function ensureCurrentVMInFn(hook: string): InstanceType<VueConstructor> {
406
const vm = getCurrentVM();
@@ -44,24 +10,6 @@ export function ensureCurrentVMInFn(hook: string): InstanceType<VueConstructor>
4410
return vm!;
4511
}
4612

47-
export function observable<T = any>(obj: T): T {
48-
const Vue = getCurrentVue();
49-
if (Vue.observable) {
50-
return Vue.observable(obj);
51-
}
52-
53-
const silent = Vue.config.silent;
54-
Vue.config.silent = true;
55-
const vm = new Vue({
56-
data: {
57-
$$state: obj,
58-
},
59-
});
60-
Vue.config.silent = silent;
61-
62-
return vm._data.$$state;
63-
}
64-
6513
export function compoundComputed(computed: {
6614
[key: string]:
6715
| (() => any)

src/install.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { UnknownObject } from './types/basic';
1+
import { AnyObject } from './types/basic';
22
import { hasSymbol, hasOwn, isPlainObject, assert } from './utils';
3-
import { isWrapper } from './helper';
3+
import { isWrapper } from './wrappers';
44
import { setCurrentVue, currentVue } from './runtimeContext';
55
import { VueConstructor } from 'vue';
66

77
/**
88
* Helper that recursively merges two data objects together.
99
*/
10-
function mergeData(to: UnknownObject, from?: UnknownObject): Object {
10+
function mergeData(to: AnyObject, from?: AnyObject): Object {
1111
if (!from) return to;
1212
let key: any;
1313
let toVal: any;

src/reactivity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { observable } from './observable';

src/reactivity/observable.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { AnyObject } from '../types/basic';
2+
import { getCurrentVue } from '../runtimeContext';
3+
import { isObject, def, hasOwn } from '../utils';
4+
import { isWrapper } from '../wrappers';
5+
import { ObservableIdentifierKey, AccessControIdentifierlKey } from '../symbols';
6+
7+
const AccessControlIdentifier = {};
8+
const ObservableIdentifier = {};
9+
10+
function setupAccessControl(target: AnyObject) {
11+
if (!isObject(target)) {
12+
return;
13+
}
14+
if (hasOwn(target, AccessControIdentifierlKey) && target.__ac__ === AccessControlIdentifier) {
15+
return;
16+
}
17+
18+
def(target, AccessControIdentifierlKey, AccessControlIdentifier);
19+
const keys = Object.keys(target);
20+
for (let i = 0; i < keys.length; i++) {
21+
defineAccessControl(target, keys[i]);
22+
}
23+
}
24+
25+
function defineAccessControl(target: AnyObject, key: any) {
26+
const property = Object.getOwnPropertyDescriptor(target, key);
27+
if (!property) {
28+
return;
29+
}
30+
31+
if (property.configurable === false) {
32+
return;
33+
}
34+
35+
let rawVal = target[key];
36+
let isValueWrapper = isWrapper(rawVal);
37+
38+
// we are sure that get and set exist.
39+
const getter = property.get!;
40+
const setter = property.set!;
41+
42+
setupAccessControl(target[key]);
43+
Object.defineProperty(target, key, {
44+
enumerable: true,
45+
configurable: true,
46+
get: function getterHandler() {
47+
if (isValueWrapper) {
48+
return rawVal.value;
49+
} else {
50+
return getter.call(target);
51+
}
52+
},
53+
set: function setterHandler(newVal) {
54+
if (isValueWrapper) {
55+
rawVal.value = newVal;
56+
setupAccessControl(newVal);
57+
return;
58+
}
59+
60+
if (isWrapper(newVal)) {
61+
isValueWrapper = true;
62+
rawVal = newVal;
63+
return;
64+
}
65+
66+
setter.call(target, newVal);
67+
setupAccessControl(newVal);
68+
},
69+
});
70+
}
71+
72+
export function observable<T = any>(obj: T): T {
73+
if (!isObject(obj)) {
74+
return obj;
75+
}
76+
77+
const Vue = getCurrentVue();
78+
let observed: T;
79+
if (Vue.observable) {
80+
observed = Vue.observable(obj);
81+
}
82+
83+
const silent = Vue.config.silent;
84+
Vue.config.silent = true;
85+
const vm = new Vue({
86+
data: {
87+
$$state: obj,
88+
},
89+
});
90+
Vue.config.silent = silent;
91+
observed = vm._data.$$state;
92+
93+
def(observed, ObservableIdentifierKey, ObservableIdentifier);
94+
setupAccessControl(observed);
95+
return observed;
96+
}

src/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { VueConstructor } from 'vue';
22
import { SetupContext } from './types/vue';
3-
import { isWrapper } from './helper';
3+
import { isWrapper } from './wrappers';
44
import { setCurrentVM } from './runtimeContext';
55
import { isPlainObject, assert, proxy, isFunction } from './utils';
66
import { value } from './functions/state';

src/symbols.ts

Lines changed: 2 additions & 0 deletions
37BF
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ function createSymbol(name: string): string {
66

77
export const WatcherPreFlushQueueKey = createSymbol('vfa.key.preFlushQueue');
88
export const WatcherPostFlushQueueKey = createSymbol('vfa.key.postFlushQueue');
9+
export const AccessControIdentifierlKey = createSymbol('vfa.key.accessControIdentifier');
10+
export const ObservableIdentifierKey = createSymbol('vfa.key.observableIdentifier');

src/types/basic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export type UnknownObject = { [x: string]: any };
1+
export type AnyObject = Record<string | symbol, any>;

src/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ export function proxy(target: any, key: string, getter: Function, setter?: Funct
1717
Object.defineProperty(target, key, sharedPropertyDefinition);
1818
}
1919

20+
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
21+
Object.defineProperty(obj, key, {
22+
value: val,
23+
enumerable: !!enumerable,
24+
writable: true,
25+
configurable: true,
26+
});
27+
}
28+
2029
const hasOwnProperty = Object.prototype.hasOwnProperty;
2130
export function hasOwn(obj: Object | any[], key: string): boolean {
2231
return hasOwnProperty.call(obj, key);
@@ -30,6 +39,10 @@ export function isArray<T>(x: unknown): x is T[] {
3039
return toString(x) === '[object Array]';
3140
}
3241

42+
export function isObject(val: unknown): val is Record<any, any> {
43+
return val !== null && typeof val === 'object';
44+
}
45+
3346
export function isPlainObject<T extends Object = {}>(x: unknown): x is T {
3447
return toString(x) === '[object Object]';
3548
}

src/wrappers/AbstractWrapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import Vue from 'vue';
22
import { getCurrentVue } from '../runtimeContext';
3-
import { proxy, hasOwn } from '../utils';
3+
import { proxy, hasOwn, def } from '../utils';
44

55
export default abstract class AbstractWrapper<V> {
66
protected _propName?: string;
77
protected _vm?: Vue;
88
abstract value: V;
99

1010
setVmProperty(vm: Vue, propName: string) {
11-
this._vm = vm;
12-
this._propName = propName;
11+
def(this, '_vm', vm);
12+
def(this, '_propName', propName);
1313

1414
const props = vm.$options.props;
1515
const methods = vm.$options.methods;

src/wrappers/ComputedWrapper.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getCurrentVue } from '../runtimeContext';
2-
import { proxy } from '../utils';
2+
import { proxy, def } from '../utils';
33
import AbstractWrapper from './AbstractWrapper';
44

55
interface ComputedInternal<T> {
@@ -8,16 +8,19 @@ interface ComputedInternal<T> {
88
}
99

1010
export default class ComputedWrapper<V> extends AbstractWrapper<V> {
11-
constructor(private internal: ComputedInternal<V>) {
11+
private _internal!: ComputedInternal<V>;
12+
13+
constructor(internal: ComputedInternal<V>) {
1214
super();
15+
def(this, '_internal', internal);
1316
}
1417

1518
get value() {
16-
return this.internal.read();
19+
return this._internal.read();
1720
}
1821

1922
set value(val: V) 10000 {
20-
if (!this.internal.write) {
23+
if (!this._internal.write) {
2124
if (process.env.NODE_ENV !== 'production') {
2225
getCurrentVue().util.warn(
2326
'Computed property' +
@@ -27,7 +30,7 @@ export default class ComputedWrapper<V> extends AbstractWrapper<V> {
2730
);
2831
}
2932
} else {
30-
this.internal.write(val);
33+
this._internal.write(val);
3134
}
3235
}
3336

src/wrappers/ValueWrapper.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
import { proxy } from '../utils';
1+
import { proxy, def } from '../utils';
22
import AbstractWrapper from './AbstractWrapper';
33

44
interface ValueInternal<T> {
55
$$state: T;
66
}
77

88
export default class ValueWrapper<V> extends AbstractWrapper<V> {
9-
constructor(private internal: ValueInternal<V>) {
9+
private _internal!: ValueInternal<V>;
10+
11+
constructor(internal: ValueInternal<V>) {
1012
super();
13+
def(this, '_internal', internal);
1114
}
1215

1316
get value() {
14-
return this.internal.$$state;
17+
return this._internal.$$state;
1518
}
1619

1720
set value(v: V) {
18-
this.internal.$$state = v;
21+
this._internal.$$state = v;
1922
}
2023

2124
exposeToDevtool() {

src/wrappers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ import ComputedWrapper from './ComputedWrapper';
55
export interface Wrapper<V> {
66
value: V;
77
}
8+
export function isWrapper<T>(obj: any): obj is AbstractWrapper<T> {
9+
return obj instanceof AbstractWrapper;
10+
}
811
export { ValueWrapper, ComputedWrapper, AbstractWrapper };

0 commit comments

Comments
 (0)
0