虚拟DOM
将目标所需的UI通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的DOM与之保持同步
1 2 3 4 5 6 7 8 9
| const vnode ={ type: 'div', props: { id: 'rootdiv' }, children: [ ] }
|
挂载:
- 一个运行时渲染器将会遍历整个虚拟DOM树,并据此构建真实的DOM树
更新:
- 存在两份及以上的虚拟树,渲染器会有比较地遍历它们,找区别并应用到真实DOM`上
渲染管线
编译:
- Vue模板被编译成渲染函数,即用来返回虚拟DOM树的函数
挂载:
- 运行时渲染器调用渲染函数,遍历返回的虚拟DOM树,并基于它创建实际的DOM节点。会作为响应式副作用执行,会追踪所用到的所有响应式依赖
更新:
- 依赖变化,副作用重新运行,创建一个更新后的虚拟DOM树。运行时渲染器遍历新树,与旧树对比,更新到真实DOM上
模板 vs 渲染函数
- 渲染函数一般只会在需要处理高度动态渲染逻辑的可重用组件中使用
- 模板对大多数应用场景都是够用且高效
带编译时信息的虚拟 DOM
- Vue 中,框架同时控制着编译器和运行时。
- 编译器可以静态分析模板并在生成的代码中留下标记,使得运行时尽可能地走捷径。
- 保留了边界情况时用户想要使用底层渲染函数的能力。
静态提升
- 模板中的元素不带任何绑定,完全静态,vue编译器会自动提升这部分元素到这个模板的渲染函数外,对比时完全跳过去
更新类型标记
- 对于单个有动态绑定的元素,vue在vnode调用中直接编码每个元素所需的更新类型,且一个元素可以有多个更新类型的标记
树结构打平
- 打平成一个数组,且仅包含所有动态的后代节点。重渲染时,只需遍历打平的树,减少虚拟DOM更新时需遍历的节点数量
对SSR激活的影响 – 提升
渲染函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { h } from 'vue'
const vnode = h( 'div', { id: 'foo', class: 'bar' }, [ ] )
const vnode = h('div', { id: 'foo' }, [])
vnode.type vnode.props vnode.children vnode.key
|
声明渲染函数 — 使用render
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { h } from 'vue'
export default { data() { return { msg: 'hello' } }, render() { return h('div', this.msg) } }
|
Vnodes 必须唯一
渲染多个重复的元素或者组件 — 工厂函数
1 2 3 4 5 6 7 8 9
| 示例: function render() { return h( 'div', Array.from({ length: 20 }).map(() => { return h('p', 'hi') }) ) }
|
JSX / TSX — 是 JavaScript 的一个类似 XML 的扩展
const vnode = <div id={dynamicId}>hello, {userName}</div>
Vue 的 JSX 编译方式与 React 中 JSX 的编译方式区别
- 可以使用 HTML attributes 比如 class 和 for 作为 props
- 不需要使用 className 或 htmlFor。
- 传递子元素给组件 (比如 slots) 的方式不同。
当使用 TSX 语法时,确保在 tsconfig.json 中配置了 “jsx”: “preserve”
渲染案例
v-if
- 渲染函数:
h('div', [this.ok ? h('div', 'yes') : h('span', 'no')])
- JSX:
<div>{this.ok ? <div>yes</div> : <span>no</span>}</div>
v-for
渲染函数:
1 2 3 4 5 6
| h( 'ul', this.items.map(({ id, text }) => { return h('li', { key: id }, text) }) )
|
JSX:
1 2 3 4 5
| <ul> {this.items.map(({ id, text }) => { return <li key={id}>{text}</li> })} </ul>
|
v-on
- 渲染函数:
1 2 3 4 5 6 7 8 9
| h( 'button', { onClick(event) { } }, 'click me' )
|
- JSX:
1 2 3 4 5 6 7
| <button onClick={(event) => { }} > click me </button>
|
事件修饰符 – 使用驼峰写法将他们拼接在事件名后面
- .passive —> onClickPassive
- 对于事件和按键修饰符,可以使用 withModifiers 函数:
- 渲染函数:
1 2 3 4 5
| import { withModifiers } from 'vue'
h('div', { onClick: withModifiers(() => {}, ['self']) })
|
- JSX:
<div onClick={withModifiers(() => {}, ['self'])} />
组件 – 不再需要注册组件,直接导入
- 渲染函数:
return h('div', [h(Foo), h(Bar)])
- JSX:
<Foo />
渲染插槽 – 通过 this.$slots 来访问插槽
- 渲染函数:
h('div', this.$slots.default())
- JSX:
<div>{this.$slots.default()}</div>
传递插槽
- 传递一个插槽函数或者是一个包含插槽函数的对象而非是数组
- 插槽函数的返回值同一个正常的渲染函数的返回值一样——并且在子组件中被访问时总是会被转化为一个 vnodes 数组。
- 渲染函数:
1 2 3 4 5 6 7 8 9 10 11
| h(MyComponent, () => 'hello')
h(MyComponent, null, { default: () => 'default slot', foo: () => h('div', 'foo'), bar: () => [h('span', 'one'), h('span', 'two')] })
|
- JSX:
1 2 3 4 5 6 7 8 9
| <MyComponent>{() => 'hello'}</MyComponent>
<MyComponent>{{ default: () => 'default slot', foo: () => <div>foo</div>, bar: () => [<span>one</span>, <span>two</span>] }}</MyComponent>
|
- 插槽以函数的形式传递使得它们可以被子组件懒调用。
内置组件
v-model
- 扩展为 modelValue 和 onUpdate:modelValue, 模板编译过程中必须自己提供props
1 2 3 4 5 6 7 8 9 10
| export default { props: ['modelValue'], emits: ['update:modelValue'], render() { return h(SomeComponent, { modelValue: this.modelValue, 'onUpdate:modelValue': (value) => this.$emit('update:modelValue', value) }) } }
|
自定义指令
1 2 3 4 5 6 7 8 9 10 11 12
| import { h, withDirectives } from 'vue'
const pin = { mounted() { }, updated() { } }
const vnode = withDirectives(h('div'), [ [pin, 200, 'top', { animate: true }] ])
|
1 2 3 4 5
| function MyComponent(props, context) { context:{attrs,emit,slots} } MyComponent.inheritAttrs = false
|