hyperapp源码解析

411 阅读2分钟

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让其插入到新的位置,而不用低效率地删除再新建节点。

参考资料

hyperapp