snabbdom中文文档

2021-02-06

snabbdom库的翻译,主要是翻译README文件,介绍了一些api的使用。

Snabbdom

原文地址:https://github.com/snabbdom/snabbdom/blob/master/README.md

一个注重简单性、模块化、强大功能和性能的虚拟DOM库。


介绍

虚拟DOM很厉害。它允许我们将应用程序的视图作为其状态的函数来表达。但是现有的解决方案太过臃肿,太过缓慢,缺乏功能,API偏向于OOP缺乏需要的功能。

Snabbdom由一个极其简单、性能优异、可扩展的内核组成,代码 ≈ 200行。它提供了一个模块化的架构,具有丰富的功能,可以通过自定义模块进行扩展。为了保持核心的简单性,所有非必要的功能都委托给模块。

你可以把Snabbdom塑造成任何你想要的东西! 选择或自定义你想要的功能。你也可以直接使用默认的扩展。

Snabbdom具有以下特点:

特点

  • 核心特点

    • 约200行代码–你可以很容易地读完整个核心,并完全理解它的工作原理。
    • 可通过模块进行扩展。
    • 丰富的Hook函数,既可用于每个vnode,也可用于全局模块。 嵌入diff和patch的任何部分。
    • 优秀的性能。Snabbdom是最快的虚拟DOM库之一。
    • patch函数,其函数签名相当于reduce/scan函数。允许更容易地与FRP库集成。
  • 模块的特点

    • h函数,用于轻松创建虚拟DOM节点。
    • SVG与h一起工作
    • 用于制作复杂的CSS动画的功能。
    • 强大的事件监听器功能。
    • Thunks可进一步diff和patch过程。
  • 第三方功能


内容表格

核心文档

Snabbdom的核心只提供最基本的功能。它的设计是尽可能的简单,同时仍然是快速和可扩展的。

init

核心只公开一个函数 “init”。这个init函数 取一个模块列表,并返回一个使用该函数的patch函数。 指定的模块集。

import { classModule } from 'snabbdom/modules/class'
import { styleModule } from 'snabbdom/modules/style'

var patch = init([classModule, styleModule])

patch

init返回的patch函数有两个参数。第一个是代表当前视图的DOM元素或vnode。第二个是代表新的、更新的视图的vnode。

如果传递一个有父节点的DOM元素,“newVnode “将变成一个DOM节点,传递的元素将被创建的DOM节点替换。如果传递了一个旧的vnode,Snabbdom将有效地修改它以匹配新vnode中的描述。

任何旧的vnode必须是之前调用patch的结果。这是必要的,因为Snabbdom在vnode中存储信息。这使得它可以实现一个更简单和更高性能的架构。这也避免了创建一个新的旧的vnode树。

patch(oldVnode, newVnode)

Unmounting

虽然没有专门用于从挂载点元素中移除VNode树的API,但有一种方法几乎可以实现这一点,即提供一个注释VNode作为patch的第二个参数,例如:

patch(oldVnode, h('!', { hooks: { post: () => { /* patch complete */ } } }))

当然,那么在挂载点还是有一个单一的注释节点。

snabbdom/h

建议你使用snabbdom/h来创建vnodes。h接受一个作为字符串的标签/选择器、一个可选的数据对象和一个可选的字符串或子代数组。

import { h } from 'snabbdom/h'

var vnode = h('div', { style: { color: '#000' } }, [
  h('h1', 'Headline'),
  h('p', 'A paragraph'),
])

snabbdom/tovnode

将一个DOM节点转换为一个虚拟节点。特别适合于在已有的、服务器端生成的内容上打patch。

import { init } from 'snabbdom/init'
import { classModule } from 'snabbdom/modules/class'
import { propsModule } from 'snabbdom/modules/props'
import { styleModule } from 'snabbdom/modules/style'
import { eventListenersModule } from 'snabbdom/modules/eventlisteners'
import { h } from 'snabbdom/h' // helper function for creating vnodes
import { toVNode } from 'snabbdom/tovnode'

var patch = init([ // Init patch function with chosen modules
  classModule, // makes it easy to toggle classes
  propsModule, // for setting properties on DOM elements
  styleModule, // handles styling on elements with support for animations
  eventListenersModule, // attaches event listeners
])

var newVNode = h('div', { style: { color: '#000' } }, [
  h('h1', 'Headline'),
  h('p', 'A paragraph'),
])

patch(toVNode(document.querySelector('.container')), newVNode)

Hooks

hooks是一种钩入DOM节点生命周期的方式。Snabbdom提供了丰富的hook选择。Hook既可以被模块用来扩展Snabbdom,也可以在正常的代码中用于在虚拟节点的生命周期中执行任意代码。

概述

名称 触发时机 回调参数
pre patch函数开始时候 none
init 增加了一个vnode vnode
create 基于vnode的DOM元素已经被创建。 emptyVnode, vnode
insert 元素已被插入到DOM中 vnode
prepatch 元素即将被修补 oldVnode, vnode
update 元素正在更新 oldVnode, vnode
postpatch 元素已被修补 oldVnode, vnode
destroy 被直接或间接删除的元素 vnode
remove 元素被直接从DOM中删除 vnode, removeCallback
post patch函数已完成 none

钩子清单: pre, create,update, destroy, remove, post.

下列Hook可在单个项目的 “Hook “属性中使用。 elements: init, create, insert, prepatch, update, postpatch, destroy, remove.

使用方法

要使用Hook,将它们作为一个对象传递到数据对象参数的hook字段。

h('div.row', {
  key: movie.rank,
  hook: {
    insert: (vnode) => { movie.elmHeight = vnode.elm.offsetHeight }
  }
})

initHook

这个Hook在patch过程中,当发现一个新的虚拟节点时被调用。这个Hook在Snabbdom以任何方式处理节点之前被调用。也就是说,在它基于vnode创建一个DOM节点之前。

insert Hook

一旦vnode的DOM元素被调用,这个Hook就会被调用。插入到文档中,剩下的patch周期就完成了。这意味着你可以进行DOM测量(比如使用getBoundingClientRect在这个Hook中安全地使用,因为知道任何元素都不会被改变后,可能会影响插入元素的位置。

remove Hook

允许您挂钩删除元素。 一旦要从DOM中删除vnode,就会调用该Hook。 处理函数同时接收vnode和回调。 您可以使用回调控制和延迟删除。 挂钩完成其业务后,应立即调用该回调,并且仅在所有`remove’挂钩均已调用其回调之后,才删除该元素。

仅当要从其父元素中删除元素时才触发该Hook,而如果它是要删除的元素的子元素则不会触发。 为此,请参见destroyHook。

destroy Hook

当从DOM中删除其DOM元素或从DOM中删除其父元素时,将在虚拟节点上调用此挂钩。

要查看此Hook和removeHook之间的区别,如下。

var vnode1 = h('div', [h('div', [h('span', 'Hello')])])
var vnode2 = h('div', [])
patch(container, vnode1)
patch(vnode1, vnode2)

这里,“destroy “对内部的 “div “元素和它所包含的 “span “元素都会被触发。另一方面,“remove “只在 “div “元素上触发,因为它是唯一一个从其父元素中分离出来的元素。

例如,你可以使用remove在元素被移除时触发动画,并使用destroyHook为被移除元素的子元素的消失附加动画。

创建模块

模块的工作原理是为hooks注册全局监听器。一个模块就是一个将Hook名称映射到函数的字典。

var myModule = {
  create: function (oldVnode, vnode) {
    // invoked whenever a new virtual node is created
  },
  update: function (oldVnode, vnode) {
    // invoked whenever a virtual node is updated
  }
}

Modules 文档

描述了核心模块。所有模块都是可选的。

class 模块

类模块提供了一个简单的方法来动态切换元素上的类。它期望在class数据属性中找到一个对象。该对象应该将类名映射到布尔值,以指示该类是否应该留在或离开vnode上。

h('a', { class: { active: true, selected: false } }, 'Toggle')

props 模块

允许你设置DOM元素的属性。

h('a', { props: { href: '/foo' } }, 'Go to Foo')

属性只能设置。不能删除。尽管浏览器允许添加和删除自定义属性,但该模块不会尝试删除。 这是有道理的,因为原生DOM属性不能被删除。如果你使用自定义属性来存储值或引用DOM上的对象,那么请考虑使用下面的模块。 数据/*属性 而不是。也许通过数据集模块

attributes 模块

和 props 模块一样,但在DOM元素上设置属性而不是属性。

h('a', { attrs: { href: '/foo' } }, 'Go to Foo')

使用setAttribute添加和更新属性。如果是一个以前添加/设置过的属性,现在已经不存在了。中的属性,它将从DOM元素的属性中被移除。列表中使用removeAttribute

如果是布尔属性(如 disabledhiddenselected…),其意义不取决于属性值。(true false),而是取决于是否存在/不存在以下情况: 属性本身在DOM元素中。这些属性被处理为模块的不同:如果一个布尔属性被设置为一个 falsy value (0, -0, null, false,NaN, undefined, 或空字符串)("")),则该属性将从属性列表中删除DOM元素。

通过www.DeepL.com/Translator(免费版)翻译

dataset 模块

允许你在DOM元素上设置自定义数据属性(data-*)。然后可以通过HTMLElement.dataset属性访问这些属性。

h('button', { dataset: { action: 'reset' } }, 'Reset')

style 模块

样式模块是为了让你的HTML看起来光滑和流畅的动画。在 它的核心是允许你对元素设置CSS属性。

h('span', {
  style: { border: '1px solid #bada55', color: '#c0ffee', fontWeight: 'bold' }
}, 'Say my name, and every colour illuminates')

需要注意的是,如果样式模块的样式属性是作为属性从样式对象中移除。要删除一个样式。你应该把它设置为空字符串。

h('div', {
  style: { position: shouldFollow ? 'fixed' : '' }
}, 'I, I follow, I follow you')

CSS variables

支持CSS自定义属性,它们必须以--为前缀。

h('div', {
  style: { '--warnColor': 'yellow' }
}, 'Warning')

delayed-properties

您可以指定被延迟的属性。每当这些属性发生变化时,该变化直到下一帧之后才会被应用。

h('span', {
  style: { opacity: '0', transition: 'opacity 1s', delayed: { opacity: '1' } }
}, 'Imma fade right in!')

这使得它很容易声明性地对元素的进入进行动画。

不支持 transition-propertyall值。

设置 “remove “的属性

在 “remove “属性中设置的样式将在该元素被删除后生效。 即将被从DOM中移除。应用的样式应该是用CSS过渡动画。只有当所有的样式都完成后动画将元素从DOM中移除。

h('span', {
  style: {
    opacity: '1',
    transition: 'opacity 1s',
    remove: { opacity: '0' }
  }
}, 'It\'s better to fade out than to burn away')

这使得它很容易声明性地对元素的移除进行动画。

不支持 transition-propertyall值。

设置 “销毁 “的属性

h('span', {
  style: {
    opacity: '1',
    transition: 'opacity 1s',
    destroy: { opacity: '0' }
  }
}, 'It\'s better to fade out than to burn away')

不支持 transition-propertyall值。

事件监听模块

The event listeners module gives powerful capabilities for attaching event listeners.

你可以通过在on提供一个与事件名称相对应的属性的对象,将一个函数附加到一个vnode上的事件上。 你想监听的事件。该函数将在事件发生时被调用,并将传递属于它的事件对象。 事件监听器模块提供了强大的附加事件监听器的功能。

function clickHandler (ev) {
  console.log('got clicked')
}
h('div', { on: { click: clickHandler } })

然而,很多时候,你对事件对象并不真正感兴趣。本身。通常情况下,你有一些与元素相关联的数据,这些数据就是 触发一个事件,而你想让这些数据传递下去。

考虑一个有三个按钮的计数器应用程序,其中一个按钮用来递增 计数器增加1,一个增加2,一个增加 你并不关心到底是哪个按钮使计数器增加3。被按下。相反,你感兴趣的是什么数字是相关的。与点击的按钮。事件监听模块允许人们 通过在命名的事件属性处提供一个数组来表达。列表中的 数组中的第一个元素应该是一个将被调用的函数。与第二个元素中的值,一旦事件发生。

function clickHandler (number) {
  console.log('button ' + number + ' was clicked!')
}
h('div', [
  h('a', { on: { click: ()=>{clickHandler(1)} } }),
  h('a', { on: { click: ()=>{clickHandler(2)} } }),
  h('a', { on: { click: ()=>{clickHandler(3)} } }),
])

每个处理程序的调用不仅包括给定的参数,还包括当前事件和附加到参数列表中的vnode。它还支持通过指定一个处理程序数组来使用每个事件的多个监听器

stopPropagation = function (ev) {
  ev.stopPropagation()
}
sendValue = function (func, ev, vnode) {
  func(vnode.elm.value)
}

h('a', { on: { click: [[sendValue, console.log], stopPropagation] } })

Snabbdom允许在渲染之间交换事件处理程序。这发生在不实际接触DOM上的事件处理程序的情况下。

但是,请注意,在vnodes之间共享事件处理程序时应该小心,因为该模块使用了避免将事件处理程序重新绑定到DOM的技术。而且一般来说,vnodes之间共享数据并不能保证有效,因为模块是允许突变给定数据的)。

尤其是,你不应该做这样的事情。


var sharedHandler = {
  change: function (e) { console.log('you chose: ' + e.target.value) }
}
h('div', [
  h('input', {
    props: { type: 'radio', name: 'test', value: '0' },
    on: sharedHandler
  }),
  h('input', {
    props: { type: 'radio', name: 'test', value: '1' },
    on: sharedHandler
  }),
  h('input', {
    props: { type: 'radio', name: 'test', value: '2' },
    on: sharedHandler
  })
])

对于许多这样的情况,你可以使用基于数组的处理程序来代替(如上所述)。 或者,简单地确保每个节点都被传递了唯一的on值。

// Works
var sharedHandler = function (e) {
  console.log('you chose: ' + e.target.value)
}
h('div', [
  h('input', {
    props: { type: 'radio', name: 'test', value: '0' },
    on: { change: sharedHandler }
  }),
  h('input', {
    props: { type: 'radio', name: 'test', value: '1' },
    on: { change: sharedHandler }
  }),
  h('input', {
    props: { type: 'radio', name: 'test', value: '2' },
    on: { change: sharedHandler }
  })
])

SVG

当使用h函数创建虚拟的SVG时,SVG就能正常工作节点。SVG元素会自动创建相应的命名空间。

var vnode = h('div', [
  h('svg', { attrs: { width: 100, height: 100 } }, [
    h('circle', { attrs: { cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow' } })
  ])
])

SVG元素中的类

某些浏览器(如IE <=11)不支持SVG元素中的classList属性。 因为_class_模块内部使用classList,在这种情况下,除非你使用classList polyfill,否则它将无法工作。如果你不想使用polyfill,你可以使用_attributes_模块的class属性)。

Thunks

thunk函数需要一个选择器、一个用于识别thunk的键、一个返回vnode的函数和一个可变数量的状态参数。如果调用,渲染函数将接收状态参数。

thunk(selector, key, renderFn, [stateArguments])

只有当 “renderFn “改变或”[stateArguments]“数组长度或其元素改变时,才会调用 “renderFn”。

key是可选的。当selector被选中时,它应该被提供。 在thunks兄弟姐妹中并不唯一。这确保了thunk是 差异化时总是正确匹配。

Thunks是一种优化策略,当一个人是处理不可更改的数据。

考虑一个简单的函数,用于基于一个数字创建一个虚拟节点。

function numberView (n) {
  return h('div', 'Number is: ' + n)
}

视图只取决于n。这意味着,如果n不变: 然后创建虚拟DOM节点,并对旧的 vnode是浪费的。为了避免开销,我们可以使用 “thunk “辅助工具。 职能。

function render (state) {
  return thunk('num', numberView, [state.number])
}

而不是实际调用 “numberView “函数,这将只是 在虚拟树中放置一个虚拟的vnode。当Snabbdom对这个 的值进行比较,它将比较 n. 如果n没有变化,它将简单地重新使用旧的vnode。这 完全避免了重新创建数字视图和差异过程。 这里的视图功能只是一个例子。在实践中,thunks只是 如果您要渲染一个复杂的视图,而这个视图需要 显著的计算时间来生成。

Virtual Node

Properties

sel : String

The .sel property of a virtual node is the CSS selector passed to h() during creation. For example: h('div#container', {}, [...]) will create a a virtual node which has div#container as its .sel property.

data : Object

The .data property of a virtual node is the place to add information for modules to access and manipulate the real DOM element when it is created; Add styles, CSS classes, attributes, etc.

The data object is the (optional) second parameter to h()

For example h('div', {props: {className: 'container'}}, [...]) will produce a virtual node with

({
  props: {
    className: 'container'
  }
})

as its .data object.

children : Array

The .children property of a virtual node is the third (optional) parameter to h() during creation. .children is simply an Array of virtual nodes that should be added as children of the parent DOM node upon creation.

For example h('div', {}, [ h('h1', {}, 'Hello, World') ]) will create a virtual node with

[
  {
    sel: 'h1',
    data: {},
    children: undefined,
    text: 'Hello, World',
    elm: Element,
    key: undefined,
  }
]

as its .children property.

text : string

当创建的虚拟节点只有一个拥有文本的子节点,只需要使用document.createTextNode()就可以创建.text属性。

For example: h('h1', {}, 'Hello') will create a virtual node with Hello as its .text property.

elm : Element

虚拟节点的.elm属性是指向真实DOM的指针。 由Snabbdom创建的节点。这个属性在做以下事情时非常有用Hook中的计算结果以及模块

key : string | number

.key属性是在你的 .data对象。.key属性用于保存。指向以前存在的DOM节点的指针,以避免重新创建它们。如果它是不必要的。这对于像 list 重新排序。键必须是字符串或数字,以便于进行 的内部存储为键/值对,因此可以进行正确的查找。对象,其中.key是键,值是 创建.elm属性。

如果提供,.key属性必须在同级元素中是唯一的。

例如:h('div', {key: 1}, [])将创建一个虚拟节点。对象,其.key属性的值为1

构建应用程序

Snabbdom是一个的虚拟DOM库。它与 关于如何构建你的应用程序的问题,以下是一些使用Snabbdom构建应用程序的方法。

下面是一些使用Snabbdom构建应用程序的方法。

  • functional-frontend-architecture – a repository containing several example applications that demonstrates an architecture that uses Snabbdom.
  • Cycle.js – “A functional and reactive JavaScript framework for cleaner code” uses Snabbdom
  • Vue.js use a fork of snabbdom.
  • scheme-todomvc build redux-like architecture on top of snabbdom bindings.
  • kaiju - Stateful components and observables on top of snabbdom
  • Tweed – An Object Oriented approach to reactive interfaces.
  • Cyclow - “A reactive frontend framework for JavaScript” uses Snabbdom
  • Tung – A JavaScript library for rendering html. Tung helps to divide html and JavaScript development.
  • sprotty - “A web-based diagramming framework” uses Snabbdom.
  • Mark Text - “Realtime preview Markdown Editor” build on Snabbdom.
  • puddles - “Tiny vdom app framework. Pure Redux. No boilerplate.” - Built with ❤️ on Snabbdom.
  • Backbone.VDOMView - A Backbone View with VirtualDOM capability via Snabbdom.
  • Rosmaro Snabbdom starter - Building user interfaces with state machines and Snabbdom.
  • Pureact - “65 lines implementation of React incl Redux and hooks with only one dependency - Snabbdom”
  • Snabberb - A minimalistic Ruby framework using Opal and Snabbdom for building reactive views.