(source: T, detectCycles?: boolean) => T
14 | export type toJSAny = (source: any, detectCycles?: boolean) => any
15 | export type toJSArr = (source: any, detectCycles: boolean, alreadySeen: Array<[any, any]>) => any
16 |
17 | export interface Config {
18 | toJS: toJST | toJSAny | toJSArr,
19 | isObservable: isObservable,
20 | observable?: observable
21 | }
22 |
23 | export class Options {
24 | public static options: Config;
25 |
26 | public static saveOptions(config: Config): void {
27 | Options.options = config;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { _Vue } from './install';
2 | import { Options } from './options';
3 |
4 | export function isObject(data: any): boolean {
5 | return data !== null && typeof data === 'object';
6 | }
7 |
8 | export function assert(condition: boolean, msg: string): void {
9 | if (!condition) {
10 | throw new Error(`[vue-mobx]: ${msg}`);
11 | }
12 | }
13 |
14 | const $mobx: string = '__mobxDidRunLazyInitializers';
15 | const $$mobx: string = '$mobx';
16 |
17 | function getProto(obj: object): object {
18 | return Object.getPrototypeOf(obj);
19 | }
20 |
21 | function getOwnProps(obj: object): string[] {
22 | // ES6 class methods aren't enumerable, can't use Object.keys
23 | return Object.getOwnPropertyNames(obj);
24 | }
25 |
26 | export function getValidModel(models: object): object {
27 | const res = {};
28 |
29 | Object.keys(models).forEach((key) => {
30 | // support typescript
31 | if (Options.options.isObservable(models[key])) {
32 | res[key] = models[key]
33 | }
34 | });
35 |
36 | return res;
37 | }
38 |
39 | export function getValidAction(models: object, methods: object): object {
40 | const res = {};
41 |
42 | Object.keys(models).forEach((key) => {
43 | const props: string[] = getOwnProps(getProto(models[key]));
44 | for (let i = 0, l = props.length; i < l; i++) {
45 | if (typeof models[key][props[i]] === 'function' && models[key][props[i]].isMobxAction) {
46 | assert(!methods[props[i]], `The "${props[i]}" method is already defined in methods.`);
47 | res[props[i]] = models[key][props[i]].bind(models[key]);
48 | }
49 | }
50 | });
51 |
52 | return res;
53 | }
54 |
55 | export function getMobxData(models: object): object {
56 | const res = {};
57 |
58 | Object.keys(models).forEach((key) => {
59 | const props: string[] = getOwnProps(getProto(models[key]));
60 | // res = (Object as any).assign({}, res, {
61 | // ...models[key].$mobx.values,
62 | // })
63 | for (let i = 0, l = props.length; i < l; i++) {
64 | if (props[i] !== 'constructor' && props[i] !== $mobx && typeof models[key][props[i]] !== 'function') {
65 | res[props[i]] = models[key][props[i]];
66 | }
67 | }
68 | });
69 |
70 | return res;
71 | }
72 |
73 | export function createComputedProps(models: object, data: object, computed: object, vm: any): object {
74 | const res = {};
75 |
76 | Object.keys(models).forEach((key) => {
77 | const model = models[key];
78 | const props = getOwnProps(getProto(model));
79 |
80 | for (let i = 0, l = props.length; i < l; i++) {
81 | if (props[i] !== $mobx && props[i] !== $$mobx && props[i] !== 'constructor' && typeof model[props[i]] !== 'function') {
82 | assert(!(props[i] in data), `The computed property "${props[i]}" is already defined in data.`);
83 | assert(!(props[i] in computed), `The computed property "${props[i]}" is already defined in computed.`);
84 |
85 | const property = Object.getOwnPropertyDescriptor(model, props[i]);
86 | (_Vue as any).util.defineReactive(model, props[i], null, null, true);
87 |
88 | res[props[i]] = {
89 | get() {
90 | return model[props[i]];
91 | },
92 |
93 | set(val: any) {
94 | (property as any).set.call(vm, val);
95 | },
96 | };
97 | }
98 | }
99 | });
100 |
101 | return res;
102 | }
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {observable, isObservable, action, isAction, toJS} from 'mobx';
3 | import Vue from 'vue';
4 | import VueMobx from '../dist/index';
5 |
6 | Vue.use(VueMobx, {
7 | // must
8 | toJS,
9 | // must
10 | isObservable,
11 | // optional
12 | observable,
13 | });
14 |
15 | class TestModel{
16 | @observable
17 | name = 'vue-mobx';
18 | count = 3;
19 | @observable
20 | info = {
21 | project: 'vue-mobx'
22 | }
23 |
24 | @action
25 | changeName(){
26 | this.name = 'mobx-vue';
27 | }
28 |
29 | changeCount(){
30 | this.count = 2;
31 | }
32 |
33 | @action
34 | async actionTest () {
35 | const v = await Promise.resolve('async action');
36 | this.name = v;
37 | }
38 | }
39 |
40 | test('fromMobx must be a object', (t) => {
41 | t.plan(4);
42 | const error = t.throws(() => {
43 | new Vue({
44 | template: 'parameter test
',
45 | fromMobx: 'test'
46 | })
47 | }, Error);
48 |
49 | const error2 = t.throws(() => {
50 | new Vue({
51 | template: 'parameter test
',
52 | fromMobx: 3
53 | })
54 | }, Error);
55 |
56 | t.true(error.message.includes('string'));
57 | t.true(error2.message.includes('number'));
58 | });
59 |
60 | test('same property will throw error', (t) => {
61 | t.plan(4);
62 |
63 | let testModel = new TestModel();
64 |
65 | const error = t.throws(() => {
66 | new Vue({
67 | template: 'same property will throw error
',
68 | data: {
69 | name: 'hello'
70 | },
71 | fromMobx: {
72 | testModel
73 | }
74 | })
75 | }, Error);
76 |
77 | const error2 = t.throws(() => {
78 | new Vue({
79 | template: 'same property will throw error
',
80 | data: {
81 | n: 'hello'
82 | },
83 | computed: {
84 | name () {
85 | return 'error';
86 | }
87 | },
88 | fromMobx: {
89 | testModel
90 | }
91 | })
92 | }, Error);
93 |
94 | t.true(error.message.includes('already defined in data'));
95 | t.true(error2.message.includes('already defined in computed'));
96 | });
97 |
98 | test('property which is not obserable will not be merged', (t) => {
99 | t.plan(4);
100 |
101 | let testModel = new TestModel();
102 |
103 | const vm = new Vue({
104 | template: 'same property will throw error
',
105 | fromMobx: {
106 | testModel
107 | }
108 | })
109 |
110 | t.true(vm.name === 'vue-mobx');
111 | t.true(vm.hasOwnProperty('changeName'));
112 | t.false(vm.hasOwnProperty('count'));
113 | t.false(vm.hasOwnProperty('changeCount'));
114 | });
115 |
116 | class Model2 {
117 | test = '111';
118 | }
119 |
120 | test('test methods and data change', (t) => {
121 | t.plan(8);
122 |
123 | let testModel = new TestModel();
124 |
125 | const vm = new Vue({
126 | template: 'same property will throw error
',
127 | data(){
128 | return {
129 | message: 'hello'
130 | }
131 | },
132 |
133 | fromMobx: {
134 | testModel
135 | }
136 | });
137 |
138 | t.is(vm.info.project, 'vue-mobx')
139 | t.is(typeof vm.$observable, 'function');
140 | t.is(typeof vm.$isObservable, 'function');
141 | t.is(typeof vm.$toJS, 'function');
142 |
143 | t.true(vm.$isObservable(testModel));
144 | t.false(vm.$isObservable(new Model2()));
145 | t.deepEqual({ project: 'vue-mobx' }, vm.$toJS(vm.info));
146 |
147 | // call method
148 | vm.changeName();
149 |
150 | t.is(vm.name, 'mobx-vue');
151 | });
152 |
153 | test('test async action', async (t) => {
154 | t.plan(4);
155 |
156 | let testModel = new TestModel();
157 |
158 | const vm = new Vue({
159 | template: 'vm1
',
160 | fromMobx: {
161 | testModel
162 | }
163 | });
164 |
165 | const vm2 = new Vue({
166 | template: 'vm2
',
167 | fromMobx: {
168 | testModel
169 | }
170 | });
171 |
172 | t.is(vm.name, 'vue-mobx');
173 | t.is(vm2.name, 'vue-mobx');
174 |
175 | await vm.actionTest();
176 |
177 | t.is(vm.name, 'async action');
178 | t.is(vm2.name, 'async action');
179 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "rootDir": "src",
7 | "outDir": "lib",
8 | "declaration": true,
9 | "declarationDir": "types",
10 | "types": [
11 | "node"
12 | ],
13 | "strict": true,
14 | "isolatedModules": false,
15 | "experimentalDecorators": true,
16 | "noImplicitAny": true,
17 | "noImplicitReturns": true,
18 | "removeComments": true,
19 | "suppressImplicitAnyIndexErrors": true,
20 | "allowSyntheticDefaultImports": true,
21 | "allowUnreachableCode": true,
22 | "allowUnusedLabels": true
23 | },
24 | "include": [
25 | "src/**/*.ts"
26 | ],
27 | "compileOnSave": false
28 | }
29 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "enable": true,
4 | "jsEnable": true,
5 | "rules": {
6 | "max-line-length": [true, 200],
7 | "indent": [true, "indent", 4],
8 | "interface-name": [true, "never-prefix"],
9 | "jsdoc-format": false,
10 | "no-consecutive-blank-lines": false,
11 | "no-console": [false],
12 | "no-duplicate-variable": true,
13 | "semicolon": ["always"],
14 | "triple-equals": [true, "allow-null-check"],
15 | "comment-format": [true, "check-space"],
16 | "no-trailing-whitespace": false,
17 | "align": [false],
18 | "eofline": false,
19 | "max-classes-per-file": [true, 5],
20 | "no-unused-variable": true,
21 | "quotemark": [true, "single", "avoid-escape"],
22 | "object-literal-sort-keys":false,
23 | "ordered-imports": [false],
24 | "no-string-literal": false
25 | },
26 | "jsRules": {
27 | "max-line-length": [true, 120],
28 | "indent": [true, "indent", 4],
29 | "comment-format": [true, "check-space"],
30 | "no-console": [false],
31 | "no-duplicate-variable": true,
32 | "semicolon": ["always"],
33 | "triple-equals": [true, "allow-null-check"],
34 | "jsdoc-format": false,
35 | "no-trailing-whitespace": false,
36 | "align": [false],
37 | "eofline": false,
38 | "max-classes-per-file": [true, 5],
39 | "no-unused-variable": true,
40 | "quotemark": [true, "single", "avoid-escape"],
41 | "object-literal-sort-keys":false
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/types/connect.d.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | export declare function connect(mapModels: object): (vueComponent: C) => C;
3 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { PluginObject } from 'vue';
2 | import { Config } from './options';
3 | declare const VueMobx: PluginObject;
4 | export default VueMobx;
5 |
--------------------------------------------------------------------------------
/types/install.d.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { Config } from './options';
3 | export declare let _Vue: typeof Vue;
4 | export declare function install(instance: typeof Vue, config: Config): void;
5 |
--------------------------------------------------------------------------------
/types/mixin.d.ts:
--------------------------------------------------------------------------------
1 | import { Config } from './options';
2 | export default function applyMixin(config: Config): void;
3 |
--------------------------------------------------------------------------------
/types/options.d.ts:
--------------------------------------------------------------------------------
1 | import { IObservableFactory, IObservableFactories } from 'mobx';
2 | export declare type isObservable = (value: any, property?: string) => boolean;
3 | export declare type observable = IObservableFactory & IObservableFactories & {
4 | deep: {
5 | struct(initialValue?: T): T;
6 | };
7 | ref: {
8 | struct(initialValue?: T): T;
9 | };
10 | };
11 | export declare type toJST = (source: T, detectCycles?: boolean) => T;
12 | export declare type toJSAny = (source: any, detectCycles?: boolean) => any;
13 | export declare type toJSArr = (source: any, detectCycles: boolean, alreadySeen: Array<[any, any]>) => any;
14 | export interface Config {
15 | toJS: toJST | toJSAny | toJSArr;
16 | isObservable: isObservable;
17 | observable?: observable;
18 | }
19 | export declare class Options {
20 | static options: Config;
21 | static saveOptions(config: Config): void;
22 | }
23 |
--------------------------------------------------------------------------------
/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | export declare function isObject(data: any): boolean;
2 | export declare function assert(condition: boolean, msg: string): void;
3 | export declare function getValidModel(models: object): object;
4 | export declare function getValidAction(models: object, methods: object): object;
5 | export declare function getMobxData(models: object): object;
6 | export declare function createComputedProps(models: object, data: object, computed: object, vm: any): object;
7 |
--------------------------------------------------------------------------------