Vue3 学习笔记 —— 函数式编程、createVNode、render、h 函数

目录

2. createVNode()、render()

2.1 初步使用 createVNode()、render()

2.2 h 函数源码分析

3. 使用 h 函数的几种方法

3.1 h 函数 接收的参数

3.2 h 函数 使用方法

4. 通过 h 函数实现 button 组件

4.1 使用 props 接收传入组件的参数

4.2 使用 emit 向组件外发送事件

4.3 使用 ctx.slots.default() 在组件内定义插槽

5. 参考视频


template 模板

JSX(类似 React)

函数式编程(h 函数)

2. createVNode()、render()

2.1 初步使用 createVNode()、render()

Vue3 插件当时也涉及到了 createVNode()、render()

h 函数本质上是用了 Vue 内置函数 —— createVNode()、render() 实现的

我在 ElementPlus 实验室里试了一下:

  • 从 vue 中,引入 createVNode, render 函数
  • 使用 createVNode() 创建虚拟 DOM 节点
  • 使用 render() 渲染虚拟 DOM 节点

createVNode —— 创建虚拟 DOM:

  • @param 参数1 创建元素类型,必选
  • @param 参数2 创建元素属性
  • @paran 参数3 创建元素内容

render —— 渲染虚拟 DOM:

  • @param 参数1 要被渲染的虚拟 DOM,必选
  • @param 参数2 要渲染的位置,必选

完整测试代码:

<template>
  <div>
  </div>
</template>

<script lang="ts" setup>
import { createVNode, render } from 'vue'

/**
 * createVNode —— 创建虚拟 DOM
 * @param 参数1 创建元素类型,必选
 * @param 参数2 创建元素属性
 * @paran 参数3 创建元素内容
 * @description 虚拟 DOM 创建完成后,需要使用 render 函数,才能在页面中渲染
 */
const testDiv = createVNode('div', { id: "myDivId" }, 'Lyrelion');
console.log('虚拟 DOM 状态 --- testDiv ---', testDiv);

/**
 * render —— 渲染虚拟 DOM
 * @param 参数1 要被渲染的虚拟 DOM,必选
 * @param 参数2 要渲染的位置,必选
 * @description 虚拟 DOM 创建完成后,需要使用 render 函数,才能在页面中渲染
 */
render(testDiv, document.body);
</script>

 

结果如下,可以看到,我给 div 添加的 id、innerHTML,在虚拟 DOM 中都体现了 

2.2 h 函数源码分析

位置:packages\runtime-core\src\h.ts

打开后,可以看到作者贴心的加了一段注释:

// “h” 是 “createVNode” 的用户友好的版本,适用于手动编写的渲染函数


// 编译器生成的代码使用 “createVNode”,因为
// 1.它是单态的,避免了额外的呼叫开销
// 2.它允许为优化指定 patchFlags

// 没有 props 的命名槽,需要显式 “null” 以避免歧义

 

可以看到最后的 h 函数方法中,一直在使用 createVNode()

import {
  VNode,
  VNodeProps,
  createVNode,
  VNodeArrayChildren,
  Fragment,
  Text,
  Comment,
  isVNode
} from './vnode'

...
...
...

// “h” 是 “createVNode” 的用户友好的版本,适用于手动编写的渲染函数
// 编译器生成的代码使用 “createVNode”,因为
// 1.它是单态的,避免了额外的呼叫开销
// 2.它允许为优化指定 patchFlags

/*
// type only
h('div')

// type + props
h('div', {})

// type + omit props + children
// Omit props does NOT support named slots
h('div', []) // array
h('div', 'foo') // text
h('div', h('br')) // vnode
h(Component, () => {}) // default slot

// type + props + children
h('div', {}, []) // array
h('div', {}, 'foo') // text
h('div', {}, h('br')) // vnode
h(Component, {}, () => {}) // default slot
h(Component, {}, {}) // named slots

// 没有 props 的命名槽,需要显式 “null” 以避免歧义
h(Component, null, {})
**/

...
...
...

// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) {
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      // single vnode without props
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren])
      }
      // props without children
      return createVNode(type, propsOrChildren)
    } else {
      // omit props
      return createVNode(type, null, propsOrChildren)
    }
  } else {
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}

 

3. 使用 h 函数的几种方法

3.1 h 函数 接收的参数

  • type —— 元素的类型,必选
  • propsOrChildren —— 数据对象(props、attrs、dom、class、style、...)
  • children —— 子节点,可以包含混合的 VNode 和 字符串

除类型 type 之外,所有参数都是可选的

3.2 h 函数 使用方法

// 除类型之外的所有参数都是可选的
h('div')
h('div', { id: 'foo' })
 
// 属性和内容,都可以在第二个参数中使用
// Vue 会自动选择正确的分配方式
h('div', { class: 'bar', innerHTML: 'hello' })
 
// 可以添加 .prop 和 .attr 等修饰符
// 分别带有 “.” 和 “^” 前缀
h('div', { '.name': 'some-name', '^width': '100' })
 
// class、style 可以是对象,也可以是数组
h('div', { class: [foo, { bar }], style: { color: 'red' } })
 
// 定义事件需要加 on,如 onXxx
h('div', { onClick: () => {} })
 
// 子集可以是字符串
h('div', { id: 'foo' }, 'hello')
 
// 如果没有props,可以省略 props
h('div', 'hello')
h('div', [h('span', 'hello')])
 
// 子数组可以包含混合的 VNode 和 字符串
h('div', ['hello', h('span', 'hello')])

4. 通过 h 函数实现 button 组件

4.1 使用 props 接收传入组件的参数

定义一个箭头函数,该函数返回一个 h 函数

箭头函数默认接收两个参数 props、ctx,就跟 setup 接收的两个参数作用一样

再来回忆下:

  • 第一个参数 div 就是元素类型
  • 第二个参数 { class: 'my-div' } 就是 数据对象(props、attrs、dom、class、style、...)
  • 第三个参数 props.text 子节点,此处就是一段文字
<template>
  <Btn text="Lyrelion"></Btn>
</template>
  
<script setup lang='ts'>
import { h } from 'vue';

type Props = {
  text: string
}

// 按钮组件 —— 定义一个箭头函数,返回 h 函数
const Btn = (props: Props, ctx: any) => {
  return h('div', {
    class: 'my-div',
  }, props.text)
}

</script>

<style scoped>
  .my-div {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 110px;
    height: 55px;
    background: red;
    border-radius: 10px;
    color: #fff
  }
</style>

 

 

4.2 使用 emit 向组件外发送事件

emit 添加事件的时候,一定要带上 on 前缀

<template>
  <Btn text="Lyrelion" @on-click="getNum"></Btn>
</template>
  
<script setup lang='ts'>
import { h } from 'vue';

type Props = {
  text: string
}

// 按钮组件 —— 定义一个箭头函数,返回 h 函数
const Btn = (props: Props, ctx: any) => {
  return h('div', {
    class: 'my-div',
    onClick: () => {
      ctx.emit('on-click', 123)
    }
  }, props.text)
}

const getNum = (num: number) => {
  console.log(num);
}

</script>

 

 

4.3 使用 ctx.slots.default() 在组件内定义插槽

<template>
  <Btn @on-click="getNum">
        <template #default>
            Lyrelion-slots
        </template>
  </Btn>
</template>
  
<script setup lang='ts'>
import { h } from 'vue';

type Props = {
  text?: string
}

// 按钮组件 —— 定义一个箭头函数,返回 h 函数
const Btn = (props: Props, ctx: any) => {
  return h('div', {
    class: 'my-div',
    onClick: () => {
      ctx.emit('on-click', 123)
    }
  }, ctx.slots.default())
}

const getNum = (num: number) => {
  console.log(num);
}

</script>

 

5. 参考视频

小满Vue3(第三十八章 函数式编程,h函数)_哔哩哔哩_bilibili小满Vue3(第三十八章 函数式编程,h函数)是Vue3 + vite + Ts + pinia + 实战 + 源码 +electron的第50集视频,该合集共计110集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1dS4y1y7vd?p=50&vd_source=8bc01635b95dbe8ecd349b2c23b03a10

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lyrelion

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

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

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

打赏作者

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

抵扣说明:

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

余额充值