├── .gitignore
├── README.md
├── babel.config.js
├── example
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
└── helloworld
│ ├── App.js
│ ├── index.html
│ ├── main.js
│ └── vue.runtime.esm-browser.js
├── jest.config.ts
├── lib
├── mini-vue3.cjs.js
├── mini-vue3.esm.js
└── types
│ └── mini-vue3.d.ts
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── reactivity
│ ├── __tests__
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ ├── shallowReactive.spec.ts
│ │ └── shallowReadonly.spec.ts
│ ├── index.ts
│ └── src
│ │ ├── baseHandlers.ts
│ │ ├── computed.ts
│ │ ├── dep.ts
│ │ ├── effect.ts
│ │ ├── enumeration.ts
│ │ ├── reactive.ts
│ │ └── ref.ts
├── runtime-core
│ ├── index.ts
│ └── src
│ │ ├── component.ts
│ │ ├── componentEmit.ts
│ │ ├── componentProps.ts
│ │ ├── componentPublicInstance.ts
│ │ ├── componentSlots.ts
│ │ ├── creatApp.ts
│ │ ├── h.ts
│ │ ├── helpers
│ │ └── renderSlots.ts
│ │ ├── renderer.ts
│ │ └── vnode.ts
└── shared
│ ├── __tests__
│ ├── base.spec.ts
│ └── isType.spec.ts
│ ├── index.ts
│ └── src
│ ├── ShapFlags.ts
│ ├── base.ts
│ └── isType.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-vue
2 |
3 | In order to learn vue source code
4 |
5 | ## reactivity
6 |
7 | 1. reactive
8 |
9 | - [x] reactive
10 | - [x] readonly
11 | - [x] isReactive
12 | - [x] isReadonly
13 | - [x] isProxy
14 | - [x] shallowReactive
15 | - [x] shallowReadonly
16 | - [ ] markRaw
17 | - [x] toRaw
18 |
19 | 2. effect
20 |
21 | - [x] effect
22 | - [x] stop
23 | - [x] trigger
24 | - [x] track
25 | - [ ] enableTracking
26 | - [ ] pauseTracking
27 | - [ ] resetTracking
28 |
29 | 3. refs
30 |
31 | - [x] ref
32 | - [x] shallowRef
33 | - [x] isRef
34 | - [x] toRef
35 | - [x] toRefs
36 | - [x] unref
37 | - [x] proxyRefs
38 | - [ ] customRef
39 | - [ ] triggerRef
40 |
41 | 4. computed
42 |
43 | - [x] computed
44 |
45 | 5. effectScope
46 |
47 | - [ ] effectScope
48 | - [ ] getCurrentScope
49 | - [ ] onScopeDispose
50 |
51 | ## runtime-core
52 |
53 | ## runtime-dom
54 |
55 | ## compiler-core
56 |
57 | ## compiler-dom
58 |
59 | ## compiler-sfc
60 |
61 | ## compiler-ssr
62 |
63 | ## server-renderer
64 |
65 | ## shared
66 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript'
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/mini-vue3.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | // emit
8 | return h('div', {}, [
9 | h('div', {}, 'App'),
10 | h(Foo, {
11 | onAdd(a, b) {
12 | console.log('onAdd', a, b)
13 | },
14 | onAddFoo() {
15 | console.log('onAddFoo')
16 | }
17 | })
18 | ])
19 | },
20 |
21 | setup() {
22 | return {}
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from '../../lib/mini-vue3.esm.js'
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log('emit add')
7 | emit('add', 1, 2)
8 | emit('add-foo')
9 | }
10 |
11 | return {
12 | emitAdd
13 | }
14 | },
15 | render() {
16 | const btn = h(
17 | 'button',
18 | {
19 | onClick: this.emitAdd
20 | },
21 | 'emitAdd'
22 | )
23 |
24 | const foo = h('p', {}, 'foo')
25 | return h('div', {}, [foo, btn])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/mini-vue3.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from '../../lib/mini-vue3.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | const app = h('div', {}, 'App')
8 | // object key
9 | const foo = h(
10 | Foo,
11 | {},
12 | {
13 | header: ({ age }) => [
14 | h('p', {}, 'header' + age),
15 | createTextVNode('你好呀')
16 | ],
17 | footer: () => h('p', {}, 'footer'),
18 | default: () => h('p', {}, 'footer2')
19 | }
20 | )
21 | // 数组 vnode
22 | // const foo = h(Foo, {}, h("p", {}, "123"));
23 | return h('div', {}, [app, foo])
24 | },
25 |
26 | setup() {
27 | return {}
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from '../../lib/mini-vue3.esm.js'
2 |
3 | export const Foo = {
4 | setup() {
5 | return {}
6 | },
7 | render() {
8 | const foo = h('p', {}, 'foo')
9 |
10 | // Foo .vnode. children
11 | console.log(this.$slots)
12 | // children -> vnode
13 | //
14 | // renderSlots
15 | // 具名插槽
16 | // 1. 获取到要渲染的元素 1
17 | // 2. 要获取到渲染的位置
18 | // 作用域插槽
19 | const age = 18
20 | return h('div', {}, [
21 | renderSlots(this.$slots, 'header', {
22 | age
23 | }),
24 | foo,
25 | renderSlots(this.$slots, 'footer'),
26 | renderSlots(this.$slots, 'default')
27 | ])
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/mini-vue3.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from '../../lib/mini-vue3.esm.js'
2 | import { Foo } from './Foo.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | render() {
7 | return h('div', {}, [h('p', {}, 'currentInstance demo'), h(Foo)])
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance()
12 | console.log('App:', instance)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from '../../lib/mini-vue3.esm.js'
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup() {
6 | const instance = getCurrentInstance()
7 | console.log('Foo:', instance)
8 | return {}
9 | },
10 | render() {
11 | return h('div', {}, 'foo')
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/mini-vue3.esm.js'
2 | import { App } from './App.js'
3 |
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/example/helloworld/App.js:
--------------------------------------------------------------------------------
1 | // import { h } from './vue.runtime.esm-browser.js'
2 | import { h } from '../../lib/mini-vue3.esm.js'
3 | window.self = null
4 | const Foo = {
5 | setup(props) {
6 | console.log(props)
7 |
8 | props.count++
9 | console.log(props)
10 | },
11 | render() {
12 | return h('span', {}, `count: ${this.count}`)
13 | }
14 | }
15 | export const App = {
16 | setup() {
17 | return {
18 | msg: 'mini-vue'
19 | }
20 | },
21 | render() {
22 | window.self = this
23 | return h(
24 | 'div',
25 | {
26 | class: ['bold'],
27 | id: 'root',
28 | onClick: () => {
29 | console.log('click')
30 | }
31 | },
32 | [
33 | h('span', { class: 'red' }, 'hi '),
34 | h('span', { class: 'blue' }, `"${this.msg}"`),
35 | h(Foo, { count: 999 })
36 | ]
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 | // import { createApp } from './vue.runtime.esm-browser.js'
2 | import { createApp } from '../../lib/mini-vue3.esm.js'
3 | import { App } from './App.js'
4 | const rootContainer = document.querySelector('#app')
5 | createApp(App).mount(rootContainer)
6 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property and type check, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | export default {
7 | // All imported modules in your tests should be mocked automatically
8 | // automock: false,
9 |
10 | // Stop running tests after `n` failures
11 | // bail: 0,
12 |
13 | // The directory where Jest should store its cached dependency information
14 | // cacheDirectory: "C:\\Users\\whylost\\AppData\\Local\\Temp\\jest",
15 |
16 | // Automatically clear mock calls and instances between every test
17 | clearMocks: true,
18 |
19 | // Indicates whether the coverage information should be collected while executing the test
20 | collectCoverage: true,
21 |
22 | // An array of glob patterns indicating a set of files for which coverage information should be collected
23 | collectCoverageFrom: ['src/*/src/**/*.ts'],
24 |
25 | // The directory where Jest should output its coverage files
26 | coverageDirectory: 'coverage',
27 |
28 | // An array of regexp pattern strings used to skip coverage collection
29 | // coveragePathIgnorePatterns: [
30 | // "\\\\node_modules\\\\"
31 | // ],
32 |
33 | // Indicates which provider should be used to instrument code for coverage
34 | // coverageProvider: 'v8',
35 |
36 | // A list of reporter names that Jest uses when writing coverage reports
37 | coverageReporters: ['html', 'lcov', 'text'],
38 |
39 | // An object that configures minimum threshold enforcement for coverage results
40 | // coverageThreshold: undefined,
41 |
42 | // A path to a custom dependency extractor
43 | // dependencyExtractor: undefined,
44 |
45 | // Make calling deprecated APIs throw helpful error messages
46 | // errorOnDeprecated: false,
47 |
48 | // Force coverage collection from ignored files using an array of glob patterns
49 | // forceCoverageMatch: [],
50 |
51 | // A path to a module which exports an async function that is triggered once before all test suites
52 | // globalSetup: undefined,
53 |
54 | // A path to a module which exports an async function that is triggered once after all test suites
55 | // globalTeardown: undefined,
56 |
57 | // A set of global variables that need to be available in all test environments
58 | // globals: {},
59 |
60 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
61 | // maxWorkers: "50%",
62 |
63 | // An array of directory names to be searched recursively up from the requiring module's location
64 | // moduleDirectories: [
65 | // "node_modules"
66 | // ],
67 |
68 | // An array of file extensions your modules use
69 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
70 |
71 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
72 | // moduleNameMapper: {},
73 |
74 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
75 | // modulePathIgnorePatterns: [],
76 |
77 | // Activates notifications for test results
78 | // notify: false,
79 |
80 | // An enum that specifies notification mode. Requires { notify: true }
81 | // notifyMode: "failure-change",
82 |
83 | // A preset that is used as a base for Jest's configuration
84 | // preset: undefined,
85 |
86 | // Run tests from one or more projects
87 | // projects: undefined,
88 |
89 | // Use this configuration option to add custom reporters to Jest
90 | // reporters: undefined,
91 |
92 | // Automatically reset mock state between every test
93 | // resetMocks: false,
94 |
95 | // Reset the module registry before running each individual test
96 | // resetModules: false,
97 |
98 | // A path to a custom resolver
99 | // resolver: undefined,
100 |
101 | // Automatically restore mock state between every test
102 | // restoreMocks: false,
103 |
104 | // The root directory that Jest should scan for tests and modules within
105 | rootDir: __dirname,
106 |
107 | // A list of paths to directories that Jest should use to search for files in
108 | // roots: [
109 | // ""
110 | // ],
111 |
112 | // Allows you to use a custom runner instead of Jest's default test runner
113 | // runner: "jest-runner",
114 |
115 | // The paths to modules that run some code to configure or set up the testing environment before each test
116 | // setupFiles: [],
117 |
118 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
119 | // setupFilesAfterEnv: [],
120 |
121 | // The number of seconds after which a test is considered as slow and reported as such in the results.
122 | // slowTestThreshold: 5,
123 |
124 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
125 | // snapshotSerializers: [],
126 |
127 | // The test environment that will be used for testing
128 | testEnvironment: 'jsdom'
129 |
130 | // Options that will be passed to the testEnvironment
131 | // testEnvironmentOptions: {},
132 |
133 | // Adds a location field to test results
134 | // testLocationInResults: false,
135 |
136 | // The glob patterns Jest uses to detect test files
137 | // testMatch: [
138 | // "**/__tests__/**/*.[jt]s?(x)",
139 | // "**/?(*.)+(spec|test).[tj]s?(x)"
140 | // ],
141 |
142 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
143 | // testPathIgnorePatterns: [
144 | // "\\\\node_modules\\\\"
145 | // ],
146 |
147 | // The regexp pattern or array of patterns that Jest uses to detect test files
148 | // testRegex: [],
149 |
150 | // This option allows the use of a custom results processor
151 | // testResultsProcessor: undefined,
152 |
153 | // This option allows use of a custom test runner
154 | // testRunner: "jest-circus/runner",
155 |
156 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
157 | // testURL: "http://localhost",
158 |
159 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
160 | // timers: "real",
161 |
162 | // A map from regular expressions to paths to transformers
163 | // transform: undefined,
164 |
165 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
166 | // transformIgnorePatterns: [
167 | // "\\\\node_modules\\\\",
168 | // "\\.pnp\\.[^\\\\]+$"
169 | // ],
170 |
171 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
172 | // unmockedModulePathPatterns: undefined,
173 |
174 | // Indicates whether each individual test should be reported during the run
175 | // verbose: undefined,
176 |
177 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
178 | // watchPathIgnorePatterns: [],
179 |
180 | // Whether to use watchman for file crawling
181 | // watchman: true,
182 | }
183 |
--------------------------------------------------------------------------------
/lib/mini-vue3.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | /** 对象属性合并 */
6 | const extend = Object.assign;
7 | /** 对象自身属性中是否具有指定的属性 */
8 | const hasOwnProperty = Object.prototype.hasOwnProperty;
9 | const hasOwn = (val, key) => hasOwnProperty.call(val, key);
10 | /** 空对象 */
11 | const EMPTY_OBJ = {};
12 | /** 是on开头的事件 */
13 | const isOn = (key) => /^on[A-Z]/.test(key);
14 | /* -n 字符串驼峰化 */
15 | const camelize = (str) => str.replace(/-(\w)/g, (_, c) => {
16 | return c ? c.toUpperCase() : '';
17 | });
18 | /* 首字母大写 */
19 | const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
20 | /* 变成事件名称 */
21 | const toHandlerKey = (str) => (str ? 'on' + capitalize(str) : '');
22 |
23 | /** 判断是不是对象 */
24 | const isObject = (val) => !!val && typeof val === 'object';
25 | /** 判断是不是String */
26 | const isString = (val) => typeof val === 'string';
27 | /** 判断是不是函数 */
28 | const isFunction = (val) => typeof val === 'function';
29 |
30 | // REVIEW 这一段依赖收集的逻辑关系 需要多复习
31 | const targetMap = new WeakMap();
32 | /**
33 | * 触发依赖执行(从reactive的target->key->dep 然后收集依赖)
34 | */
35 | function trigger(target, key) {
36 | let depsMap = targetMap.get(target);
37 | // 没有收集过依赖(tracked),直接跳过trigger
38 | if (!depsMap)
39 | return;
40 | let dep = depsMap.get(key);
41 | dep && triggerEffects(dep);
42 | }
43 | /**
44 | * 触发依赖执行(直接触发dep中的依赖)
45 | */
46 | function triggerEffects(dep) {
47 | dep.forEach((effect) => {
48 | if (effect.scheduler) {
49 | effect.scheduler();
50 | }
51 | else {
52 | effect.run();
53 | }
54 | });
55 | }
56 |
57 | // 储存reactive和其原始对象对应关系的 全局WeakMap
58 | const reactiveMap = new WeakMap();
59 | const shallowReactiveMap = new WeakMap();
60 | const readonlyMap = new WeakMap();
61 | const shallowReadonlyMap = new WeakMap();
62 | function reactive(raw) {
63 | return createReactiveObject(raw, reactiveHandlers, reactiveMap);
64 | }
65 | /**
66 | * 返回原始对象的只读代理
67 | * @param raw
68 | */
69 | function readonly(raw) {
70 | return createReactiveObject(raw, readonlyHandlers, readonlyMap);
71 | }
72 | function shallowReadonly(raw) {
73 | return createReactiveObject(raw, shallowReadonlyHandlers, shallowReadonlyMap);
74 | }
75 | /**
76 | * 创建反应对象
77 | * @param target 原始对象
78 | * @param baseHandlers Proxy处理函数的对象
79 | */
80 | function createReactiveObject(target, baseHandlers, proxyMap) {
81 | // target 已经具有相应的 Proxy
82 | const existingProxy = proxyMap.get(target);
83 | if (existingProxy) {
84 | return existingProxy;
85 | }
86 | const proxy = new Proxy(target, baseHandlers);
87 | proxyMap.set(target, proxy);
88 | return proxy;
89 | }
90 |
91 | const reactvieGet = createGetter();
92 | const reactvieSet = createSetter();
93 | /** reactive 拦截方法 */
94 | const reactiveHandlers = {
95 | get: reactvieGet,
96 | set: reactvieSet
97 | };
98 | const readonlyGet = createGetter(true);
99 | const readonlySet = readonlySetter;
100 | /** readonly 拦截方法 */
101 | const readonlyHandlers = {
102 | get: readonlyGet,
103 | set: readonlySet
104 | };
105 | const shallowReadonlyGet = createGetter(true, true);
106 | const shallowReadonlySet = readonlySetter;
107 | /** shallowReadonly 拦截方法 */
108 | const shallowReadonlyHandlers = {
109 | get: shallowReadonlyGet,
110 | set: shallowReadonlySet
111 | };
112 | /**
113 | * 创建proxy对象的get方法
114 | * @param isReadonly
115 | */
116 | function createGetter(isReadonly = false, shallow = false) {
117 | return (target, key, receiver) => {
118 | // isReactive和isReadonly 检测 不是readonly的就是reactive
119 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
120 | return !isReadonly;
121 | }
122 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
123 | return isReadonly;
124 | }
125 | else if (key === "__v_raw" /* RAW */ &&
126 | receiver ===
127 | (isReadonly
128 | ? shallow
129 | ? shallowReadonlyMap
130 | : readonlyMap
131 | : shallow
132 | ? shallowReactiveMap
133 | : reactiveMap).get(target)) {
134 | return target;
135 | }
136 | const res = Reflect.get(target, key, receiver);
137 | // 不执行嵌套对象的深度readonly转换
138 | if (shallow) {
139 | return res;
140 | }
141 | // 实现 reactive和readonly 的嵌套对象转换
142 | if (isObject(res)) {
143 | return isReadonly ? readonly(res) : reactive(res);
144 | }
145 | return res;
146 | };
147 | }
148 | /**
149 | * 创建非readoly Proxy对象的 set 方法
150 | */
151 | function createSetter() {
152 | // reactive创建的代理对象 触发 set 操作时 触发依赖执行
153 | return (target, key, value, receiver) => {
154 | const res = Reflect.set(target, key, value, receiver);
155 | // 触发依赖执行
156 | trigger(target, key);
157 | return res;
158 | };
159 | }
160 | /**
161 | * readoly Proxy对象的 set 方法
162 | */
163 | function readonlySetter(target, key) {
164 | // readonly创建的代理对象, 触发 set 操作时控制台进行警告提示
165 | console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
166 | return true;
167 | }
168 |
169 | function emit(instance, eventName, ...args) {
170 | const { props } = instance;
171 | const handlerName = toHandlerKey(camelize(eventName));
172 | const handler = props[handlerName];
173 | handler && handler(...args);
174 | }
175 |
176 | function initProps(instance, rawProps) {
177 | instance.props = rawProps || {};
178 | }
179 |
180 | const publicPropertiesMap = extend(Object.create(null), {
181 | $el: (i) => i.vnode.el,
182 | $slots: (i) => i.slots
183 | });
184 | /** publicInstanceProxyHandlers */
185 | const publicInstanceProxyHandlers = {
186 | get({ _: instance }, key) {
187 | const { setupState, props } = instance;
188 | if (hasOwn(setupState, key)) {
189 | return setupState[key];
190 | }
191 | else if (hasOwn(props, key)) {
192 | return props[key];
193 | }
194 | // 如果 key 存在于公共Api中 则返回对应的公共Api
195 | else if (hasOwn(publicPropertiesMap, key)) {
196 | return publicPropertiesMap[key](instance);
197 | }
198 | }
199 | };
200 |
201 | function initSlots(instance, children) {
202 | const { vnode } = instance;
203 | if (vnode.shapeFlag & 32 /* SLOT_CHILDREN */) {
204 | normalizeObjectSlots(children, instance.slots);
205 | }
206 | }
207 | function normalizeObjectSlots(children, slots) {
208 | for (const key in children) {
209 | const value = children[key];
210 | slots[key] = (props) => normalizeSlotValue(value(props));
211 | }
212 | }
213 | function normalizeSlotValue(value) {
214 | return Array.isArray(value) ? value : [value];
215 | }
216 |
217 | /** 创建组件实例 */
218 | function createComponentInstance(vnode) {
219 | const type = vnode.type;
220 | const instance = {
221 | vnode,
222 | props: EMPTY_OBJ,
223 | emit: () => { },
224 | slots: EMPTY_OBJ,
225 | type,
226 | render: null,
227 | subTree: null,
228 | setupState: EMPTY_OBJ,
229 | proxy: null,
230 | ctx: EMPTY_OBJ
231 | };
232 | instance.emit = emit.bind(null, instance);
233 | instance.ctx = { _: instance };
234 | return instance;
235 | }
236 | /** 初始化组件 */
237 | function setupComponent(instance) {
238 | const { props, children } = instance.vnode;
239 | initProps(instance, props);
240 | initSlots(instance, children);
241 | setupStatefulComponent(instance);
242 | }
243 | /** 初始化 有状态的(非函数式)组件 */
244 | function setupStatefulComponent(instance) {
245 | // 此处 type = component VNode
246 | const { type: { setup }, props, ctx } = instance;
247 | // context
248 | instance.proxy = new Proxy(ctx, publicInstanceProxyHandlers);
249 | if (setup) {
250 | // 执行 setup 前 将currentInstance指向当前组件的instance
251 | currentInstance = instance;
252 | const setupResult = setup(shallowReadonly(props), instance);
253 | // 当前组件的 setup 执行完成后 清空将currentInstance
254 | currentInstance = null;
255 | handleSetupResult(instance, setupResult);
256 | }
257 | }
258 | /** 处理组件内 setup函数 的返回值 */
259 | function handleSetupResult(instance, setupResult) {
260 | // TODO setup 返回的是函数时
261 | // 如果 setup 返回的是对象时 将setupResult赋值给组件实例对象上的setupState
262 | if (isObject(setupResult)) {
263 | instance.setupState = setupResult;
264 | }
265 | finishComponentSetup(instance);
266 | }
267 | /** 组件初始化 完成 */
268 | function finishComponentSetup(instance) {
269 | const { type: component } = instance;
270 | instance.render = component.render;
271 | }
272 | // 组件实例全局变量
273 | let currentInstance = null;
274 | // 在setup里获取当前组件的实例
275 | const getCurrentInstance = () => currentInstance;
276 |
277 | const Fragment = Symbol('Fragment');
278 | const Text = Symbol('Text');
279 | /** 创建虚拟节点 */
280 | const createVNode = (type, props = null, children = null, shapeFlag = initShapFlag(type)) => {
281 | const vnode = {
282 | type,
283 | props,
284 | children,
285 | el: null,
286 | shapeFlag
287 | };
288 | /* 子节点是文本时 标为 TEXT_CHILDREN 否则视为 ARRAY_CHILDREN */
289 | vnode.shapeFlag |= isString(children)
290 | ? 8 /* TEXT_CHILDREN */
291 | : 16 /* ARRAY_CHILDREN */;
292 | if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
293 | if (typeof children === 'object') {
294 | vnode.shapeFlag |= 32 /* SLOT_CHILDREN */;
295 | }
296 | }
297 | return vnode;
298 | };
299 | /** 初始化VNode的默认shapeFlags */
300 | const initShapFlag = (type) => isString(type)
301 | ? 1 /* ELEMENT */
302 | : isFunction(type)
303 | ? 2 /* FUNCTIONAL_COMPONENT */
304 | : 4 /* STATEFUL_COMPONENT */;
305 | const createTextVNode = (text) => {
306 | return createVNode(Text, null, text);
307 | };
308 |
309 | /** render */
310 | function render(vnode, container) {
311 | // 调用 patch 方法
312 | patch(vnode, container);
313 | }
314 | /** 处理各种vnode */
315 | function patch(vnode, container) {
316 | const { type, shapeFlag } = vnode;
317 | switch (type) {
318 | case Fragment:
319 | // 处理Fragment类型的vnode
320 | processFragment(vnode, container);
321 | break;
322 | case Text:
323 | // 处理Fragment类型的vnode
324 | processText(vnode, container);
325 | break;
326 | default:
327 | if (shapeFlag & 1 /* ELEMENT */) {
328 | // 处理Element类型的vnode
329 | processElement(vnode, container);
330 | }
331 | else if (shapeFlag & 4 /* STATEFUL_COMPONENT */) {
332 | // 处理有状态的组件vnode
333 | processComponent(vnode, container);
334 | }
335 | break;
336 | }
337 | }
338 | /** 处理Fragment */
339 | const processText = (vnode, container) => {
340 | const { children } = vnode;
341 | const textNode = (vnode.el = document.createTextNode(children));
342 | container.append(textNode);
343 | };
344 | /** 处理Fragment */
345 | const processFragment = (vnode, container) => mountChildren(vnode.children, container);
346 | /** 处理Element */
347 | const processElement = (vnode, container) => mountElement(vnode, container);
348 | /** 处理组件 */
349 | const processComponent = (vnode, container) => mountComponent(vnode, container);
350 | /** 挂载Element */
351 | const mountElement = (vnode, container) => {
352 | // 根据tagName创建HTML节点
353 | const el = (vnode.el = document.createElement(vnode.type));
354 | const { children, props } = vnode;
355 | // 处理 props 通过循环给DOM节点设置属性
356 | for (const key in props) {
357 | // 挂载事件
358 | if (isOn(key) && isFunction(props[key])) {
359 | el.addEventListener(key.slice(2).toLowerCase(), props[key]);
360 | }
361 | else {
362 | el.setAttribute(key, props[key]);
363 | }
364 | }
365 | // 处理子节点 子节点可能是 TEXT_CHILDREN 或 ARRAY_CHILDREN
366 | if (vnode.shapeFlag & 8 /* TEXT_CHILDREN */) {
367 | // 获取el节点及其后代的文本内容并把子节点替换为文本
368 | el.textContent = children;
369 | }
370 | else if (vnode.shapeFlag & 16 /* ARRAY_CHILDREN */) {
371 | mountChildren(children, el);
372 | }
373 | // 将el节点插入到容器节点的子末位
374 | container.append(el);
375 | };
376 | /** 挂载子节点 */
377 | const mountChildren = (vnodeArray, container) => vnodeArray.forEach((vnode) => patch(vnode, container));
378 | /** 挂载组件 */
379 | const mountComponent = (initialVNode, container) => {
380 | const instance = createComponentInstance(initialVNode);
381 | setupComponent(instance);
382 | setupRenderEffect(instance, container);
383 | };
384 | /** setupRenderEffect */
385 | function setupRenderEffect(instance, container) {
386 | const subTree = instance.render.call(instance.proxy);
387 | patch(subTree, container);
388 | instance.vnode.el = subTree.el;
389 | }
390 |
391 | /** 创建一个提供应用上下文的应用实例 */
392 | function createApp(rootComponent) {
393 | return {
394 | mount(rootContainer) {
395 | // 先把组件转化成虚拟节点 component -> VNode
396 | // 所有逻辑操作都会基于 VNode 做处理
397 | const vnode = createVNode(rootComponent);
398 | render(vnode, rootContainer);
399 | }
400 | };
401 | }
402 |
403 | /** 返回一个`虚拟节点` */
404 | function h(type, props, children) {
405 | return createVNode(type, props, children);
406 | }
407 |
408 | function renderSlots(slots, slotName, props) {
409 | const slot = slots === null || slots === void 0 ? void 0 : slots[slotName];
410 | if (slot) {
411 | if (typeof slot === 'function') {
412 | return createVNode(Fragment, {}, slot(props));
413 | }
414 | }
415 | }
416 |
417 | exports.createApp = createApp;
418 | exports.createTextVNode = createTextVNode;
419 | exports.getCurrentInstance = getCurrentInstance;
420 | exports.h = h;
421 | exports.renderSlots = renderSlots;
422 |
--------------------------------------------------------------------------------
/lib/mini-vue3.esm.js:
--------------------------------------------------------------------------------
1 | /** 对象属性合并 */
2 | const extend = Object.assign;
3 | /** 对象自身属性中是否具有指定的属性 */
4 | const hasOwnProperty = Object.prototype.hasOwnProperty;
5 | const hasOwn = (val, key) => hasOwnProperty.call(val, key);
6 | /** 空对象 */
7 | const EMPTY_OBJ = {};
8 | /** 是on开头的事件 */
9 | const isOn = (key) => /^on[A-Z]/.test(key);
10 | /* -n 字符串驼峰化 */
11 | const camelize = (str) => str.replace(/-(\w)/g, (_, c) => {
12 | return c ? c.toUpperCase() : '';
13 | });
14 | /* 首字母大写 */
15 | const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
16 | /* 变成事件名称 */
17 | const toHandlerKey = (str) => (str ? 'on' + capitalize(str) : '');
18 |
19 | /** 判断是不是对象 */
20 | const isObject = (val) => !!val && typeof val === 'object';
21 | /** 判断是不是String */
22 | const isString = (val) => typeof val === 'string';
23 | /** 判断是不是函数 */
24 | const isFunction = (val) => typeof val === 'function';
25 |
26 | // REVIEW 这一段依赖收集的逻辑关系 需要多复习
27 | const targetMap = new WeakMap();
28 | /**
29 | * 触发依赖执行(从reactive的target->key->dep 然后收集依赖)
30 | */
31 | function trigger(target, key) {
32 | let depsMap = targetMap.get(target);
33 | // 没有收集过依赖(tracked),直接跳过trigger
34 | if (!depsMap)
35 | return;
36 | let dep = depsMap.get(key);
37 | dep && triggerEffects(dep);
38 | }
39 | /**
40 | * 触发依赖执行(直接触发dep中的依赖)
41 | */
42 | function triggerEffects(dep) {
43 | dep.forEach((effect) => {
44 | if (effect.scheduler) {
45 | effect.scheduler();
46 | }
47 | else {
48 | effect.run();
49 | }
50 | });
51 | }
52 |
53 | // 储存reactive和其原始对象对应关系的 全局WeakMap
54 | const reactiveMap = new WeakMap();
55 | const shallowReactiveMap = new WeakMap();
56 | const readonlyMap = new WeakMap();
57 | const shallowReadonlyMap = new WeakMap();
58 | function reactive(raw) {
59 | return createReactiveObject(raw, reactiveHandlers, reactiveMap);
60 | }
61 | /**
62 | * 返回原始对象的只读代理
63 | * @param raw
64 | */
65 | function readonly(raw) {
66 | return createReactiveObject(raw, readonlyHandlers, readonlyMap);
67 | }
68 | function shallowReadonly(raw) {
69 | return createReactiveObject(raw, shallowReadonlyHandlers, shallowReadonlyMap);
70 | }
71 | /**
72 | * 创建反应对象
73 | * @param target 原始对象
74 | * @param baseHandlers Proxy处理函数的对象
75 | */
76 | function createReactiveObject(target, baseHandlers, proxyMap) {
77 | // target 已经具有相应的 Proxy
78 | const existingProxy = proxyMap.get(target);
79 | if (existingProxy) {
80 | return existingProxy;
81 | }
82 | const proxy = new Proxy(target, baseHandlers);
83 | proxyMap.set(target, proxy);
84 | return proxy;
85 | }
86 |
87 | const reactvieGet = createGetter();
88 | const reactvieSet = createSetter();
89 | /** reactive 拦截方法 */
90 | const reactiveHandlers = {
91 | get: reactvieGet,
92 | set: reactvieSet
93 | };
94 | const readonlyGet = createGetter(true);
95 | const readonlySet = readonlySetter;
96 | /** readonly 拦截方法 */
97 | const readonlyHandlers = {
98 | get: readonlyGet,
99 | set: readonlySet
100 | };
101 | const shallowReadonlyGet = createGetter(true, true);
102 | const shallowReadonlySet = readonlySetter;
103 | /** shallowReadonly 拦截方法 */
104 | const shallowReadonlyHandlers = {
105 | get: shallowReadonlyGet,
106 | set: shallowReadonlySet
107 | };
108 | /**
109 | * 创建proxy对象的get方法
110 | * @param isReadonly
111 | */
112 | function createGetter(isReadonly = false, shallow = false) {
113 | return (target, key, receiver) => {
114 | // isReactive和isReadonly 检测 不是readonly的就是reactive
115 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
116 | return !isReadonly;
117 | }
118 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
119 | return isReadonly;
120 | }
121 | else if (key === "__v_raw" /* RAW */ &&
122 | receiver ===
123 | (isReadonly
124 | ? shallow
125 | ? shallowReadonlyMap
126 | : readonlyMap
127 | : shallow
128 | ? shallowReactiveMap
129 | : reactiveMap).get(target)) {
130 | return target;
131 | }
132 | const res = Reflect.get(target, key, receiver);
133 | // 不执行嵌套对象的深度readonly转换
134 | if (shallow) {
135 | return res;
136 | }
137 | // 实现 reactive和readonly 的嵌套对象转换
138 | if (isObject(res)) {
139 | return isReadonly ? readonly(res) : reactive(res);
140 | }
141 | return res;
142 | };
143 | }
144 | /**
145 | * 创建非readoly Proxy对象的 set 方法
146 | */
147 | function createSetter() {
148 | // reactive创建的代理对象 触发 set 操作时 触发依赖执行
149 | return (target, key, value, receiver) => {
150 | const res = Reflect.set(target, key, value, receiver);
151 | // 触发依赖执行
152 | trigger(target, key);
153 | return res;
154 | };
155 | }
156 | /**
157 | * readoly Proxy对象的 set 方法
158 | */
159 | function readonlySetter(target, key) {
160 | // readonly创建的代理对象, 触发 set 操作时控制台进行警告提示
161 | console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
162 | return true;
163 | }
164 |
165 | function emit(instance, eventName, ...args) {
166 | const { props } = instance;
167 | const handlerName = toHandlerKey(camelize(eventName));
168 | const handler = props[handlerName];
169 | handler && handler(...args);
170 | }
171 |
172 | function initProps(instance, rawProps) {
173 | instance.props = rawProps || {};
174 | }
175 |
176 | const publicPropertiesMap = extend(Object.create(null), {
177 | $el: (i) => i.vnode.el,
178 | $slots: (i) => i.slots
179 | });
180 | /** publicInstanceProxyHandlers */
181 | const publicInstanceProxyHandlers = {
182 | get({ _: instance }, key) {
183 | const { setupState, props } = instance;
184 | if (hasOwn(setupState, key)) {
185 | return setupState[key];
186 | }
187 | else if (hasOwn(props, key)) {
188 | return props[key];
189 | }
190 | // 如果 key 存在于公共Api中 则返回对应的公共Api
191 | else if (hasOwn(publicPropertiesMap, key)) {
192 | return publicPropertiesMap[key](instance);
193 | }
194 | }
195 | };
196 |
197 | function initSlots(instance, children) {
198 | const { vnode } = instance;
199 | if (vnode.shapeFlag & 32 /* SLOT_CHILDREN */) {
200 | normalizeObjectSlots(children, instance.slots);
201 | }
202 | }
203 | function normalizeObjectSlots(children, slots) {
204 | for (const key in children) {
205 | const value = children[key];
206 | slots[key] = (props) => normalizeSlotValue(value(props));
207 | }
208 | }
209 | function normalizeSlotValue(value) {
210 | return Array.isArray(value) ? value : [value];
211 | }
212 |
213 | /** 创建组件实例 */
214 | function createComponentInstance(vnode) {
215 | const type = vnode.type;
216 | const instance = {
217 | vnode,
218 | props: EMPTY_OBJ,
219 | emit: () => { },
220 | slots: EMPTY_OBJ,
221 | type,
222 | render: null,
223 | subTree: null,
224 | setupState: EMPTY_OBJ,
225 | proxy: null,
226 | ctx: EMPTY_OBJ
227 | };
228 | instance.emit = emit.bind(null, instance);
229 | instance.ctx = { _: instance };
230 | return instance;
231 | }
232 | /** 初始化组件 */
233 | function setupComponent(instance) {
234 | const { props, children } = instance.vnode;
235 | initProps(instance, props);
236 | initSlots(instance, children);
237 | setupStatefulComponent(instance);
238 | }
239 | /** 初始化 有状态的(非函数式)组件 */
240 | function setupStatefulComponent(instance) {
241 | // 此处 type = component VNode
242 | const { type: { setup }, props, ctx } = instance;
243 | // context
244 | instance.proxy = new Proxy(ctx, publicInstanceProxyHandlers);
245 | if (setup) {
246 | // 执行 setup 前 将currentInstance指向当前组件的instance
247 | currentInstance = instance;
248 | const setupResult = setup(shallowReadonly(props), instance);
249 | // 当前组件的 setup 执行完成后 清空将currentInstance
250 | currentInstance = null;
251 | handleSetupResult(instance, setupResult);
252 | }
253 | }
254 | /** 处理组件内 setup函数 的返回值 */
255 | function handleSetupResult(instance, setupResult) {
256 | // TODO setup 返回的是函数时
257 | // 如果 setup 返回的是对象时 将setupResult赋值给组件实例对象上的setupState
258 | if (isObject(setupResult)) {
259 | instance.setupState = setupResult;
260 | }
261 | finishComponentSetup(instance);
262 | }
263 | /** 组件初始化 完成 */
264 | function finishComponentSetup(instance) {
265 | const { type: component } = instance;
266 | instance.render = component.render;
267 | }
268 | // 组件实例全局变量
269 | let currentInstance = null;
270 | // 在setup里获取当前组件的实例
271 | const getCurrentInstance = () => currentInstance;
272 |
273 | const Fragment = Symbol('Fragment');
274 | const Text = Symbol('Text');
275 | /** 创建虚拟节点 */
276 | const createVNode = (type, props = null, children = null, shapeFlag = initShapFlag(type)) => {
277 | const vnode = {
278 | type,
279 | props,
280 | children,
281 | el: null,
282 | shapeFlag
283 | };
284 | /* 子节点是文本时 标为 TEXT_CHILDREN 否则视为 ARRAY_CHILDREN */
285 | vnode.shapeFlag |= isString(children)
286 | ? 8 /* TEXT_CHILDREN */
287 | : 16 /* ARRAY_CHILDREN */;
288 | if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
289 | if (typeof children === 'object') {
290 | vnode.shapeFlag |= 32 /* SLOT_CHILDREN */;
291 | }
292 | }
293 | return vnode;
294 | };
295 | /** 初始化VNode的默认shapeFlags */
296 | const initShapFlag = (type) => isString(type)
297 | ? 1 /* ELEMENT */
298 | : isFunction(type)
299 | ? 2 /* FUNCTIONAL_COMPONENT */
300 | : 4 /* STATEFUL_COMPONENT */;
301 | const createTextVNode = (text) => {
302 | return createVNode(Text, null, text);
303 | };
304 |
305 | /** render */
306 | function render(vnode, container) {
307 | // 调用 patch 方法
308 | patch(vnode, container);
309 | }
310 | /** 处理各种vnode */
311 | function patch(vnode, container) {
312 | const { type, shapeFlag } = vnode;
313 | switch (type) {
314 | case Fragment:
315 | // 处理Fragment类型的vnode
316 | processFragment(vnode, container);
317 | break;
318 | case Text:
319 | // 处理Fragment类型的vnode
320 | processText(vnode, container);
321 | break;
322 | default:
323 | if (shapeFlag & 1 /* ELEMENT */) {
324 | // 处理Element类型的vnode
325 | processElement(vnode, container);
326 | }
327 | else if (shapeFlag & 4 /* STATEFUL_COMPONENT */) {
328 | // 处理有状态的组件vnode
329 | processComponent(vnode, container);
330 | }
331 | break;
332 | }
333 | }
334 | /** 处理Fragment */
335 | const processText = (vnode, container) => {
336 | const { children } = vnode;
337 | const textNode = (vnode.el = document.createTextNode(children));
338 | container.append(textNode);
339 | };
340 | /** 处理Fragment */
341 | const processFragment = (vnode, container) => mountChildren(vnode.children, container);
342 | /** 处理Element */
343 | const processElement = (vnode, container) => mountElement(vnode, container);
344 | /** 处理组件 */
345 | const processComponent = (vnode, container) => mountComponent(vnode, container);
346 | /** 挂载Element */
347 | const mountElement = (vnode, container) => {
348 | // 根据tagName创建HTML节点
349 | const el = (vnode.el = document.createElement(vnode.type));
350 | const { children, props } = vnode;
351 | // 处理 props 通过循环给DOM节点设置属性
352 | for (const key in props) {
353 | // 挂载事件
354 | if (isOn(key) && isFunction(props[key])) {
355 | el.addEventListener(key.slice(2).toLowerCase(), props[key]);
356 | }
357 | else {
358 | el.setAttribute(key, props[key]);
359 | }
360 | }
361 | // 处理子节点 子节点可能是 TEXT_CHILDREN 或 ARRAY_CHILDREN
362 | if (vnode.shapeFlag & 8 /* TEXT_CHILDREN */) {
363 | // 获取el节点及其后代的文本内容并把子节点替换为文本
364 | el.textContent = children;
365 | }
366 | else if (vnode.shapeFlag & 16 /* ARRAY_CHILDREN */) {
367 | mountChildren(children, el);
368 | }
369 | // 将el节点插入到容器节点的子末位
370 | container.append(el);
371 | };
372 | /** 挂载子节点 */
373 | const mountChildren = (vnodeArray, container) => vnodeArray.forEach((vnode) => patch(vnode, container));
374 | /** 挂载组件 */
375 | const mountComponent = (initialVNode, container) => {
376 | const instance = createComponentInstance(initialVNode);
377 | setupComponent(instance);
378 | setupRenderEffect(instance, container);
379 | };
380 | /** setupRenderEffect */
381 | function setupRenderEffect(instance, container) {
382 | const subTree = instance.render.call(instance.proxy);
383 | patch(subTree, container);
384 | instance.vnode.el = subTree.el;
385 | }
386 |
387 | /** 创建一个提供应用上下文的应用实例 */
388 | function createApp(rootComponent) {
389 | return {
390 | mount(rootContainer) {
391 | // 先把组件转化成虚拟节点 component -> VNode
392 | // 所有逻辑操作都会基于 VNode 做处理
393 | const vnode = createVNode(rootComponent);
394 | render(vnode, rootContainer);
395 | }
396 | };
397 | }
398 |
399 | /** 返回一个`虚拟节点` */
400 | function h(type, props, children) {
401 | return createVNode(type, props, children);
402 | }
403 |
404 | function renderSlots(slots, slotName, props) {
405 | const slot = slots === null || slots === void 0 ? void 0 : slots[slotName];
406 | if (slot) {
407 | if (typeof slot === 'function') {
408 | return createVNode(Fragment, {}, slot(props));
409 | }
410 | }
411 | }
412 |
413 | export { createApp, createTextVNode, getCurrentInstance, h, renderSlots };
414 |
--------------------------------------------------------------------------------
/lib/types/mini-vue3.d.ts:
--------------------------------------------------------------------------------
1 | /** 创建一个提供应用上下文的应用实例 */
2 | declare function createApp(rootComponent: any): {
3 | mount(rootContainer: any): void;
4 | };
5 |
6 | declare const enum ShapeFlags {
7 | ELEMENT = 1,
8 | FUNCTIONAL_COMPONENT = 2,
9 | STATEFUL_COMPONENT = 4,
10 | TEXT_CHILDREN = 8,
11 | ARRAY_CHILDREN = 16,
12 | SLOT_CHILDREN = 32,
13 | COMPONENT = 6
14 | }
15 |
16 | declare type Component = {
17 | setup: (props: object) => object;
18 | render: () => (type: any, props?: any, children?: any) => VNode;
19 | };
20 | declare type Data = Record;
21 | declare type Slot = (...args: any[]) => VNode[];
22 | declare type InternalSlots = {
23 | [name: string]: Slot | undefined;
24 | };
25 | declare type Slots = Readonly;
26 | interface ComponentInternalInstance {
27 | vnode: VNode;
28 | props: Data;
29 | emit: Function;
30 | slots: InternalSlots;
31 | type: any;
32 | /**
33 | * 此组件vdom树的 根vnode(虚拟节点)
34 | */
35 | subTree: VNode;
36 | /**
37 | * proxy
38 | */
39 | proxy: any;
40 | /**
41 | * ctx
42 | */
43 | ctx: Data;
44 | }
45 | declare const getCurrentInstance: () => ComponentInternalInstance;
46 |
47 | declare type VNodeTypes = string | VNode | Component | typeof Fragment | typeof Text;
48 | interface VNode {
49 | type: VNodeTypes;
50 | props: Record | null;
51 | children: VNodeNormalizedChildren;
52 | el: Node | null;
53 | shapeFlag: ShapeFlags;
54 | }
55 | declare type VNodeChildAtom = VNode | string | number | boolean | null | undefined | void;
56 | declare type VNodeArrayChildren = Array;
57 | declare type VNodeNormalizedChildren = string | VNodeArrayChildren | null;
58 | declare const Fragment: unique symbol;
59 | declare const Text: unique symbol;
60 | declare const createTextVNode: (text: string) => VNode;
61 |
62 | /** 返回一个`虚拟节点` */
63 | declare function h(type: any, props?: any, children?: any): VNode;
64 |
65 | declare function renderSlots(slots: Slots, slotName: string, props: any): VNode | undefined;
66 |
67 | export { createApp, createTextVNode, getCurrentInstance, h, renderSlots };
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-vue",
3 | "version": "1.0.0",
4 | "types": "lib/types/mini-vue3.d.ts",
5 | "main": "lib/mini-vue3.cjs.js",
6 | "module": "lib/mini-vue3.esm.js",
7 | "license": "MIT",
8 | "scripts": {
9 | "build": "rollup -c rollup.config.js",
10 | "build-watch": "rollup -c rollup.config.js --watch",
11 | "test": "jest --runInBand",
12 | "test-coverage": "jest --runInBand --coverage",
13 | "test-watchAll": "jest --runInBand --watchAll"
14 | },
15 | "devDependencies": {
16 | "@babel/core": "^7.15.5",
17 | "@babel/preset-env": "^7.15.4",
18 | "@babel/preset-typescript": "^7.15.0",
19 | "@rollup/plugin-typescript": "^8.2.5",
20 | "@types/jest": "^27.0.1",
21 | "babel-jest": "^27.1.0",
22 | "jest": "^27.1.0",
23 | "rollup": "^2.58.0",
24 | "rollup-plugin-dts": "^4.0.0",
25 | "ts-node": "^10.2.1",
26 | "tslib": "^2.3.1",
27 | "typescript": "^4.4.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json'
2 | import typescript from '@rollup/plugin-typescript'
3 | import dts from 'rollup-plugin-dts'
4 | export default [
5 | {
6 | input: './src/index.ts',
7 | output: [
8 | // cjs(commonjs)
9 | {
10 | format: 'cjs',
11 | file: pkg.main
12 | },
13 | // esm
14 | {
15 | format: 'es',
16 | file: pkg.module
17 | }
18 | ],
19 |
20 | plugins: [typescript()]
21 | },
22 | {
23 | input: './src/index.ts',
24 | output: [{ file: pkg.types, format: 'es' }],
25 | plugins: [dts()]
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /** mini-vue3 的出口文件 */
2 | export * from './runtime-core/index'
3 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from '../src/computed'
2 | import { effect } from '../src/effect'
3 | import { isReadonly, reactive } from '../src/reactive'
4 | import { ref } from '../src/ref'
5 |
6 | describe('reactivity/computed', () => {
7 | it('should return updated value', () => {
8 | const reactiveObj = reactive<{ foo?: number }>({})
9 | const computedValue = computed(() => reactiveObj.foo)
10 | console.warn = jest.fn()
11 |
12 | // 默认情况下 computed 是只读的
13 | expect(isReadonly(computedValue)).toBe(true)
14 | // @ts-ignore-nextline 对只读的computed赋值不会成功,并且会发出警告
15 | computedValue.value = 1
16 | expect(console.warn).toBeCalledTimes(1)
17 |
18 | expect(computedValue.value).toBe(undefined)
19 | reactiveObj.foo = 1
20 | expect(computedValue.value).toBe(1)
21 | })
22 |
23 | it('should compute lazily', () => {
24 | const reactiveObj = reactive<{ foo?: number }>({})
25 | const getter = jest.fn(() => reactiveObj.foo)
26 | const computedValue = computed(getter)
27 |
28 | // 懒执行
29 | expect(getter).not.toHaveBeenCalled()
30 |
31 | expect(computedValue.value).toBe(undefined)
32 | expect(getter).toHaveBeenCalledTimes(1)
33 |
34 | // computed 的依赖不改变时, 再次获取值时使用缓存的computed值
35 | expect(computedValue.value).toBe(undefined)
36 | expect(getter).toHaveBeenCalledTimes(1)
37 |
38 | // computed的依赖改变后,在被再次读取值之前不重新计算
39 | reactiveObj.foo = 0
40 | reactiveObj.foo = 1
41 | expect(getter).toHaveBeenCalledTimes(1)
42 |
43 | // computed的依赖改变后,再次读取值时重新计算
44 | expect(computedValue.value).toBe(1)
45 | expect(getter).toHaveBeenCalledTimes(2)
46 |
47 | // computed 的依赖不改变时, 再次获取值时使用缓存的computed值,不会重新计算
48 | computedValue.value
49 | expect(getter).toHaveBeenCalledTimes(2)
50 | })
51 |
52 | it('should trigger effect', () => {
53 | const reactiveObj = reactive<{ foo?: number }>({})
54 | const computedValue = computed(() => reactiveObj.foo)
55 | let dummy
56 | effect(() => {
57 | dummy = computedValue.value
58 | })
59 | expect(dummy).toBe(undefined)
60 | reactiveObj.foo = 1
61 | expect(dummy).toBe(1)
62 | })
63 |
64 | it('should support setter', () => {
65 | const n = ref(1)
66 | const plusOne = computed({
67 | get: () => n.value + 1,
68 | set: (val) => {
69 | n.value = val - 1
70 | }
71 | })
72 |
73 | expect(isReadonly(plusOne)).toBe(false)
74 | expect(plusOne.value).toBe(2)
75 | n.value++
76 | expect(plusOne.value).toBe(3)
77 |
78 | plusOne.value = 0
79 | expect(n.value).toBe(-1)
80 | })
81 | it('should not support setter when the configuration object only has getter', () => {
82 | const n = ref(1)
83 | // @ts-ignore-nextline
84 | const plusOne = computed({ get: () => n.value + 1 })
85 | console.warn = jest.fn()
86 |
87 | expect(isReadonly(plusOne)).toBe(true)
88 | expect(plusOne.value).toBe(2)
89 | n.value++
90 | expect(plusOne.value).toBe(3)
91 |
92 | // @ts-ignore-nextline
93 | plusOne.value = 0
94 | expect(console.warn).toHaveBeenCalledTimes(1)
95 | expect(n.value).toBe(2)
96 | })
97 | })
98 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from '../src/reactive'
2 | import { effect, stop } from '../src/effect'
3 | describe('effect', () => {
4 | it('happy path', () => {
5 | const user = reactive({
6 | age: 10
7 | })
8 |
9 | let nextAge
10 | effect(() => {
11 | nextAge = user.age + 1
12 | })
13 | expect(nextAge).toBe(11)
14 |
15 | // update
16 | user.age++
17 | expect(nextAge).toBe(12)
18 | })
19 |
20 | // 调用 effect(fn) -> 返回一个 runner函数
21 | // -> 手动调用 runner()时 -> 会调用effect(fn)内的 fn()
22 | // -> 返回fn()的返回值
23 | it('should return runner when call effect', () => {
24 | let foo = 0
25 | const runner = effect(() => {
26 | foo++
27 | return foo
28 | })
29 | // effect(fn)函数会默认执行一次fn()
30 | expect(foo).toBe(1)
31 | // 调用effect(fn)返回的runner()函数会再次执行一次fn()
32 | runner()
33 | expect(foo).toBe(2)
34 | // 调用effect(fn)返回的runner()函数会得到再次执行一次fn()后fn()的返回值
35 | expect(runner()).toBe(3)
36 | })
37 |
38 | it('scheduler', () => {
39 | let dummy
40 | let run: any
41 | const scheduler = jest.fn(() => {
42 | run = runner
43 | })
44 | const obj = reactive({ foo: 1 })
45 | const runner = effect(
46 | () => {
47 | dummy = obj.foo
48 | },
49 | { scheduler }
50 | )
51 | // 调用effect时默认会调用一次fn 不调用scheduler函数
52 | expect(scheduler).not.toHaveBeenCalled()
53 | expect(dummy).toBe(1)
54 |
55 | // 此后修改effect的依赖的值时
56 | // 默认情况下有scheduler时会触发scheduler函数并且不调用fn()
57 | // 无scheduler时,会触发调用fn()
58 | obj.foo++
59 | expect(scheduler).toHaveBeenCalledTimes(1)
60 | expect(dummy).toBe(1)
61 | // 手动运行runner函数时 只调用fn() 不会调用 scheduler函数
62 | run() // 通过scheduler()函数把effect()返回的runner函数赋值给 run -> run===runner
63 | expect(dummy).toBe(2)
64 | expect(scheduler).toHaveBeenCalledTimes(1)
65 | })
66 |
67 | it('stop', () => {
68 | let dummy
69 | const obj = reactive({ prop: 1 })
70 | const runner = effect(() => {
71 | dummy = obj.prop
72 | })
73 | obj.prop = 2
74 | expect(dummy).toBe(2)
75 |
76 | stop(runner)
77 | obj.prop++
78 | expect(dummy).toBe(2)
79 | // 不会重复stop 和 清空依赖
80 | stop(runner)
81 |
82 | // stop掉的effect 应该仍然可以手动调用 runner
83 | runner()
84 | expect(dummy).toBe(3)
85 | })
86 |
87 | it('onStop', () => {
88 | const onStop = jest.fn()
89 | const runner = effect(() => {}, {
90 | onStop
91 | })
92 |
93 | // 对effect()返回的runner执行stop()操作时
94 | // 会执行一次,生成runner函数时传入的 onStop 函数
95 | stop(runner)
96 | expect(onStop).toBeCalledTimes(1)
97 | })
98 | })
99 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | reactive,
3 | isReactive,
4 | isProxy,
5 | toRaw,
6 | readonly,
7 | shallowReadonly,
8 | shallowReactive,
9 | toReactive,
10 | isReadonly,
11 | toReadonly
12 | } from '../src/reactive'
13 | describe('reactive', () => {
14 | it('happy path', () => {
15 | const original = { foo: 1 }
16 | const observed = reactive(original)
17 | expect(observed).not.toBe(original) // 包装后的对象与源对象不同
18 | expect(observed.foo).toBe(1) // 可以获取到被包装对象内的值 与源对象对应的值一致
19 | expect(isProxy(original)).toBe(false) // 验证 original 不是是一个proxy对象
20 | expect(isReactive(original)).toBe(false) // 验证 original 不是是一个 reactive
21 | expect(isProxy(observed)).toBe(true) // 验证 observed 是一个proxy对象
22 | expect(isReactive(observed)).toBe(true) // 验证 observed 是一个 reactive
23 | })
24 |
25 | it('nested reactives', () => {
26 | const original = {
27 | text: 'some text',
28 | nested: {
29 | foo: 1
30 | },
31 | array: [{ bar: 2 }]
32 | }
33 | const observed = reactive(original)
34 | // 此时没有track过依赖 没有触发trigger
35 | observed.text = 'text'
36 | // 只有嵌套的对象被转换为 reactive
37 | expect(isReactive(observed.text)).toBe(false)
38 | expect(isReactive(observed.nested)).toBe(true)
39 | expect(isReactive(observed.array)).toBe(true)
40 | expect(isReactive(observed.array[0])).toBe(true)
41 | })
42 |
43 | it('toRaw', () => {
44 | const obj = { foo: { bar: 'bar' }, sum: 1 }
45 | const reactiveObj = reactive(obj)
46 | const shallowReactiveObj = shallowReactive(obj)
47 | const readonlyObj = readonly(obj)
48 | const shallowReadonlyObj = shallowReadonly(obj)
49 | expect(toRaw(obj)).toBe(obj)
50 | expect(toRaw(reactiveObj)).toBe(obj)
51 | expect(toRaw(shallowReactiveObj)).toBe(obj)
52 | expect(toRaw(readonlyObj)).toBe(obj)
53 | expect(toRaw(shallowReadonlyObj)).toBe(obj)
54 | })
55 |
56 | it('toReactive', () => {
57 | const obj = { num: 5 }
58 | const str = 'text'
59 | // 非对象数据返回其本身
60 | expect(toReactive(str)).toBe(str)
61 | expect(isReactive(toReactive(str))).toBe(false)
62 | // 返回对象的响应式副本
63 | expect(toReactive(obj)).not.toBe(obj)
64 | expect(isReactive(toReactive(obj))).toBe(true)
65 | expect(isReadonly(toReactive(obj))).toBe(false)
66 | })
67 |
68 | it('toReadonly', () => {
69 | const obj = { num: 5 }
70 | const str = 'text'
71 | // 非对象数据返回其本身
72 | expect(toReadonly(str)).toBe(str)
73 | expect(isReadonly(toReadonly(str))).toBe(false)
74 | // 返回对象的响应式副本
75 | expect(toReadonly(obj)).not.toBe(obj)
76 | expect(isReadonly(toReadonly(obj))).toBe(true)
77 | expect(isReactive(toReadonly(obj))).toBe(false)
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, readonly } from '../src/reactive'
2 |
3 | describe('readonly', () => {
4 | it('happy path', () => {
5 | const original = { foo: 1, bar: { baz: 2 } }
6 | const wrapped = readonly(original)
7 |
8 | // readonly 代理后生成一个与源对象内容相同新的proxy对象
9 | expect(wrapped).not.toBe(original)
10 | expect(isProxy(wrapped)).toBe(true)
11 | expect(isReadonly(wrapped)).toBe(true)
12 | expect(isProxy(original)).toBe(false)
13 | expect(isReadonly(original)).toBe(false)
14 | expect(wrapped.foo).toBe(1)
15 | expect(wrapped.bar.baz).toBe(2)
16 | })
17 |
18 | it('should warn when called set', () => {
19 | // readonly() 创建的proxy对象不可以被set
20 | console.warn = jest.fn()
21 | const user = readonly({
22 | age: 10
23 | })
24 | user.age = 11
25 | expect(console.warn).toBeCalledTimes(1)
26 | user.age++
27 | expect(console.warn).toBeCalledTimes(2)
28 | })
29 |
30 | it('nested readonlys', () => {
31 | const original = {
32 | text: 'some text',
33 | nested: {
34 | foo: 1
35 | },
36 | array: [{ bar: 2 }]
37 | }
38 | const wrapped = readonly(original)
39 | // 只有嵌套的对象被转换为 readonly
40 | expect(isReadonly(wrapped.text)).toBe(false)
41 | expect(isReadonly(wrapped.nested)).toBe(true)
42 | expect(isReadonly(wrapped.array)).toBe(true)
43 | expect(isReadonly(wrapped.array[0])).toBe(true)
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../src/effect'
2 | import { isReactive, isReadonly, reactive } from '../src/reactive'
3 | import { isRef, proxyRefs, ref, shallowRef, toRef, toRefs, unref } from '../src/ref'
4 | describe('reactivity/ref', () => {
5 | it('ref happy path', () => {
6 | const r = ref(1)
7 | expect(isReadonly(r)).toBe(false)
8 | expect(r.value).toBe(1)
9 | r.value = 2
10 | expect(r.value).toBe(2)
11 | })
12 | // ref() 没有初始值时也能正常工作
13 | it('should work without initial value', () => {
14 | const a = ref()
15 | let dummy
16 | effect(() => {
17 | dummy = a.value
18 | })
19 | expect(dummy).toBe(undefined)
20 | a.value = 2
21 | expect(dummy).toBe(2)
22 | })
23 | // ref()创建的变量应该是响应式的
24 | it('should be reactive', () => {
25 | const a = ref(1)
26 | let dummy
27 | let calls = 0
28 | effect(() => {
29 | calls++
30 | dummy = a.value
31 | })
32 | expect(calls).toBe(1)
33 | expect(dummy).toBe(1)
34 | a.value = 2
35 | expect(calls).toBe(2)
36 | expect(dummy).toBe(2)
37 | // 相同的值不触发 trigger()
38 | a.value = 2
39 | expect(calls).toBe(2)
40 | expect(dummy).toBe(2)
41 | })
42 | // 嵌套属性应该是响应式的
43 | it('should make nested properties reactive', () => {
44 | const a = ref({
45 | count: 1
46 | })
47 | // 嵌套属性应该是被reactive代理的响应式对象
48 | expect(isReactive(a.value)).toBe(true)
49 | let dummy
50 | effect(() => {
51 | dummy = a.value.count
52 | })
53 | expect(dummy).toBe(1)
54 | a.value.count = 2
55 | expect(dummy).toBe(2)
56 | })
57 |
58 | it('isRef', () => {
59 | expect(isRef(ref(1))).toBe(true)
60 | // expect(isRef(computed(() => 1))).toBe(true)
61 |
62 | expect(isRef(0)).toBe(false)
63 | expect(isRef(1)).toBe(false)
64 | // 看起来像 ref 的对象不一定是 ref
65 | expect(isRef({ value: 0 })).toBe(false)
66 | })
67 |
68 | it('unref', () => {
69 | expect(unref(1)).toBe(1)
70 | expect(unref(ref(1))).toBe(1)
71 | })
72 |
73 | it('proxyRefs', () => {
74 | const user = {
75 | age: ref(10),
76 | name: 'xiaohong'
77 | }
78 |
79 | const proxyUser = proxyRefs(user)
80 | expect(user.age.value).toBe(10)
81 | expect(proxyUser.age).toBe(10)
82 | expect(proxyUser.name).toBe('xiaohong')
83 |
84 | proxyUser.age = 20
85 | expect(proxyUser.age).toBe(20)
86 | expect(user.age.value).toBe(20)
87 |
88 | // @ts-ignore
89 | proxyUser.age = ref(10)
90 | expect(proxyUser.age).toBe(10)
91 | expect(user.age.value).toBe(10)
92 | })
93 |
94 | it('shallowRef', () => {
95 | const sref = shallowRef({ a: 1 })
96 | expect(isReactive(sref.value)).toBe(false)
97 |
98 | let dummy
99 | effect(() => {
100 | dummy = sref.value.a
101 | })
102 | expect(dummy).toBe(1)
103 |
104 | sref.value = { a: 2 }
105 | expect(isReactive(sref.value)).toBe(false)
106 | expect(dummy).toBe(2)
107 | })
108 | it('should return the ref when the shallowRef wraps a ref', () => {
109 | const r = ref({ num: 1 })
110 | const sr = shallowRef(r)
111 | expect(sr).toBe(r)
112 | expect(isReactive(sr.value)).toBe(true)
113 | })
114 |
115 | it('shallowRef force trigger', () => {
116 | const sref = shallowRef({ a: 1 })
117 | let dummy
118 | effect(() => {
119 | dummy = sref.value.a
120 | })
121 | expect(dummy).toBe(1)
122 |
123 | sref.value.a = 2
124 | expect(dummy).toBe(1) // should not trigger yet
125 |
126 | // force trigger
127 | // triggerRef(sref)
128 | // expect(dummy).toBe(2)
129 | })
130 |
131 | it('toRef', () => {
132 | const a = reactive({
133 | x: 1
134 | })
135 | const x = toRef(a, 'x')
136 | expect(isRef(x)).toBe(true)
137 | expect(x.value).toBe(1)
138 |
139 | // source -> proxy
140 | a.x = 2
141 | expect(x.value).toBe(2)
142 |
143 | // proxy -> source
144 | x.value = 3
145 | expect(a.x).toBe(3)
146 |
147 | // reactivity
148 | let dummyX
149 | effect(() => {
150 | dummyX = x.value
151 | })
152 | expect(dummyX).toBe(x.value)
153 |
154 | // mutating source should trigger effect using the proxy refs
155 | a.x = 4
156 | expect(dummyX).toBe(4)
157 |
158 | // should keep ref
159 | const r = { x: ref(1) }
160 | const rr = toRef(r, 'x')
161 | expect(rr).toBe(r.x)
162 | rr.value = 2
163 | expect(r.x.value).toBe(2)
164 | r.x.value = 3
165 | expect(rr.value).toBe(3)
166 | })
167 |
168 | it('toRefs', () => {
169 | const a = reactive({
170 | x: 1,
171 | y: 2
172 | })
173 |
174 | const { x, y } = toRefs(a)
175 |
176 | expect(isRef(x)).toBe(true)
177 | expect(isRef(y)).toBe(true)
178 | expect(x.value).toBe(1)
179 | expect(y.value).toBe(2)
180 |
181 | // source -> proxy
182 | a.x = 2
183 | a.y = 3
184 | expect(x.value).toBe(2)
185 | expect(y.value).toBe(3)
186 |
187 | // proxy -> source
188 | x.value = 3
189 | y.value = 4
190 | expect(a.x).toBe(3)
191 | expect(a.y).toBe(4)
192 |
193 | // reactivity
194 | let dummyX, dummyY
195 | effect(() => {
196 | dummyX = x.value
197 | dummyY = y.value
198 | })
199 | expect(dummyX).toBe(x.value)
200 | expect(dummyY).toBe(y.value)
201 |
202 | // mutating source should trigger effect using the proxy refs
203 | a.x = 4
204 | a.y = 5
205 | expect(dummyX).toBe(4)
206 | expect(dummyY).toBe(5)
207 | })
208 |
209 | it('toRefs should warn on plain object', () => {
210 | console.warn = jest.fn()
211 | toRefs({})
212 | expect(console.warn).toHaveBeenCalledTimes(1)
213 | })
214 |
215 | it('toRefs should warn on plain array', () => {
216 | console.warn = jest.fn()
217 | toRefs([])
218 | expect(console.warn).toHaveBeenCalledTimes(1)
219 | })
220 |
221 | it('toRefs reactive array', () => {
222 | const arr = reactive(['a', 'b', 'c'])
223 | const refs = toRefs(arr)
224 |
225 | expect(Array.isArray(refs)).toBe(true)
226 |
227 | refs[0].value = '1'
228 | expect(arr[0]).toBe('1')
229 |
230 | arr[1] = '2'
231 | expect(refs[1].value).toBe('2')
232 | })
233 | })
234 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/shallowReactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, shallowReactive } from '../src/reactive'
2 |
3 | describe('shallowReactive', () => {
4 | it('should only set the non-reactive attribute of the root node as reactive', () => {
5 | const props = shallowReactive({ n: { foo: 1 } })
6 | expect(isReactive(props)).toBe(true)
7 | expect(isReactive(props.n)).toBe(false)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/src/reactivity/__tests__/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from '../src/reactive'
2 |
3 | describe('shallowReadonly', () => {
4 | it('should only set the non-reactive attribute of the root node as readonly', () => {
5 | const props = shallowReadonly({ n: { foo: 1 } })
6 | expect(isReadonly(props)).toBe(true)
7 | expect(isReadonly(props.n)).toBe(false)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export * from './src/ref'
2 | export * from './src/reactive'
3 | export * from './src/effect'
4 | export * from './src/computed'
5 |
--------------------------------------------------------------------------------
/src/reactivity/src/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '../../shared/index'
2 | import { track, trigger } from './effect'
3 | import {
4 | reactive,
5 | reactiveMap,
6 | readonly,
7 | readonlyMap,
8 | shallowReactiveMap,
9 | shallowReadonlyMap,
10 | Target
11 | } from './reactive'
12 | import { ReactivityFlags } from './enumeration'
13 | import { isRef, unref } from './ref'
14 |
15 | type ReactiveGetter = (
16 | target: T & Target,
17 | key: PropertyKey,
18 | receiver?: any
19 | ) => T
20 | type ReactiveSetter = (
21 | target: T & Target,
22 | key: PropertyKey,
23 | value: any,
24 | receiver?: any
25 | ) => boolean
26 |
27 | const reactvieGet = createGetter()
28 | const reactvieSet = createSetter()
29 | /** reactive 拦截方法 */
30 | export const reactiveHandlers: ProxyHandler