hyperapp源码解析
hyperapp介绍
- Hyperapp 是 JS 框架,其源码不到 400 行,压缩 gzip 后只有 1kB,却可以实现简单web应用
hyperapp使用
- 简单的实现一个todoList
<script type="module">
import { h, text, app } from "https://unpkg.com/hyperapp"
const AddTodo = (state) => ({
...state,
value: "",
todos: state.todos.concat(state.value),
})
const NewValue = (state, event) => ({
...state,
value: event.target.value,
})
app({
init: { todos: [], value: "" },
view: ({ todos, value }) =>
h("main", {}, [
h("h1", {}, text("To do list")),
h("input", { type: "text", oninput: NewValue, value }),
h("ul", {},
todos.map((todo) => h("li", {}, text(todo)))
),
h("button", { onclick: AddTodo }, text("New!")),
]),
node: document.getElementById("app"),
})
</script>
<main id="app"></main>
- state:为组件内部数据
- h类似于react的createElement,创建虚拟DOM
- view:类似于react的render,渲染视图
- node:挂载的DOM节点
h函数
- h函数创建VDOM
// 创建虚拟DOM
export var text = (value, node) =>
createVNode(value, EMPTY_OBJ, EMPTY_ARR, TEXT_NODE, node)
export var h = (tag, props, children = EMPTY_ARR) =>
createVNode(tag, props, isArray(children) ? children : [children])
var createVNode = (tag, props, children, type, node) => ({
tag,
props,
key: props.key,
children,
type,
node,
})
app函数
- 创建监听函数listen,在patch阶段被DOM监听调用 dispatch根据数据调用update跟新视图
- 定义render函数
- 根据初始化数据init调用render方法
// 监听函数
var listener = function (event) {
dispatch(this.events[event.type], event)
}
// dispatch函数创建,根据action的参数调用update从而重新render
dispatch((action, props) =>{
debugger;
return typeof action === "function"
? dispatch(action(state, props))
: isArray(action)
? typeof action[0] === "function"
? dispatch(action[0], action[1])
: action
.slice(1)
.map(
(fx) => fx && fx !== true && fx[0](dispatch, fx[1]),
update(action[0])
)
: update(action)
}))(init)
// render函数
var render = () =>
(node = patch(
node.parentNode,
node,
vdom,
(vdom = view(state)),
listener,
(busy = false)
))
patch函数
- patch:新旧节点的 diff 和更新
- 新旧节点相同(可直接通过 === 判断)时:不进行任何操作,直接返回
oldVNode === newVNode- 旧节点不存在或者新旧节点不同(通过 tag 判断)时:调用 createElement 创建新节点,并插入到 parent 的子元素中。如果旧节点存在,调用 removeElement 删除(初始也是这一步)
if(oldVNode == null || oldVNode.tag !== newVNode.tag){ node = parent.insertBefore( createNode((newVNode = maybeVNode(newVNode)), listener, isSvg), node ) if (oldVNode != null) { parent.removeChild(oldVNode.node) } }- 新旧节点均为非元素节点时:将 element 的 nodeValue 值赋为 newNode
if ( oldVNode != null && oldVNode.type === TEXT_NODE && newVNode.type === TEXT_NODE ) { if (oldVNode.tag !== newVNode.tag) node.nodeValue = newVNode.tag}-
新旧节点均存在,且为元素节点时候,更新节点属性,然后进入 children 数组中递归调用 patch 函数进行更新,通过给每个元素一个key让其插入到新的位置,而不用低效率地删除再新建节点。