Last active
September 3, 2019 10:45
-
-
Save yyx990803/58467efe53ef0fdd57618e91b7c3ffee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="app"></div> | |
<script> | |
// fileA.js | |
let currentEffect | |
class Dep { | |
constructor() { | |
this.subscribers = new Set() | |
} | |
depend() { | |
if (currentEffect) { | |
this.subscribers.add(currentEffect) | |
} | |
} | |
notify() { | |
this.subscribers.forEach(sub => { | |
sub() | |
}) | |
} | |
} | |
function effect(runner) { | |
currentEffect = runner | |
runner() | |
currentEffect = null | |
} | |
// --- | |
const depsStorage = new WeakMap() | |
const handlers = { | |
get(target, key, receiver) { | |
let deps = depsStorage.get(target) | |
if (!deps) { | |
deps = {} | |
depsStorage.set(target, deps) | |
} | |
let dep = deps[key] | |
if (!dep) { | |
dep = deps[key] = new Dep() | |
} | |
dep.depend() | |
return observable(target[key]) | |
}, | |
set(target, key, value) { | |
target[key] = value | |
// notify | |
let deps = depsStorage.get(target) | |
if (!deps) { | |
return | |
} | |
const dep = deps[key] | |
if (dep) { | |
dep.notify() | |
} | |
} | |
} | |
const observedValues = new WeakMap() | |
function observable(obj) { | |
if (!obj || typeof obj !== 'object') { | |
return obj | |
} | |
if (observedValues.has(obj)) { | |
return observedValues.get(obj) | |
} | |
// check if obj is an already observed value | |
const observed = new Proxy(obj, handlers) | |
observedValues.set(obj, observed) | |
return observed | |
} | |
// property additions / deletions | |
// array index / length mutations | |
// Map / Set... | |
function h(tag, attrs, children) { | |
return { | |
tag, | |
attrs, | |
children: children && children.map(c => { | |
if (typeof c === 'string') { | |
return { | |
text: c | |
} | |
} else { | |
return c | |
} | |
}) | |
} | |
} | |
function mount(vdom, container) { | |
if (vdom.text) { | |
mountText(vdom, container) | |
} else { | |
mountElement(vdom, container) | |
} | |
} | |
function mountElement(vdom, container) { | |
const { tag, attrs, children } = vdom | |
const el = document.createElement(tag) | |
vdom.el = el | |
if (attrs) { | |
for (const key in attrs) { | |
el.setAttribute(key, attrs[key]) | |
} | |
} | |
if (children) { | |
children.forEach(child => { | |
mount(child, el) | |
}) | |
} | |
container.appendChild(el) | |
} | |
function mountText(vdom, container) { | |
const text = document.createTextNode(vdom.text) | |
vdom.el = text | |
container.appendChild(text) | |
} | |
function patch(vdom1, vdom2) { | |
const isText1 = !!vdom1.text | |
const isText2 = !!vdom2.text | |
if (isText1 && isText2) { | |
patchText(vdom1, vdom2) | |
} else if (!isText1 && !isText2) { | |
patchElement(vdom1, vdom2) | |
} else { | |
replaceNode(vdom1, vdom2) | |
} | |
} | |
function patchText(vdom1, vdom2) { | |
const el = vdom2.el = vdom1.el | |
if (vdom2.text !== vdom1.text) { | |
el.textContent = vdom2.text | |
} | |
} | |
function patchElement(vdom1, vdom2) { | |
if (vdom1.tag !== vdom2.tag) { | |
replaceNode(vdom1, vdom2) | |
return | |
} | |
const el = vdom2.el = vdom1.el | |
// diff attrs | |
// update different ones | |
// remove ones that are no longer present | |
// diff children | |
const oldChildren = vdom1.children | |
const newChildren = vdom2.children | |
newChildren.forEach((newChild, i) => { | |
const oldChild = oldChildren[i] | |
if (oldChild) { | |
patch(oldChild, newChild) | |
} else { | |
mount(newChild, el) | |
} | |
}) | |
if (oldChildren.length > newChildren.length) { | |
oldChildren.slice(newChildren.length).forEach(oldChild => { | |
// remove oldChild | |
}) | |
} | |
} | |
function replaceNode(vdom1, vdom2) { | |
// remove vdom1 | |
// mount vdom2 | |
} | |
// -------------- | |
const state = observable({ | |
msg: 'hello' | |
}) | |
function render(state) { | |
return h('div', { id: 'foo' }, [ | |
h('span', null, [state.msg]), | |
h('span', null, ['world']) | |
]) | |
} | |
let prevVDOM | |
effect(() => { | |
if (!prevVDOM) { | |
prevVDOM = render(state) | |
mount(prevVDOM, document.getElementById('app')) | |
} else { | |
const nextVDOM = render(state) | |
patch(prevVDOM, nextVDOM) | |
prevVDOM = nextVDOM | |
} | |
}) | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="count"></div> | |
<button onclick="state.count++">++</button> | |
<script> | |
let currentEffect | |
class Dep { | |
constructor() { | |
this.subscribers = new Set() | |
} | |
depend() { | |
if (currentEffect) { | |
this.subscribers.add(currentEffect) | |
} | |
} | |
notify() { | |
this.subscribers.forEach(sub => { | |
sub() | |
}) | |
} | |
} | |
function effect(runner) { | |
currentEffect = runner | |
runner() | |
currentEffect = null | |
} | |
// --- | |
function observable(obj) { | |
Object.keys(obj).forEach(key => { | |
let value = obj[key] | |
const dep = new Dep() | |
Object.defineProperty(obj, key, { | |
get() { | |
dep.depend() | |
return value | |
}, | |
set(newValue) { | |
value = newValue | |
dep.notify() | |
} | |
}) | |
}) | |
return obj | |
} | |
const state = observable({ | |
count: 0 | |
}) | |
effect(() => { | |
document.getElementById('count').textContent = state.count | |
}) | |
state.count++ | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="count"></div> | |
<button onclick="state.count++">++</button> | |
<script> | |
// fileA.js | |
let currentEffect | |
class Dep { | |
constructor() { | |
this.subscribers = new Set() | |
} | |
depend() { | |
if (currentEffect) { | |
this.subscribers.add(currentEffect) | |
} | |
} | |
notify() { | |
this.subscribers.forEach(sub => { | |
sub() | |
}) | |
} | |
} | |
function effect(runner) { | |
currentEffect = runner | |
runner() | |
currentEffect = null | |
} | |
// --- | |
const depsStorage = new WeakMap() | |
const handlers = { | |
get(target, key, receiver) { | |
let deps = depsStorage.get(target) | |
if (!deps) { | |
deps = {} | |
depsStorage.set(target, deps) | |
} | |
let dep = deps[key] | |
if (!dep) { | |
dep = deps[key] = new Dep() | |
} | |
dep.depend() | |
return observable(target[key]) | |
}, | |
set(target, key, value) { | |
target[key] = value | |
// notify | |
let deps = depsStorage.get(target) | |
if (!deps) { | |
return | |
} | |
const dep = deps[key] | |
if (dep) { | |
dep.notify() | |
} | |
} | |
} | |
const observedValues = new WeakMap() | |
function observable(obj) { | |
if (!obj || typeof obj !== 'object') { | |
return obj | |
} | |
if (observedValues.has(obj)) { | |
return observedValues.get(obj) | |
} | |
// check if obj is an already observed value | |
const observed = new Proxy(obj, handlers) | |
observedValues.set(obj, observed) | |
return observed | |
} | |
// property additions / deletions | |
// array index / length mutations | |
// Map / Set... | |
const state = observable({ | |
count: 0 | |
}) | |
effect(() => { | |
document.getElementById('count').textContent = state.count | |
}) | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="count"></div> | |
<script> | |
let currentEffect | |
class Observable { | |
constructor(initialValue) { | |
this.value = initialValue | |
this.subscribers = new Set() | |
} | |
get() { | |
this.depend() | |
return this.value | |
} | |
set(newValue) { | |
this.value = newValue | |
this.notify() | |
} | |
depend() { | |
if (currentEffect) { | |
this.subscribers.add(currentEffect) | |
} | |
} | |
notify() { | |
this.subscribers.forEach(sub => { | |
sub() | |
}) | |
} | |
} | |
function effect(runner) { | |
currentEffect = runner | |
runner() | |
currentEffect = null | |
} | |
const count = new Observable(1) | |
effect(() => { | |
document.getElementById('count').textContent = count.get() | |
}) // 1 | |
count.set(2) // 2 | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Implement a simple virtual DOM mounting algorithm. | |
<div id="app"></div> | |
<script> | |
function h(tag, attrs, children) { | |
return { | |
tag, | |
attrs, | |
children | |
} | |
} | |
const vdom = h('div', { id: 'foo' }, [ | |
h('span', null, [this.msg]), | |
h('span', null, ['world']) | |
]) | |
function mount(vdom, container) { | |
if (typeof vdom === 'string') { | |
mountText(vdom, container) | |
} else { | |
mountElement(vdom, container) | |
} | |
} | |
function mountElement(vdom, container) { | |
const { tag, attrs, children } = vdom | |
const el = document.createElement(tag) | |
if (attrs) { | |
for (const key in attrs) { | |
el.setAttribute(key, attrs[key]) | |
} | |
} | |
if (children) { | |
children.forEach(child => { | |
mount(child, el) | |
}) | |
} | |
container.appendChild(el) | |
} | |
function mountText(vdom, container) { | |
const text = document.createTextNode(vdom) | |
container.appendChild(text) | |
} | |
mount(vdom, document.getElementById('app')) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment