├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── package.json
└── src
├── effect.js
├── handlers.js
└── reactive.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 JrainLau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tiny-reactive
2 |
3 | 精简版的 Vue 3.0 响应式系统。
4 |
5 | ## 运行
6 | Clone 到本地以后,执行 `npm i` 安装 `http-server` 依赖,然后执行 `npm run dev` 即可。
7 |
8 | 注意,由于直接在 html 中使用了 es 模块,浏览器默认会缓存。所以在调试的时候需要在 network 里设置 disable cache,否则修改的代码不会生效。
9 |
10 | ## Lisence
11 | MIT
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tiny Reactive
8 |
9 |
10 |
31 |
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tiny-reactive",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "http-server"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/jrainlau/tiny-reactive.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/jrainlau/tiny-reactive/issues"
18 | },
19 | "homepage": "https://github.com/jrainlau/tiny-reactive#readme",
20 | "devDependencies": {
21 | "http-server": "^0.11.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/effect.js:
--------------------------------------------------------------------------------
1 | export const targetMap = new WeakMap()
2 | export const effectStack = []
3 |
4 | export function track (target, operationType, key) {
5 | const effect = effectStack[effectStack.length - 1]
6 | if (effect) {
7 | let depsMap = targetMap.get(target)
8 | if (depsMap === void 0) {
9 | targetMap.set(target, (depsMap = new Map()))
10 | }
11 |
12 | let dep = depsMap.get(key)
13 | if (dep === void 0) {
14 | depsMap.set(key, (dep = new Set()))
15 | }
16 |
17 | if (!dep.has(effect)) {
18 | dep.add(effect)
19 | }
20 | }
21 | }
22 |
23 | export function trigger (target, operationType, key) {
24 | const depsMap = targetMap.get(target)
25 | if (depsMap === void 0) {
26 | return
27 | }
28 | const effects = new Set()
29 | if (key !== void 0) {
30 | const dep = depsMap.get(key)
31 | dep && dep.forEach(effect => {
32 | effects.add(effect)
33 | })
34 | }
35 | if (operationType === 'add' || operationType === 'set') {
36 | const iterationKey = Array.isArray(target) ? 'length' : Symbol('iterate')
37 | const dep = depsMap.get(iterationKey)
38 | dep && dep.forEach(effect => {
39 | effects.add(effect)
40 | })
41 | }
42 | effects.forEach(effect => {
43 | effect()
44 | })
45 | }
46 |
47 | export function effect (fn) {
48 | const effect = function effect(...args) {
49 | return run(effect, fn, args)
50 | }
51 | effect()
52 | return effect
53 | }
54 |
55 | export function run(effect, fn, args) {
56 | if (effectStack.indexOf(effect) === -1) {
57 | try {
58 | effectStack.push(effect)
59 | return fn(...args)
60 | } finally {
61 | effectStack.pop()
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/handlers.js:
--------------------------------------------------------------------------------
1 | import { reactive } from './reactive.js'
2 | import { track, trigger } from './effect.js'
3 |
4 | const isObject = (val) => val !== null && typeof val === 'object'
5 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key)
6 |
7 | function createGetter (target, key, receiver) {
8 | const res = Reflect.get(target, key, receiver)
9 | track(target, 'get', key)
10 | return isObject(res)
11 | ? reactive(res)
12 | : res
13 | }
14 |
15 | function createSetter (target, key, value, receiver) {
16 | const hadKey = hasOwn(target, key)
17 | const oldValue = target[key]
18 | const result = Reflect.set(target, key, value, receiver)
19 | if (!hadKey) {
20 | trigger(target, 'add', key)
21 | } else if (value !== oldValue) {
22 | trigger(target, 'set', key)
23 | }
24 |
25 | return result
26 | }
27 |
28 | function createDeleteProperty (target, key) {
29 | const hadKey = hasOwn(target, key)
30 | const oldValue = target[key]
31 | const result = Reflect.deleteProperty(target, key)
32 |
33 | if (hadKey) {
34 | trigger(target, 'delete', key)
35 | }
36 |
37 | return result
38 | }
39 |
40 | export const handler = {
41 | get: createGetter,
42 | set: createSetter,
43 | deleteProperty: createDeleteProperty
44 | }
45 |
--------------------------------------------------------------------------------
/src/reactive.js:
--------------------------------------------------------------------------------
1 | import { handler } from './handlers.js'
2 |
3 | export function reactive(target) {
4 | const observed = new Proxy(target, handler)
5 | return observed
6 | }
7 |
--------------------------------------------------------------------------------