Skip to content

Instantly share code, notes, and snippets.

@yyx990803
Last active September 3, 2019 10:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yyx990803/58467efe53ef0fdd57618e91b7c3ffee to your computer and use it in GitHub Desktop.
Save yyx990803/58467efe53ef0fdd57618e91b7c3ffee to your computer and use it in GitHub Desktop.
<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>
<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>
<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>
<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>
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