├── .prettierrc ├── 01-basic.js ├── 02-depsMap.js ├── 03-targetMap.js ├── 04-all-together.js ├── 05-activeEffect.js ├── 06-ref.js ├── 07-computed.js ├── 08-vue-reactivity.js └── reactivity.cjs.js /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /01-basic.js: -------------------------------------------------------------------------------- 1 | let price = 5 2 | let quantity = 2 3 | let total = 0 4 | 5 | let dep = new Set() 6 | 7 | let effect = () => { 8 | total = price * quantity 9 | } 10 | 11 | function track() { 12 | dep.add(effect) 13 | } 14 | 15 | function trigger() { 16 | dep.forEach(effect => effect()) 17 | } 18 | 19 | track() 20 | effect() 21 | -------------------------------------------------------------------------------- /02-depsMap.js: -------------------------------------------------------------------------------- 1 | const depsMap = new Map() 2 | function track(key) { 3 | // Make sure this effect is being tracked. 4 | let dep = depsMap.get(key) 5 | if (!dep) { 6 | // There is no dep (effects) on this key yet 7 | depsMap.set(key, (dep = new Set())) // Create a new Set 8 | } 9 | 10 | dep.add(effect) // Add effect to dep 11 | } 12 | function trigger(key) { 13 | let dep = depsMap.get(key) // Get the dep (effects) associated with this key 14 | if (dep) { 15 | // If they exist 16 | dep.forEach(effect => { 17 | // run them all 18 | effect() 19 | }) 20 | } 21 | } 22 | 23 | let product = { price: 5, quantity: 2 } 24 | let total = 0 25 | 26 | let effect = () => { 27 | total = product.price * product.quantity 28 | } 29 | 30 | track('quantity') 31 | effect() 32 | // console.log(total) 33 | 34 | // product.quantity = 3 35 | // trigger('quantity') 36 | // console.log(total) 37 | -------------------------------------------------------------------------------- /03-targetMap.js: -------------------------------------------------------------------------------- 1 | const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated 2 | function track(target, key) { 3 | // We need to make sure this effect is being tracked. 4 | let depsMap = targetMap.get(target) // Get the current depsMap for this target 5 | if (!depsMap) { 6 | // There is no map. 7 | targetMap.set(target, (depsMap = new Map())) // Create one 8 | } 9 | let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set 10 | if (!dep) { 11 | // There is no dependencies (effects) 12 | depsMap.set(key, (dep = new Set())) // Create a new Set 13 | } 14 | dep.add(effect) // Add effect to dependency map 15 | } 16 | function trigger(target, key) { 17 | const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects) 18 | if (!depsMap) { 19 | return 20 | } 21 | let dep = depsMap.get(key) // If there are dependencies (effects) associated with this 22 | if (dep) { 23 | dep.forEach(effect => { 24 | // run them all 25 | effect() 26 | }) 27 | } 28 | } 29 | let product = { price: 5, quantity: 2 } 30 | let total = 0 31 | let effect = () => { 32 | total = product.price * product.quantity 33 | } 34 | track(product, 'quantity') 35 | effect() 36 | // console.log(total) 37 | // product.quantity = 3 38 | // trigger(product, 'quantity') 39 | // console.log(total) 40 | -------------------------------------------------------------------------------- /04-all-together.js: -------------------------------------------------------------------------------- 1 | const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated 2 | function track(target, key) { 3 | // We need to make sure this effect is being tracked. 4 | let depsMap = targetMap.get(target) // Get the current depsMap for this target 5 | if (!depsMap) { 6 | // There is no map. 7 | targetMap.set(target, (depsMap = new Map())) // Create one 8 | } 9 | let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set 10 | if (!dep) { 11 | // There is no dependencies (effects) 12 | depsMap.set(key, (dep = new Set())) // Create a new Set 13 | } 14 | dep.add(effect) // Add effect to dependency map 15 | } 16 | function trigger(target, key) { 17 | const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects) 18 | if (!depsMap) { 19 | return 20 | } 21 | let dep = depsMap.get(key) // If there are dependencies (effects) associated with this 22 | if (dep) { 23 | dep.forEach((effect) => { 24 | // run them all 25 | effect() 26 | }) 27 | } 28 | } 29 | 30 | function reactive(target) { 31 | const handlers = { 32 | get(target, key, receiver) { 33 | let result = Reflect.get(target, key, receiver) 34 | track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET 35 | return result 36 | }, 37 | set(target, key, value, receiver) { 38 | let oldValue = target[key] 39 | let result = Reflect.set(target, key, value, receiver) 40 | if (result && oldValue != value) { 41 | trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them. 42 | } 43 | return result 44 | }, 45 | } 46 | return new Proxy(target, handlers) 47 | } 48 | 49 | let product = reactive({ price: 5, quantity: 2 }) 50 | let total = 0 51 | 52 | var effect = () => { 53 | total = product.price * product.quantity 54 | } 55 | effect() 56 | 57 | console.log('before updated quantity total = ' + total) 58 | product.quantity = 3 59 | console.log('after updated quantity total = ' + total) 60 | console.log('Updated quantity to = ' + product.quantity) 61 | -------------------------------------------------------------------------------- /05-activeEffect.js: -------------------------------------------------------------------------------- 1 | const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated 2 | let activeEffect = null // The active effect running 3 | 4 | function track(target, key) { 5 | if (activeEffect) { 6 | // <------ Check to see if we have an activeEffect 7 | // We need to make sure this effect is being tracked. 8 | let depsMap = targetMap.get(target) // Get the current depsMap for this target 9 | if (!depsMap) { 10 | // There is no map. 11 | targetMap.set(target, (depsMap = new Map())) // Create one 12 | } 13 | let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set 14 | if (!dep) { 15 | // There is no dependencies (effects) 16 | depsMap.set(key, (dep = new Set())) // Create a new Set 17 | } 18 | dep.add(activeEffect) // Add effect to dependency map 19 | } 20 | } 21 | 22 | function trigger(target, key) { 23 | const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects) 24 | if (!depsMap) { 25 | return 26 | } 27 | let dep = depsMap.get(key) // If there are dependencies (effects) associated with this 28 | if (dep) { 29 | dep.forEach((effect) => { 30 | // run them all 31 | effect() 32 | }) 33 | } 34 | } 35 | 36 | function reactive(target) { 37 | const handler = { 38 | get(target, key, receiver) { 39 | let result = Reflect.get(target, key, receiver) 40 | track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET 41 | return result 42 | }, 43 | set(target, key, value, receiver) { 44 | let oldValue = target[key] 45 | let result = Reflect.set(target, key, value, receiver) 46 | if (result && oldValue != value) { 47 | trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them. 48 | } 49 | return result 50 | }, 51 | } 52 | return new Proxy(target, handler) 53 | } 54 | 55 | function effect(eff) { 56 | activeEffect = eff 57 | activeEffect() 58 | activeEffect = null 59 | } 60 | 61 | let product = reactive({ price: 5, quantity: 2 }) 62 | let salePrice = 0 63 | let total = 0 64 | 65 | effect(() => { 66 | total = product.price * product.quantity 67 | }) 68 | effect(() => { 69 | salePrice = product.price * 0.9 70 | }) 71 | 72 | console.log( 73 | `Before updated quantity total (should be 10) = ${total} salePrice (should be 4.5) = ${salePrice}` 74 | ) 75 | product.quantity = 3 76 | console.log( 77 | `After updated quantity total (should be 15) = ${total} salePrice (should be 4.5) = ${salePrice}` 78 | ) 79 | product.price = 10 80 | console.log( 81 | `After updated price total (should be 30) = ${total} salePrice (should be 9) = ${salePrice}` 82 | ) 83 | -------------------------------------------------------------------------------- /06-ref.js: -------------------------------------------------------------------------------- 1 | const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated 2 | let activeEffect = null // The active effect running 3 | 4 | function track(target, key) { 5 | if (activeEffect) { 6 | // <------ Check to see if we have an activeEffect 7 | // We need to make sure this effect is being tracked. 8 | let depsMap = targetMap.get(target) // Get the current depsMap for this target 9 | if (!depsMap) { 10 | // There is no map. 11 | targetMap.set(target, (depsMap = new Map())) // Create one 12 | } 13 | let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set 14 | if (!dep) { 15 | // There is no dependencies (effects) 16 | depsMap.set(key, (dep = new Set())) // Create a new Set 17 | } 18 | dep.add(activeEffect) // Add effect to dependency map 19 | } 20 | } 21 | 22 | function trigger(target, key) { 23 | const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects) 24 | if (!depsMap) { 25 | return 26 | } 27 | let dep = depsMap.get(key) // If there are dependencies (effects) associated with this 28 | if (dep) { 29 | dep.forEach((effect) => { 30 | // run them all 31 | effect() 32 | }) 33 | } 34 | } 35 | 36 | function reactive(target) { 37 | const handler = { 38 | get(target, key, receiver) { 39 | let result = Reflect.get(target, key, receiver) 40 | track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET 41 | return result 42 | }, 43 | set(target, key, value, receiver) { 44 | let oldValue = target[key] 45 | let result = Reflect.set(target, key, value, receiver) 46 | if (result && oldValue != value) { 47 | trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them. 48 | } 49 | return result 50 | }, 51 | } 52 | return new Proxy(target, handler) 53 | } 54 | 55 | function ref(raw) { 56 | const r = { 57 | get value() { 58 | track(r, 'value') 59 | return raw 60 | }, 61 | set value(newVal) { 62 | raw = newVal 63 | trigger(r, 'value') 64 | }, 65 | } 66 | return r 67 | } 68 | 69 | // function ref(intialValue) { 70 | // return reactive({ value: initialValue }) 71 | // } 72 | 73 | function effect(eff) { 74 | activeEffect = eff 75 | activeEffect() 76 | activeEffect = null 77 | } 78 | 79 | let product = reactive({ price: 5, quantity: 2 }) 80 | let salePrice = ref(0) 81 | let total = 0 82 | 83 | effect(() => { 84 | salePrice.value = product.price * 0.9 85 | }) 86 | 87 | effect(() => { 88 | total = salePrice.value * product.quantity 89 | }) 90 | 91 | console.log( 92 | `Before updated quantity total (should be 9) = ${total} salePrice (should be 4.5) = ${salePrice.value}` 93 | ) 94 | product.quantity = 3 95 | console.log( 96 | `After updated quantity total (should be 13.5) = ${total} salePrice (should be 4.5) = ${salePrice.value}` 97 | ) 98 | product.price = 10 99 | console.log( 100 | `After updated price total (should be 27) = ${total} salePrice (should be 9) = ${salePrice.value}` 101 | ) 102 | -------------------------------------------------------------------------------- /07-computed.js: -------------------------------------------------------------------------------- 1 | const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated 2 | let activeEffect = null // The active effect running 3 | 4 | function track(target, key) { 5 | if (activeEffect) { 6 | // <------ Check to see if we have an activeEffect 7 | // We need to make sure this effect is being tracked. 8 | let depsMap = targetMap.get(target) // Get the current depsMap for this target 9 | if (!depsMap) { 10 | // There is no map. 11 | targetMap.set(target, (depsMap = new Map())) // Create one 12 | } 13 | let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set 14 | if (!dep) { 15 | // There is no dependencies (effects) 16 | depsMap.set(key, (dep = new Set())) // Create a new Set 17 | } 18 | dep.add(activeEffect) // Add effect to dependency map 19 | } 20 | } 21 | 22 | function trigger(target, key) { 23 | const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects) 24 | if (!depsMap) { 25 | return 26 | } 27 | let dep = depsMap.get(key) // If there are dependencies (effects) associated with this 28 | if (dep) { 29 | dep.forEach((eff) => { 30 | // run them all 31 | eff() 32 | }) 33 | } 34 | } 35 | 36 | function reactive(target) { 37 | const handler = { 38 | get(target, key, receiver) { 39 | let result = Reflect.get(target, key, receiver) 40 | track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET 41 | return result 42 | }, 43 | set(target, key, value, receiver) { 44 | let oldValue = target[key] 45 | let result = Reflect.set(target, key, value, receiver) 46 | if (result && oldValue != value) { 47 | trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them. 48 | } 49 | return result 50 | }, 51 | } 52 | return new Proxy(target, handler) 53 | } 54 | 55 | function ref(raw) { 56 | const r = { 57 | get value() { 58 | track(r, 'value') 59 | return raw 60 | }, 61 | set value(newVal) { 62 | raw = newVal 63 | trigger(r, 'value') 64 | }, 65 | } 66 | return r 67 | } 68 | 69 | function effect(eff) { 70 | activeEffect = eff 71 | activeEffect() 72 | activeEffect = null 73 | } 74 | 75 | function computed(getter) { 76 | let result = ref() 77 | 78 | effect(() => (result.value = getter())) 79 | 80 | return result 81 | } 82 | 83 | let product = reactive({ price: 5, quantity: 2 }) 84 | 85 | let salePrice = computed(() => { 86 | return product.price * 0.9 87 | }) 88 | 89 | let total = computed(() => { 90 | return salePrice.value * product.quantity 91 | }) 92 | 93 | console.log( 94 | `Before updated quantity total (should be 9) = ${total.value} salePrice (should be 4.5) = ${salePrice.value}` 95 | ) 96 | product.quantity = 3 97 | console.log( 98 | `After updated quantity total (should be 13.5) = ${total.value} salePrice (should be 4.5) = ${salePrice.value}` 99 | ) 100 | product.price = 10 101 | console.log( 102 | `After updated price total (should be 27) = ${total.value} salePrice (should be 9) = ${salePrice.value}` 103 | ) 104 | 105 | // Plus let's verify we can add additional objects to the reactive object 106 | 107 | product.name = 'Shoes' 108 | 109 | effect(() => { 110 | console.log(`Product name is now ${product.name}`) 111 | }) 112 | 113 | product.name = 'Socks' 114 | -------------------------------------------------------------------------------- /08-vue-reactivity.js: -------------------------------------------------------------------------------- 1 | var { reactive, computed, effect } = require('./reactivity.cjs') 2 | 3 | let product = reactive({ price: 5, quantity: 2 }) 4 | 5 | let salePrice = computed(() => { 6 | return product.price * 0.9 7 | }) 8 | 9 | let total = computed(() => { 10 | return salePrice.value * product.quantity 11 | }) 12 | 13 | console.log( 14 | `Before updated quantity total (should be 9) = ${total.value} salePrice (should be 4.5) = ${salePrice.value}` 15 | ) 16 | product.quantity = 3 17 | console.log( 18 | `After updated quantity total (should be 13.5) = ${total.value} salePrice (should be 4.5) = ${salePrice.value}` 19 | ) 20 | product.price = 10 21 | console.log( 22 | `After updated price total (should be 27) = ${total.value} salePrice (should be 9) = ${salePrice.value}` 23 | ) 24 | 25 | product.name = 'Shoes' 26 | 27 | effect(() => { 28 | console.log(`Product name is now ${product.name}`) 29 | }) 30 | 31 | product.name = 'Socks' 32 | -------------------------------------------------------------------------------- /reactivity.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | // Make a map and return a function for checking if a key 6 | // is in that map. 7 | // 8 | // IMPORTANT: all calls of this function must be prefixed with /*#__PURE__*/ 9 | // So that rollup can tree-shake them if necessary. 10 | function makeMap(str, expectsLowerCase) { 11 | const map = Object.create(null); 12 | const list = str.split(','); 13 | for (let i = 0; i < list.length; i++) { 14 | map[list[i]] = true; 15 | } 16 | return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]; 17 | } 18 | 19 | const EMPTY_OBJ = Object.freeze({}) 20 | ; 21 | const extend = (a, b) => { 22 | for (const key in b) { 23 | a[key] = b[key]; 24 | } 25 | return a; 26 | }; 27 | const hasOwnProperty = Object.prototype.hasOwnProperty; 28 | const hasOwn = (val, key) => hasOwnProperty.call(val, key); 29 | const isArray = Array.isArray; 30 | const isFunction = (val) => typeof val === 'function'; 31 | const isSymbol = (val) => typeof val === 'symbol'; 32 | const isObject = (val) => val !== null && typeof val === 'object'; 33 | const objectToString = Object.prototype.toString; 34 | const toTypeString = (value) => objectToString.call(value); 35 | const toRawType = (value) => { 36 | return toTypeString(value).slice(8, -1); 37 | }; 38 | const cacheStringFunction = (fn) => { 39 | const cache = Object.create(null); 40 | return ((str) => { 41 | const hit = cache[str]; 42 | return hit || (cache[str] = fn(str)); 43 | }); 44 | }; 45 | const capitalize = cacheStringFunction((str) => { 46 | return str.charAt(0).toUpperCase() + str.slice(1); 47 | }); 48 | // compare whether a value has changed, accounting for NaN. 49 | const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); 50 | 51 | const targetMap = new WeakMap(); 52 | const effectStack = []; 53 | let activeEffect; 54 | const ITERATE_KEY = Symbol('iterate'); 55 | function isEffect(fn) { 56 | return fn != null && fn._isEffect === true; 57 | } 58 | function effect(fn, options = EMPTY_OBJ) { 59 | if (isEffect(fn)) { 60 | fn = fn.raw; 61 | } 62 | const effect = createReactiveEffect(fn, options); 63 | if (!options.lazy) { 64 | effect(); 65 | } 66 | return effect; 67 | } 68 | function stop(effect) { 69 | if (effect.active) { 70 | cleanup(effect); 71 | if (effect.options.onStop) { 72 | effect.options.onStop(); 73 | } 74 | effect.active = false; 75 | } 76 | } 77 | function createReactiveEffect(fn, options) { 78 | const effect = function reactiveEffect(...args) { 79 | return run(effect, fn, args); 80 | }; 81 | effect._isEffect = true; 82 | effect.active = true; 83 | effect.raw = fn; 84 | effect.deps = []; 85 | effect.options = options; 86 | return effect; 87 | } 88 | function run(effect, fn, args) { 89 | if (!effect.active) { 90 | return fn(...args); 91 | } 92 | if (!effectStack.includes(effect)) { 93 | cleanup(effect); 94 | try { 95 | enableTracking(); 96 | effectStack.push(effect); 97 | activeEffect = effect; 98 | return fn(...args); 99 | } 100 | finally { 101 | effectStack.pop(); 102 | resetTracking(); 103 | activeEffect = effectStack[effectStack.length - 1]; 104 | } 105 | } 106 | } 107 | function cleanup(effect) { 108 | const { deps } = effect; 109 | if (deps.length) { 110 | for (let i = 0; i < deps.length; i++) { 111 | deps[i].delete(effect); 112 | } 113 | deps.length = 0; 114 | } 115 | } 116 | let shouldTrack = true; 117 | const trackStack = []; 118 | function pauseTracking() { 119 | trackStack.push(shouldTrack); 120 | shouldTrack = false; 121 | } 122 | function enableTracking() { 123 | trackStack.push(shouldTrack); 124 | shouldTrack = true; 125 | } 126 | function resetTracking() { 127 | const last = trackStack.pop(); 128 | shouldTrack = last === undefined ? true : last; 129 | } 130 | function track(target, type, key) { 131 | if (!shouldTrack || activeEffect === undefined) { 132 | return; 133 | } 134 | let depsMap = targetMap.get(target); 135 | if (depsMap === void 0) { 136 | targetMap.set(target, (depsMap = new Map())); 137 | } 138 | let dep = depsMap.get(key); 139 | if (dep === void 0) { 140 | depsMap.set(key, (dep = new Set())); 141 | } 142 | if (!dep.has(activeEffect)) { 143 | dep.add(activeEffect); 144 | activeEffect.deps.push(dep); 145 | if ( activeEffect.options.onTrack) { 146 | activeEffect.options.onTrack({ 147 | effect: activeEffect, 148 | target, 149 | type, 150 | key 151 | }); 152 | } 153 | } 154 | } 155 | function trigger(target, type, key, newValue, oldValue, oldTarget) { 156 | const depsMap = targetMap.get(target); 157 | if (depsMap === void 0) { 158 | // never been tracked 159 | return; 160 | } 161 | const effects = new Set(); 162 | const computedRunners = new Set(); 163 | if (type === "clear" /* CLEAR */) { 164 | // collection being cleared 165 | // trigger all effects for target 166 | depsMap.forEach(dep => { 167 | addRunners(effects, computedRunners, dep); 168 | }); 169 | } 170 | else if (key === 'length' && isArray(target)) { 171 | depsMap.forEach((dep, key) => { 172 | if (key === 'length' || key >= newValue) { 173 | addRunners(effects, computedRunners, dep); 174 | } 175 | }); 176 | } 177 | else { 178 | // schedule runs for SET | ADD | DELETE 179 | if (key !== void 0) { 180 | addRunners(effects, computedRunners, depsMap.get(key)); 181 | } 182 | // also run for iteration key on ADD | DELETE | Map.SET 183 | if (type === "add" /* ADD */ || 184 | type === "delete" /* DELETE */ || 185 | (type === "set" /* SET */ && target instanceof Map)) { 186 | const iterationKey = isArray(target) ? 'length' : ITERATE_KEY; 187 | addRunners(effects, computedRunners, depsMap.get(iterationKey)); 188 | } 189 | } 190 | const run = (effect) => { 191 | scheduleRun(effect, target, type, key, { 192 | newValue, 193 | oldValue, 194 | oldTarget 195 | } 196 | ); 197 | }; 198 | // Important: computed effects must be run first so that computed getters 199 | // can be invalidated before any normal effects that depend on them are run. 200 | computedRunners.forEach(run); 201 | effects.forEach(run); 202 | } 203 | function addRunners(effects, computedRunners, effectsToAdd) { 204 | if (effectsToAdd !== void 0) { 205 | effectsToAdd.forEach(effect => { 206 | if (effect !== activeEffect) { 207 | if (effect.options.computed) { 208 | computedRunners.add(effect); 209 | } 210 | else { 211 | effects.add(effect); 212 | } 213 | } 214 | }); 215 | } 216 | } 217 | function scheduleRun(effect, target, type, key, extraInfo) { 218 | if ( effect.options.onTrigger) { 219 | const event = { 220 | effect, 221 | target, 222 | key, 223 | type 224 | }; 225 | effect.options.onTrigger(extraInfo ? extend(event, extraInfo) : event); 226 | } 227 | if (effect.options.scheduler !== void 0) { 228 | effect.options.scheduler(effect); 229 | } 230 | else { 231 | effect(); 232 | } 233 | } 234 | 235 | // global immutability lock 236 | let LOCKED = true; 237 | function lock() { 238 | LOCKED = true; 239 | } 240 | function unlock() { 241 | LOCKED = false; 242 | } 243 | 244 | const builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol) 245 | .map(key => Symbol[key]) 246 | .filter(isSymbol)); 247 | const get = /*#__PURE__*/ createGetter(); 248 | const shallowReactiveGet = /*#__PURE__*/ createGetter(false, true); 249 | const readonlyGet = /*#__PURE__*/ createGetter(true); 250 | const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true); 251 | const arrayInstrumentations = {}; 252 | ['includes', 'indexOf', 'lastIndexOf'].forEach(key => { 253 | arrayInstrumentations[key] = function (...args) { 254 | const arr = toRaw(this); 255 | for (let i = 0, l = this.length; i < l; i++) { 256 | track(arr, "get" /* GET */, i + ''); 257 | } 258 | return arr[key](...args.map(toRaw)); 259 | }; 260 | }); 261 | function createGetter(isReadonly = false, shallow = false) { 262 | return function get(target, key, receiver) { 263 | if (isArray(target) && hasOwn(arrayInstrumentations, key)) { 264 | return Reflect.get(arrayInstrumentations, key, receiver); 265 | } 266 | const res = Reflect.get(target, key, receiver); 267 | if (isSymbol(key) && builtInSymbols.has(key)) { 268 | return res; 269 | } 270 | if (shallow) { 271 | track(target, "get" /* GET */, key); 272 | // TODO strict mode that returns a shallow-readonly version of the value 273 | return res; 274 | } 275 | // ref unwrapping, only for Objects, not for Arrays. 276 | if (isRef(res) && !isArray(target)) { 277 | return res.value; 278 | } 279 | track(target, "get" /* GET */, key); 280 | return isObject(res) 281 | ? isReadonly 282 | ? // need to lazy access readonly and reactive here to avoid 283 | // circular dependency 284 | readonly(res) 285 | : reactive(res) 286 | : res; 287 | }; 288 | } 289 | const set = /*#__PURE__*/ createSetter(); 290 | const shallowReactiveSet = /*#__PURE__*/ createSetter(false, true); 291 | const readonlySet = /*#__PURE__*/ createSetter(true); 292 | const shallowReadonlySet = /*#__PURE__*/ createSetter(true, true); 293 | function createSetter(isReadonly = false, shallow = false) { 294 | return function set(target, key, value, receiver) { 295 | if (isReadonly && LOCKED) { 296 | { 297 | console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target); 298 | } 299 | return true; 300 | } 301 | const oldValue = target[key]; 302 | if (!shallow) { 303 | value = toRaw(value); 304 | if (!isArray(target) && isRef(oldValue) && !isRef(value)) { 305 | oldValue.value = value; 306 | return true; 307 | } 308 | } 309 | const hadKey = hasOwn(target, key); 310 | const result = Reflect.set(target, key, value, receiver); 311 | // don't trigger if target is something up in the prototype chain of original 312 | if (target === toRaw(receiver)) { 313 | if (!hadKey) { 314 | trigger(target, "add" /* ADD */, key, value); 315 | } 316 | else if (hasChanged(value, oldValue)) { 317 | trigger(target, "set" /* SET */, key, value, oldValue); 318 | } 319 | } 320 | return result; 321 | }; 322 | } 323 | function deleteProperty(target, key) { 324 | const hadKey = hasOwn(target, key); 325 | const oldValue = target[key]; 326 | const result = Reflect.deleteProperty(target, key); 327 | if (result && hadKey) { 328 | trigger(target, "delete" /* DELETE */, key, undefined, oldValue); 329 | } 330 | return result; 331 | } 332 | function has(target, key) { 333 | const result = Reflect.has(target, key); 334 | track(target, "has" /* HAS */, key); 335 | return result; 336 | } 337 | function ownKeys(target) { 338 | track(target, "iterate" /* ITERATE */, ITERATE_KEY); 339 | return Reflect.ownKeys(target); 340 | } 341 | const mutableHandlers = { 342 | get, 343 | set, 344 | deleteProperty, 345 | has, 346 | ownKeys 347 | }; 348 | const readonlyHandlers = { 349 | get: readonlyGet, 350 | set: readonlySet, 351 | has, 352 | ownKeys, 353 | deleteProperty(target, key) { 354 | if (LOCKED) { 355 | { 356 | console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target); 357 | } 358 | return true; 359 | } 360 | else { 361 | return deleteProperty(target, key); 362 | } 363 | } 364 | }; 365 | const shallowReactiveHandlers = { 366 | ...mutableHandlers, 367 | get: shallowReactiveGet, 368 | set: shallowReactiveSet 369 | }; 370 | // Props handlers are special in the sense that it should not unwrap top-level 371 | // refs (in order to allow refs to be explicitly passed down), but should 372 | // retain the reactivity of the normal readonly object. 373 | const shallowReadonlyHandlers = { 374 | ...readonlyHandlers, 375 | get: shallowReadonlyGet, 376 | set: shallowReadonlySet 377 | }; 378 | 379 | const toReactive = (value) => isObject(value) ? reactive(value) : value; 380 | const toReadonly = (value) => isObject(value) ? readonly(value) : value; 381 | const getProto = (v) => Reflect.getPrototypeOf(v); 382 | function get$1(target, key, wrap) { 383 | target = toRaw(target); 384 | key = toRaw(key); 385 | track(target, "get" /* GET */, key); 386 | return wrap(getProto(target).get.call(target, key)); 387 | } 388 | function has$1(key) { 389 | const target = toRaw(this); 390 | key = toRaw(key); 391 | track(target, "has" /* HAS */, key); 392 | return getProto(target).has.call(target, key); 393 | } 394 | function size(target) { 395 | target = toRaw(target); 396 | track(target, "iterate" /* ITERATE */, ITERATE_KEY); 397 | return Reflect.get(getProto(target), 'size', target); 398 | } 399 | function add(value) { 400 | value = toRaw(value); 401 | const target = toRaw(this); 402 | const proto = getProto(target); 403 | const hadKey = proto.has.call(target, value); 404 | const result = proto.add.call(target, value); 405 | if (!hadKey) { 406 | trigger(target, "add" /* ADD */, value, value); 407 | } 408 | return result; 409 | } 410 | function set$1(key, value) { 411 | value = toRaw(value); 412 | key = toRaw(key); 413 | const target = toRaw(this); 414 | const proto = getProto(target); 415 | const hadKey = proto.has.call(target, key); 416 | const oldValue = proto.get.call(target, key); 417 | const result = proto.set.call(target, key, value); 418 | if (!hadKey) { 419 | trigger(target, "add" /* ADD */, key, value); 420 | } 421 | else if (hasChanged(value, oldValue)) { 422 | trigger(target, "set" /* SET */, key, value, oldValue); 423 | } 424 | return result; 425 | } 426 | function deleteEntry(key) { 427 | key = toRaw(key); 428 | const target = toRaw(this); 429 | const proto = getProto(target); 430 | const hadKey = proto.has.call(target, key); 431 | const oldValue = proto.get ? proto.get.call(target, key) : undefined; 432 | // forward the operation before queueing reactions 433 | const result = proto.delete.call(target, key); 434 | if (hadKey) { 435 | trigger(target, "delete" /* DELETE */, key, undefined, oldValue); 436 | } 437 | return result; 438 | } 439 | function clear() { 440 | const target = toRaw(this); 441 | const hadItems = target.size !== 0; 442 | const oldTarget = target instanceof Map 443 | ? new Map(target) 444 | : new Set(target) 445 | ; 446 | // forward the operation before queueing reactions 447 | const result = getProto(target).clear.call(target); 448 | if (hadItems) { 449 | trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget); 450 | } 451 | return result; 452 | } 453 | function createForEach(isReadonly) { 454 | return function forEach(callback, thisArg) { 455 | const observed = this; 456 | const target = toRaw(observed); 457 | const wrap = isReadonly ? toReadonly : toReactive; 458 | track(target, "iterate" /* ITERATE */, ITERATE_KEY); 459 | // important: create sure the callback is 460 | // 1. invoked with the reactive map as `this` and 3rd arg 461 | // 2. the value received should be a corresponding reactive/readonly. 462 | function wrappedCallback(value, key) { 463 | return callback.call(observed, wrap(value), wrap(key), observed); 464 | } 465 | return getProto(target).forEach.call(target, wrappedCallback, thisArg); 466 | }; 467 | } 468 | function createIterableMethod(method, isReadonly) { 469 | return function (...args) { 470 | const target = toRaw(this); 471 | const isPair = method === 'entries' || 472 | (method === Symbol.iterator && target instanceof Map); 473 | const innerIterator = getProto(target)[method].apply(target, args); 474 | const wrap = isReadonly ? toReadonly : toReactive; 475 | track(target, "iterate" /* ITERATE */, ITERATE_KEY); 476 | // return a wrapped iterator which returns observed versions of the 477 | // values emitted from the real iterator 478 | return { 479 | // iterator protocol 480 | next() { 481 | const { value, done } = innerIterator.next(); 482 | return done 483 | ? { value, done } 484 | : { 485 | value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), 486 | done 487 | }; 488 | }, 489 | // iterable protocol 490 | [Symbol.iterator]() { 491 | return this; 492 | } 493 | }; 494 | }; 495 | } 496 | function createReadonlyMethod(method, type) { 497 | return function (...args) { 498 | if (LOCKED) { 499 | { 500 | const key = args[0] ? `on key "${args[0]}" ` : ``; 501 | console.warn(`${capitalize(type)} operation ${key}failed: target is readonly.`, toRaw(this)); 502 | } 503 | return type === "delete" /* DELETE */ ? false : this; 504 | } 505 | else { 506 | return method.apply(this, args); 507 | } 508 | }; 509 | } 510 | const mutableInstrumentations = { 511 | get(key) { 512 | return get$1(this, key, toReactive); 513 | }, 514 | get size() { 515 | return size(this); 516 | }, 517 | has: has$1, 518 | add, 519 | set: set$1, 520 | delete: deleteEntry, 521 | clear, 522 | forEach: createForEach(false) 523 | }; 524 | const readonlyInstrumentations = { 525 | get(key) { 526 | return get$1(this, key, toReadonly); 527 | }, 528 | get size() { 529 | return size(this); 530 | }, 531 | has: has$1, 532 | add: createReadonlyMethod(add, "add" /* ADD */), 533 | set: createReadonlyMethod(set$1, "set" /* SET */), 534 | delete: createReadonlyMethod(deleteEntry, "delete" /* DELETE */), 535 | clear: createReadonlyMethod(clear, "clear" /* CLEAR */), 536 | forEach: createForEach(true) 537 | }; 538 | const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]; 539 | iteratorMethods.forEach(method => { 540 | mutableInstrumentations[method] = createIterableMethod(method, false); 541 | readonlyInstrumentations[method] = createIterableMethod(method, true); 542 | }); 543 | function createInstrumentationGetter(instrumentations) { 544 | return (target, key, receiver) => Reflect.get(hasOwn(instrumentations, key) && key in target 545 | ? instrumentations 546 | : target, key, receiver); 547 | } 548 | const mutableCollectionHandlers = { 549 | get: createInstrumentationGetter(mutableInstrumentations) 550 | }; 551 | const readonlyCollectionHandlers = { 552 | get: createInstrumentationGetter(readonlyInstrumentations) 553 | }; 554 | 555 | // WeakMaps that store {raw <-> observed} pairs. 556 | const rawToReactive = new WeakMap(); 557 | const reactiveToRaw = new WeakMap(); 558 | const rawToReadonly = new WeakMap(); 559 | const readonlyToRaw = new WeakMap(); 560 | // WeakSets for values that are marked readonly or non-reactive during 561 | // observable creation. 562 | const readonlyValues = new WeakSet(); 563 | const nonReactiveValues = new WeakSet(); 564 | const collectionTypes = new Set([Set, Map, WeakMap, WeakSet]); 565 | const isObservableType = /*#__PURE__*/ makeMap('Object,Array,Map,Set,WeakMap,WeakSet'); 566 | const canObserve = (value) => { 567 | return (!value._isVue && 568 | !value._isVNode && 569 | isObservableType(toRawType(value)) && 570 | !nonReactiveValues.has(value)); 571 | }; 572 | function reactive(target) { 573 | // if trying to observe a readonly proxy, return the readonly version. 574 | if (readonlyToRaw.has(target)) { 575 | return target; 576 | } 577 | // target is explicitly marked as readonly by user 578 | if (readonlyValues.has(target)) { 579 | return readonly(target); 580 | } 581 | if (isRef(target)) { 582 | return target; 583 | } 584 | return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers); 585 | } 586 | function readonly(target) { 587 | // value is a mutable observable, retrieve its original and return 588 | // a readonly version. 589 | if (reactiveToRaw.has(target)) { 590 | target = reactiveToRaw.get(target); 591 | } 592 | return createReactiveObject(target, rawToReadonly, readonlyToRaw, readonlyHandlers, readonlyCollectionHandlers); 593 | } 594 | // Return a reactive-copy of the original object, where only the root level 595 | // properties are readonly, and does NOT unwrap refs nor recursively convert 596 | // returned properties. 597 | // This is used for creating the props proxy object for stateful components. 598 | function shallowReadonly(target) { 599 | return createReactiveObject(target, rawToReadonly, readonlyToRaw, shallowReadonlyHandlers, readonlyCollectionHandlers); 600 | } 601 | // Return a reactive-copy of the original object, where only the root level 602 | // properties are reactive, and does NOT unwrap refs nor recursively convert 603 | // returned properties. 604 | function shallowReactive(target) { 605 | return createReactiveObject(target, rawToReactive, reactiveToRaw, shallowReactiveHandlers, mutableCollectionHandlers); 606 | } 607 | function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) { 608 | if (!isObject(target)) { 609 | { 610 | console.warn(`value cannot be made reactive: ${String(target)}`); 611 | } 612 | return target; 613 | } 614 | // target already has corresponding Proxy 615 | let observed = toProxy.get(target); 616 | if (observed !== void 0) { 617 | return observed; 618 | } 619 | // target is already a Proxy 620 | if (toRaw.has(target)) { 621 | return target; 622 | } 623 | // only a whitelist of value types can be observed. 624 | if (!canObserve(target)) { 625 | return target; 626 | } 627 | const handlers = collectionTypes.has(target.constructor) 628 | ? collectionHandlers 629 | : baseHandlers; 630 | observed = new Proxy(target, handlers); 631 | toProxy.set(target, observed); 632 | toRaw.set(observed, target); 633 | return observed; 634 | } 635 | function isReactive(value) { 636 | return reactiveToRaw.has(value) || readonlyToRaw.has(value); 637 | } 638 | function isReadonly(value) { 639 | return readonlyToRaw.has(value); 640 | } 641 | function toRaw(observed) { 642 | return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed; 643 | } 644 | function markReadonly(value) { 645 | readonlyValues.add(value); 646 | return value; 647 | } 648 | function markNonReactive(value) { 649 | nonReactiveValues.add(value); 650 | return value; 651 | } 652 | 653 | const convert = (val) => isObject(val) ? reactive(val) : val; 654 | function isRef(r) { 655 | return r ? r._isRef === true : false; 656 | } 657 | function ref(value) { 658 | return createRef(value); 659 | } 660 | function shallowRef(value) { 661 | return createRef(value, true); 662 | } 663 | function createRef(value, shallow = false) { 664 | if (isRef(value)) { 665 | return value; 666 | } 667 | if (!shallow) { 668 | value = convert(value); 669 | } 670 | const r = { 671 | _isRef: true, 672 | get value() { 673 | track(r, "get" /* GET */, 'value'); 674 | return value; 675 | }, 676 | set value(newVal) { 677 | value = shallow ? newVal : convert(newVal); 678 | trigger(r, "set" /* SET */, 'value', { newValue: newVal } ); 679 | } 680 | }; 681 | return r; 682 | } 683 | function unref(ref) { 684 | return isRef(ref) ? ref.value : ref; 685 | } 686 | function toRefs(object) { 687 | if ( !isReactive(object)) { 688 | console.warn(`toRefs() expects a reactive object but received a plain one.`); 689 | } 690 | const ret = {}; 691 | for (const key in object) { 692 | ret[key] = toProxyRef(object, key); 693 | } 694 | return ret; 695 | } 696 | function toProxyRef(object, key) { 697 | return { 698 | _isRef: true, 699 | get value() { 700 | return object[key]; 701 | }, 702 | set value(newVal) { 703 | object[key] = newVal; 704 | } 705 | }; 706 | } 707 | 708 | function computed(getterOrOptions) { 709 | let getter; 710 | let setter; 711 | if (isFunction(getterOrOptions)) { 712 | getter = getterOrOptions; 713 | setter = () => { 714 | console.warn('Write operation failed: computed value is readonly'); 715 | } 716 | ; 717 | } 718 | else { 719 | getter = getterOrOptions.get; 720 | setter = getterOrOptions.set; 721 | } 722 | let dirty = true; 723 | let value; 724 | let computed; 725 | const runner = effect(getter, { 726 | lazy: true, 727 | // mark effect as computed so that it gets priority during trigger 728 | computed: true, 729 | scheduler: () => { 730 | if (!dirty) { 731 | dirty = true; 732 | trigger(computed, "set" /* SET */, 'value'); 733 | } 734 | } 735 | }); 736 | computed = { 737 | _isRef: true, 738 | // expose effect so computed can be stopped 739 | effect: runner, 740 | get value() { 741 | if (dirty) { 742 | value = runner(); 743 | dirty = false; 744 | } 745 | track(computed, "get" /* GET */, 'value'); 746 | return value; 747 | }, 748 | set value(newValue) { 749 | setter(newValue); 750 | } 751 | }; 752 | return computed; 753 | } 754 | 755 | exports.ITERATE_KEY = ITERATE_KEY; 756 | exports.computed = computed; 757 | exports.effect = effect; 758 | exports.enableTracking = enableTracking; 759 | exports.isReactive = isReactive; 760 | exports.isReadonly = isReadonly; 761 | exports.isRef = isRef; 762 | exports.lock = lock; 763 | exports.markNonReactive = markNonReactive; 764 | exports.markReadonly = markReadonly; 765 | exports.pauseTracking = pauseTracking; 766 | exports.reactive = reactive; 767 | exports.readonly = readonly; 768 | exports.ref = ref; 769 | exports.resetTracking = resetTracking; 770 | exports.shallowReactive = shallowReactive; 771 | exports.shallowReadonly = shallowReadonly; 772 | exports.shallowRef = shallowRef; 773 | exports.stop = stop; 774 | exports.toRaw = toRaw; 775 | exports.toRefs = toRefs; 776 | exports.track = track; 777 | exports.trigger = trigger; 778 | exports.unlock = unlock; 779 | exports.unref = unref; 780 | --------------------------------------------------------------------------------