Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

虚拟DOM库Snabbdom #27

Open
janeLLLL opened this issue Oct 20, 2020 · 0 comments
Open

虚拟DOM库Snabbdom #27

janeLLLL opened this issue Oct 20, 2020 · 0 comments

Comments

@janeLLLL
Copy link
Owner

janeLLLL commented Oct 20, 2020

虚拟DOM库Snabbdom源码解析

报告:Vue.js内部当数据变化后,操作的是虚拟DOM,会对比新旧两次虚拟DOM的差异,再把差异更新到真实DOM

虚拟DOM库Snabbdom源码解析

虚拟DOM如何工作的?

Virtual DOM虚拟DOM:由普通的JS对象来描述DOM对象,因为不是真实的DOM对象,所以叫Virtual DOM

{
    sel: 'div',
    data: {},
    children: undefined,
    text: '1',
    elm: undefined,
    key: undefined
}

为什么使用?

  • 手动操作DOM麻烦,还需要考虑浏览器兼容性问题,虽有jQuery等简化DOM操作,随着项目的复杂DOM操作复杂提升
  • 为了简化DOM复杂,有了MVVM框架,解决了视图和状态的同步问题
  • 使用模板引擎简化视图,但是它没有解决跟踪状态变化问题,于是出现了Virtual DOM
  • Virtual DOM好处是当状态改变不需要立即更新DOM,创建一个虚拟树来描述DOM,Virtual DOM内部将弄清楚如何有效(diff)的更新DOM

描述:

  • 虚拟DOM可以维护程序的状态,跟踪上一次的状态
  • 通过比较前后两次状态的差异更新真实DOM

作用

  1. 维护视图和状态的关系

  2. 复杂视图情况下提升渲染性能

  3. 除了渲染DOM以外,还可以实现SSR(Nuxt.js/Next.js)、原生应用(Weex/React Native)、小程序(mpvue/uin-app)等

Virtual DOM库

  • Snabbdom
  • virtual-dom

创建项目

  • 打包工具:parcel
  • 创建项目,并安装parcel
yarn init -y
yarn add parcel-bundler
  • 配置package.jsonscripts
"scripts": {
	"dev": "parcel index.html --open",
	"build": "parcel build index.html"
}
  • 创建目录结构
index.html
package.json
-src
	main.js

导入Snabbdom

  • Snabbdom导入使用commonjs模块语法,流行使用ES6模块化语法import
  • ES6模块与CommonJS模块的差异
yarn add snabbdom
import { h, thunk, init } from 'snabbdom'
  • Snabbdom核心仅提供最基本的功能,只导出了三个函数init()、h()、thunk()

    • init()是一个高阶函数,返回patch()

    • h()返回虚拟节点VNode,这个函数我们在使用Vuejs的时候见过

      new Vue({
          router,
          store,
          render: h => h(App)
      }).$mount('#app')
    • thunk()是一种优化策略,可以在处理不可变数据时使用

  • 注意:导入不能使用import snabbdom from 'snabbdom'

    • 原因:末尾到处使用的语法时export到处API,没有使用export default导出默认输出

      export {h} from './h'
      export {thunk} from './thunk'
      
      export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI){
      }

Snabbdom代码演示

  1. 1.hello world
//main.js
import {h, init} from 'snabbdom'

/**
* 1.hello world
* 参数:数组、模块
* 返回值:patch函数,作用对比两个vnode的差异更新到真实DOM
*/
let patch = init([])
/**
* 第一个参数:标签+选择器
* 第二个参数:如果是字符串的话就是标签中内容
*/
let vnode = h('div#container.cls', 'hello world')

let app = document.querySelector('#app')
/**
* 第一个参数:可以是DOM元素,内部会把DOM元素转换为VNode
* 第二个参数:VNode
* 返回值:VNode
*/
let oldVnode = patch(app, vnode)

//假设的时刻:覆盖之前的元素
vnode = h('div', 'hello snabbdom')

patch(oldVnode, vnode)
  1. div中放置子元素h1,p
import { h, init } from 'snabbdom'

let patch = init([])

let vnode = h('div#contatainer', [
    h('h1', 'hello'),
    h('p', 'p标签')
])

let app = document.querySelector('#app')

let oldVnode = patch(app, vnode)

setTimeout(() => {
    vnode = h('div#contatainer', [
    h('h1', '22222'),
    h('p', '11111')
	])
    patch(oldVnode, vnode)
    
    //清空页面元素-错误:无法读取null的key属性
    //patch(oldVnode, null)
    patch(oldVnode, h('!'))
},2000)
<script src="./src/main.js"></script>
<!--要引入-->

模块

  • snabbdom的核心库并不能处理元素的属性/样式/事件等,如果需要处理的话,可以使用模块
  • 常用模块:官方提供了6个模块
    1. attributes
      • 设置DOM元素属性,使用setAttribute()
      • 处理布尔类型的属性
    2. props
      • attributes模块相似,设置DOM元素的属性element[attr] = value
      • 不处理布尔类型的属性
    3. class
      • 切换类样式
      • 注意:给元素设置类样式是通过sel选择器
    4. dataset
      • 设置data-*的自定义属性
    5. eventlisteners
      • 注册和移除事件
    6. style
      • 设置行内样式,支持动画
      • delayed/remove/destroy

模块使用

  • 模块使用步骤:
    • 导入需要的模块
    • init()中注册模块
    • 使用h()函数创建VNode的时候,可以把第二个参数设置为对象,其它参数往后移
  • 代码
//main.js
import { init, h } from 'snabbdom'

// 1.导入需要的模块
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'

// 2.init()中注册模块
let patch = init([
    style,
    eventlisteners
])

// 3.使用h()函数的第二个参数传入模块需要的数据(对象)
/**以前的h()
* let vnode = h('div#contatainer', [
*     h('h1', 'hello'),
*     h('p', 'p标签')
* ])
*/

let vnode = h('div', {
    style: {
        backgroundColor: 'red'
    },
    on: {
        click: eventHandler
    }
},[
    h('h1','hello!'),
    h('p','这是p')
])
function eventHandler() {
    console.log('1')
}

let app = document.quertSelector('#app')
patch(app, vnode)

Snabbdom源码解析

Snabbdom核心

  • 使用h()函数创建JavaScript对象(VNode)描述真实DOM
  • init()设置模块,创建patch()
  • patch()比较新旧两个VNode
  • 把变化的内容更新到真实DOM树上

h()

  • Vue中的h()函数支持组件机制,但Snabbdom不支持,但一样可以传入选择器

    new Vue({
        router,
        store,
        render: h => h(App)
    }).$mount('#app')
    • h()最早适用于hyperscript,使用JavaScript创建超文本
    • Snabbdom中h()不是创建超文本,而是创建VNode
  • 函数重载

    1. 参数个数类型不同的函数
    2. JavaScript中没有重载的概念
    3. typescript中有重载,不过重载的实现还是通过代码调整参数
  • 可以定义两个重名函数,通过参数个数、类型不同来区分

  • 分析:h.ts中重载的几个h()函数

VNode

export interface VNode {
    //选择器
    sel: string | undefined;
    //节点数据:属性/样式/事件
    data: VNodeData | undefined;
    //子节点:和text只能互斥
    children: Array<VNode | string> | undefined;
    //记录vnode对应的真实DOM
    elm: Node | undefined;
    //节点中的内容,和children只能互斥
    text: string | undefined;
    //优化用
    key: Key | undefined;
}

VNode渲染真实DOM

patch(oldVnode, newVnode )

  • patch()打补丁,把新节点中变化的内容渲染到真实DOM,最后返回新节点,作为下一次处理的旧节点
  • patchVnode():对比新旧节点VNode是否是相同节点
    • 若不是相同节点,删除之前的内容removeVnodes(),重新渲染
    • 如果是相同节点:
      • 判断新的VNode是否有text,如果有并且和oldVnode的text不同,setTextContent()直接更新文本内容
      • updateChildren():如果新的VNode有children,判断子节点是否有变化,判断子节点的过程使用的就是diff算法
  • diff过程只进行同层级比较

init函数

  • 返回一个patch(),是高阶函数

patch函数

  • 把vnode渲染成真实dom
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant