vue 之 h() 函数

目录

前言

h() 函数是什么

h() 函数参数

h() 函数基本使用

h() 函数创建 vnodes

h() 函数 API


前言

vue 在绝大多数情况下都推荐使用模板来编写 html 结构,但是对于一些复杂场景下需要完全的 JS 编程能力,这个时候我们就可以使用渲染函数 ,它比模板更接近编译器

vue 在生成真实的 DOM 之前,会将我们的节点转换成 VNode,而 VNode 组合在一起形成一颗树结构,就是虚拟 DOM(VDOM)

  我们之前编写的 template 中的 HTML 最终也是使用渲染函数生成对应的 VNode

h() 函数是什么

Vue 提供了一个 h() 函数用于创建 vnodes:

import { h } from 'vue'

const vnode = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)

h() 是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode(),但当你需要多次使用渲染函数时,一个简短的名字会更省力。

h()函数参数

  • type:类型参数,必填。一个html标签名,一个组件或者一个异步组件,或函数式组件
  • props:props参数,非必填。一个对象,内容包括了即将创建的节点的属性,例如 idclassstyle等,节点的事件监听也是通过 props 参数进行传递,并且以 on 开头,以 onXxx 的格式进行书写,如 onInputonClick 等。不写的话最好用 null占位
  • children:子节点,非必填。内容可以是文本、虚拟 DOM 节点和插槽等等。

官方完整类型参数如下:

// 完整参数签名
function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode

type Children = string | number | boolean | VNode | null | Children[]

type Slot = () => Children

type Slots = { [name: string]: Slot }

h() 函数基本使用

h()函数可以在两个地方使用:

  • render 函数中
    render() {
        return h(
          "div",
          {
            class: "app",
          },
          [
            // 这里this是可以取到setup中的返回值的 
            h("h2", null, `当前计数: ${this.counter}`),
            h("button", {onclick:() => this.counter++}, "+1"),
            h("button", {onclick:() => this.counter--}, "-1"),
          ]
        );
      },
  • setup函数中
     setup() {
        const counter = ref(0);
        return () =>
          h(
            "div",
            {
              class: "app",
            },
            [
              // 这里this是可以取到setup中的返回值的
              h("h2", null, `当前计数: ${counter.value}`),
              h("button", { onclick: () => counter.value++ }, "+1"),
              h("button", { onclick: () => counter.value-- }, "-1"),
            ]
          );
      },

h() 函数创建 vnodes

  • 创建原生元素
    // 除了类型必填以外,其他的参数都是可选的
    h('div')
    h('div', { id: 'foo' })
    
    // attribute 和 property 都能在 prop 中书写
    // Vue 会自动将它们分配到正确的位置
    h('div', { class: 'bar', innerHTML: 'hello' })
    
    // 像 `.prop` 和 `.attr` 这样的的属性修饰符
    // 可以分别通过 `.` 和 `^` 前缀来添加
    h('div', { '.name': 'some-name', '^width': '100' })
    
    // 类与样式可以像在模板中一样
    // 用数组或对象的形式书写
    h('div', { class: [foo, { bar }], style: { color: 'red' } })
    
    // 事件监听器应以 onXxx 的形式书写
    h('div', { onClick: () => {} })
    
    // children 可以是一个字符串
    h('div', { id: 'foo' }, 'hello')
    
    // 没有 props 时可以省略不写
    h('div', 'hello')
    h('div', [h('span', 'hello')])
    
    // children 数组可以同时包含 vnodes 与字符串
    h('div', ['hello', h('span', 'hello')])
  • 创建组件
    import Foo from './Foo.vue'
    
    // 传递 prop
    h(Foo, {
      // 等价于 some-prop="hello"
      someProp: 'hello',
      // 等价于 @update="() => {}"
      onUpdate: () => {}
    })
    
    // 传递单个默认插槽
    h(Foo, () => 'default slot')
    
    // 传递具名插槽
    // 注意,需要使用 `null` 来避免
    // 插槽对象被当作是 prop
    h(MyComponent, null, {
      default: () => 'default slot',
      foo: () => h('div', 'foo'),
      bar: () => [h('span', 'one'), h('span', 'two')]
    })

MyComponent 组件

  render() {
    return h("div", null, [
        h("h2", null,  "hello world"),
        [
            this.$slots.default ? this.$slots.default() : h("h2", null, "我是默认插槽"),
            this.$slots.foo()
            this.$slots.bar()
        ]
    ]);    
  }

h() 函数 API

{
  // 和`v-bind:class`一样的 API
  'class': {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`一样的 API
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 正常的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器基于 `on`
  // 所以不再支持如 `v-on:keyup.enter` 修饰器
  // 需要手动匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // Scoped slots in the form of
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其他组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其他特殊顶层属性
  key: 'myKey',
  ref: 'myRef'
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘子味的冰淇淋~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值