vue2 中如何使用 render 函数编写组件

vue2 中如何使用 render 函数编写组件

    • render 基础语法
    • createElement
      • 返回值:VNode
      • 参数
        • 处理样式和类
        • 组件 props
        • HTML 特性和 DOM 属性
        • 处理事件
        • 插槽
        • 指令
        • v-model 指令
        • 其他属性
    • 使用 render 封装一个输入框
    • 其他问题
    • 参考

vue 提供了声明式编写 UI 的方式,即 vue 提供了对 DOM 进行描述的方式。

有两种描述 DOM 的方式:模板和 render 函数。模板在编译阶段会被转成 render 函数,这一过程叫编译模板。

模板可读性好,但是有时候模板并不灵活,大型的模板可读性也不好。

render 函数可读性不高,但是灵活,使用 render 函数封装组件,使用得当,可提高组件的扩展性和易用性。jsx 可解决 render 函数读写性不高的问题。

在 vue 的项目入口文件中,下面的代码新建一个 vue 应用的根组件,并默认命名为 Root,并将其挂载在 HTML 模板 #app div 上,它的模板在哪?

new Vue({render: h => h(App),
}).$mount('#app')

这是一个没有模板的组件。

KaTeX parse error: Expected 'EOF', got '#' at position 8: mount('#̲app'),选择器对应的 do…mount(elector)`挂载元素,替换 selector 后,dom 的属性丢失。 两者表现不同,有点奇怪,但是不需要太关注这个区别。

今天再来复习 render 函数,重点关注这些容易踩坑的地方:

  1. 学习 render 函数的使用,重点:样式、事件、插槽、指令、props、v-model、函数组件的处理。

  2. 学习使用 jsx 封装组件。

  3. 在 render 函数中使用表单组件,因为表单组件涉及到 v-model 容易踩坑。

为何要了解 render ?

在 render 函数中,可用 jsx 代替模板语法,可充分发挥 js 的能力,使得组件扩展性更好、封装更加优雅。

官网说得比较明白了,但是例子过于简单,只能体现优雅,没有体现扩展性,稍后封装一个能体现扩展性的组件。

render 基础语法

render 函数签名:

render(createElement: CreateElement, hack: RenderContext<Props>): VNode;
  1. 返回值 – VNode (虚拟节点) — 一个用于描述 vue 组件结构的 JS 对象

vnode对象

说明
①. 返回值往往是一个单节点,划线的 context 是 vue 全局对象,有 $children 属性,是一个 VNode 数组,元素是一个 VNode,这样的层层嵌套,正好对应的组件的嵌套。 $el 是组件挂载点,_uid 是组件 id。调试时可能会用到。

②. 也可以返回 VNode 数组。比如返回 this.$scopedSlots.default() 这是一个作用域插槽,可能是数组。

  1. 参数

第一个参数 createElement,是一个函数,DOM 中有 createElement 用于创建 DOM 节点。vue 中的 createElement 是用于创建 VNode 的。

h 是 createElement 的别名,代表 Hyperscript (生成 HTML 的脚本),Hyperscript itself stands for “script that generates HTML structures”。

写成h,更加方便输入,更加语义化。

第二个参数 hack 是渲染上下文,组件的propslistenersslots 都在这个参数里。

hack 参数

说明
第二个参数在函数组件中才有,非函数组件为 undefined。因为函数组件中不存在组件实例 this,要提供这个参数获取 props 等内容。
第二个参数通常写成context,更加语义化。

render 写成这样:

render(h,context){return <span>render函数</span>
}

render 使用 es6 声明方法的方式且返回 jsx ,vue 会自动注入 const h = this.$createElement,仅限于 render 函数,其他函数要使用,就要手动通过参数传入。

render(){return <span>render函数</span>
}

这是 es5 的方式:

render:function(){ // 显示传递 h:  function(h) 才行return <span>render函数</span>
}

render 不能返回文本,想要显示文本内容,必须套一个标签, react 的 render 可以返回文本。

返回 jsx,vue 内部会调用 createElement编译成 VNode。

如何返回纯文本?

返回文本的 hack 写法
return this._v('someText'),不推荐这么写,会导致他人难以理解。

createElement

返回值:VNode

VNode 是一个描述组件的普通 js 对象。

参数

createElement(// html 标签 、自定义标签,比如 el-input// template // NOTE 用于传递插槽// 一个组件选项对象// resolve 了上述任何一种的一个 async 函数 // TODO 如何使用'div', // NOTE 必需的// 模板使用到的数据对象{},// string 或者 子VNode[]
)

第一个参数不能省略,所以不能返回纯文本。 和 react 的 render 不同。

注意
第一个参数可以是 template,往往和第二个参数的slot属性一起使用,指定插槽名称,传递插槽时可以用到。
resolve 的用法我没有搜索到例子,欢迎大佬告诉我。

重点关注第二个参数

处理样式和类
{// :class = "{foo:true,bar:false}"class:{foo: true,bar: false},// :style="{color:'red','font-size':'14px'}"style:{color:'red',fontSize:'14px'}
}
组件 props
{props: {myCustomProp: '组件属性值'}
}
HTML 特性和 DOM 属性
{// HTML 特性// NOTE 在组件内部使用 $attrs 获取 attrs// NOTE 会和 class 属性合并吗?// NOTE 和 class 属性的优先级,谁高?// 这里的 class 不会添加到 标签上attrs: {id: 'divId',class: 'className'},// DOM 属性domProps:{textContent: 'div 文本',// 优先级高于 v-textinnerHTML: 'BAR'  // 优先级高于 v-html}
}

注意
①. attrs 特性中的 class 不会被添加到标签上。
②. 注意区分 HTML 特性和 DOM 属性的区别。

处理事件
{// v-bind:eventon: {customEventName: value => {// 监听组件的自定义事件 即 emit 触发事件}},// 监听组件上的原生事件,只能用在组件上nativeOn: {click: target => {// 监听原生事件 即非 emit 触发的事件}}
}

注意
nativeOn 只能用于自定义组件。

插槽
{scopedSlots: {// 默认插槽default: props => h('span',props.text),otherSlot: props => h('div',props.customProp)},slot: 'slotName'// 一般和第一个参数 template 一起使用
}

使用模板定义一个按钮:

<template><div><slot name="left"></slot><button><slot v-bind:person="person"><span>按钮</span></slot></button><slot name="right" v-bind:age="person.age"></slot></div>
</template><script>export default {name: 'MyButton',data() {return {person: {name: 'jack',age: 23,},}},}
</script>

在模板种使用该组件:

<MyButton><template #right="{age}"><span>按钮右边 {{ age }} 岁</span></template><template v-slot="{ person }">这是按钮,{{ person }}</template><template #left><span>按钮左边</span></template>
</MyButton>

在 render 中使用该组件

import MyButton from './MyButton.vue'
export default {name: 'UseButton',render(h) {//NOTE h 第一个参数为 template 第二个参数里的 slot 属性指定插槽名称const slotLeft = h('template', { slot: 'left' }, '按钮左边')const slotRight = h('template', { slot: 'right' }, '按钮右边')const slotDefault = h('template', { slot: 'default' }, '默认插槽')const children = [slotLeft, slotDefault, slotRight]return h(MyButton, {}, children)},
}

在 render 中获取作用域插槽抛出的数据

import MyButton from './MyButton.vue'
export default {name: 'UseButton',render(h) {const slotLeft = h('template', { slot: 'left' }, '按钮左边')const children = [slotLeft]return h(MyButton,{scopedSlots: {default: props => {console.log(props)const { person } = propsconst text = `作用域插槽,${JSON.stringify(person)}`// 返回 h 创建的 VNodereturn h('span', {}, text)},right: props => {console.log(props)const { age } = props// 返回 jsxreturn <span>按钮右边 {age}</span>},},},children)},
}

总结

①. 普通命名插槽,使用h('template',{slot:'slotName'},children) 编写,然后放渲染组件的第三个参数里。

②. 作用域插槽在第二个参数的 scopedSlots 对象里,该对象的每个属性名是组件的插槽名,值是一个函数,参数为插槽绑定的数据。

使用 render 函数重写编写 MyButton

export default {name: 'MyButton',data() {return {person: {name: 'jack',age: 23,},}},render(h) {// NOTE default 关键字 不重命名 无法解构const { left, right, default: _defaultSlot } = this.$scopedSlots// NOTE 传递一个对象,在模板中使用解构取出属性const defaultSlot = _defaultSlot({ person: this.person })const leftSlot = left()const rightSlot = right(this.person)const button = h('button', {}, [defaultSlot])return h('div', {}, [leftSlot, button, rightSlot])},
}

返回 jsx

export default {name: 'MyButton',data() {return {person: {name: 'jack',age: 23,},}},render(h) {const { left, right, default: _defaultSlot } = this.$scopedSlots// NOTE 检查插槽是否存在const defaultSlot = (_defaultSlot && _defaultSlot({ person: this.person })) || <span>按钮</span>const leftSlot = (left && left()) || ''const rightSlot = right(this.person)const button = h('button', {}, [defaultSlot])//  返回 jsx 使得 dom 结构更加清晰return (<div>{leftSlot}{defaultSlot}{rightSlot}</div>)},
}

函数式组件:

export default {name: 'MyButton',functional: true,props: {person: {type: Object,default: () => ({ name: 'jack', age: 23 }),},},// NO DATA in functional component// data() {//   return {//     person: {//       name: 'jack',//       age: 23,//     },//   }// },render(h, { props, scopedSlots }) {const { left, right, default: _defaultSlot } = scopedSlotsconst defaultSlot = (_defaultSlot && _defaultSlot({ person: props.person })) || <span>按钮</span>const leftSlot = (left && left()) || ''const rightSlot = right(props.person)const button = h('button', {}, [defaultSlot])return (<div>{leftSlot}{button}{rightSlot}</div>)},
}

总结
①. 普通插槽、命名插槽、作用域插槽都通过 this.$scopedSlots 获取,它们都是返回 VNode 的函数。
②. 插槽绑定的数据通过插槽函数传递,基本数据使用 {} 包裹,方便在模板中解构。
③. 返回 jsx 能让 div 结构更加清晰。
④. 注意检查是否存在插槽,以启用后备内容。

指令
{directives: [{ name: 'directive-name', value: '2', expression: '1+1', arg: 'foo', modifiers: { foo: true } }]
}

在模板中定义指令

<template><!-- title 是名字, 指令的 value 由表达式计算出来 --><!-- v-title:argument.modifier1.modifier2="expression" --><div>在模板中编写指令<p v-title>这是简单指令</p><!-- 只能带一个参数 --><p v-title:argu>这是带参数的指令</p><!-- 动态参数 --><p v-title:[dynamicArgu()]>这是带动态参数的指令</p><p v-title:argu.foo.bar>这是带参数和修饰符的指令</p><p v-title:job.foo="data">这是带参数、修饰符和普通表达式的指令</p><p v-title:job.foo="expresFun">这是带参数、修饰符和函数表达式的指令</p></div>
</template><script>export default {name: 'Title',directives: {title: {inserted(el, bindings, vnode) {const { context: that } = vnodeconst { value = false } = bindingsif (typeof value === 'function') {that.setTile(el, value(that.data))} else {that.setTile(el, value)}},componentUpdated(el, bindings, vnode) {const { context: that } = vnodeconst { value = false } = bindingsif (typeof value === 'function') {that.setTile(el, value(that.data))} else {that.setTile(el, value)}},},},data() {return {data: { age: 23, job: 'web dev' },}},methods: {setTile(el, titleValue) {const textContent = el.textContentconst title = textContent.trim() || '暂无数据'el.title = typeof titleValue === 'string' ? titleValue : title},dynamicArgu() {return Math.random() > 0.5 ? 'argu1' : 'argu0'},expresFun(data) {return data.age + '岁'},},}
</script>

指令对象 bindings

指令对象

总结
不建议在 render 函数中编写指令,难以理解,指令需要在模板使用才能发挥其设计的目的。render 中可直接控制 DOM。

v-model 指令

使用 render 定义组件,如何提供 v-model

说明:
prop:–value + 使用 on 监听组件的事件,在处理函数中触发 input 自定义事件。

在 render 函数中使用 v-model 指令的处理有三种方案:

① . 在数据对象中使用 model 属性:

{model: {value: this.value,// value 是 data 里的属性callback: value => {// 可以再赋值之前做其他逻辑// 验证数据// 触发事件this.value = value}}
}

②. 传递 value + 监听 input 事件

{props: {// value 是 data 中的属性value: this.value},on: {input: value => {// 可做其他事情// 触发事件this.value = value}}
}

③. 在 jsx 中使用 vModel 属性

// input 是 data 中的属性
<MyInput vModel={this.input} />

三种方案的优缺点:

model 属性更加好,当表单项还有其他事件时,还可以在 on 中监听它们,比如 element 的下拉,有changeclear 等事件。

props value + input, 很符合 v-model 的语法糖。

jsx+ vModel 属性,简洁,常用。

其他属性
{key: 'v for 中的 key',ref:'模板变量',refInFor: true, // 循环中的 ref 是一个数组
}

使用 render 封装一个输入框

MyInput.jsx

import './my-input.css'
export default {name: 'MyInput',props: {// 需要实现 v-model 指令value: {type: [String, Number],default: '',},},render(h) {return h('input', {class: {'my-input': true,},style: {backgroundColor: '#ccc',},attrs: {id: 'my-input',class: 'a-my-input','data-key': 'key',},domProps: {value: this.value,},// 监听 input 的 input 事件on: {input: ({ target }) => {this.$emit('input', target.value)},},})},
}

说明
还可以使用 computed: domProp 的 value 接收一个计算属性,为该计算属性提供 setter 和 getter ,在 input 事件处理函数中设置计算属性的值,在 setter 中触发 自定义的 input 事件。这种方法不如上面的明白,代码量也多了。

在模板中使用该组件

<MyInput v-model="myInput" />

在 render 函数中使用

export default {name: 'UseInput',data() {return {input: '',}},render(h) {return h('div', {}, [h(MyInput, {model: {value: this.input,callback: value => {// 可在此做其他事件this.input = value},},}),h('h3', {}, this.input),])},
}

希望 UseInput,支持 v-model,即在二次封装 MyInput。

方案 1:添加 value props 在 model 中触发 input,删除 data 中的 input。

import MyInput from './my-input.jsx'
export default {name: 'UseInput',props: {value: { type: [String, Number], default: '' },},render(h) {return h('div', {}, [h(MyInput, {model: {value: this.value,callback: value => {// 可在此做其他事件this.$emit('input', value)},},}),h('h3', {}, this.value),])},
}

方案 2: 添加 value props,将其通过 props 传入 UseInput,监听 UseInput 的input事件,在此触发input事件。

import MyInput from './my-input.jsx'
export default {name: 'UseInput',props: {value: { type: [String, Number], default: '' },},render(h) {return h('div', {}, [h(MyInput, {props: {value: this.value,},on: {input: value => {this.$emit('input', value)},},}),h('h3', {}, this.value),])},
}

说明: 对于具有多种事件的表单项,比如 element 的下拉框,第一种方案更加好,on 属性留位置给从外传入的处理函数。

方案 3: jsx + vModel + prop value

import MyInput from './my-input.jsx'
export default {name: 'UseInput',props: {value: { type: [String, Number], default: '' },},data() {return {input: this.value,}},render(h) {return (<div><MyInput vModel={this.input} />{/* <h2>{this.input}</h2> */}</div>)},
}

** 注意**:这种方案不能实现双向绑定

其他问题

  1. 如何限制继承的属性,inheritAttrs 设置为 false,无法显示。

在模板定义的组件中,inheritAttrs 属性设置为 false, 除styleclass 以为的属性不会添加到根组件,实现手动控制。

render 定义的组件中,也是一样的。

参考

What does the ‘h’ stand for in Vue’s render method?

A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid

How to use v-model ( for custom input component) in render function?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/24702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VL830 USB4 最高支持40Gbps芯片功能阐述以及原理图分享

前文斥巨资拆了一个扩展坞供大家参考。其中核心即为本文要说的这个VL830,USB4的HUB芯片。 拆解报告传送门&#xff1a;USB4 Gen3x2 最高40Gbps传输速率的HUB扩展坞拆解分析 OK&#xff0c;闲话少叙。直接进入主题&#xff0c;我就直接翻译规格书了。 VL830是一款USB4端点设备…

C++笔试强训day41

目录 1.棋子翻转 2.宵暗的妖怪 3.过桥 1.棋子翻转 链接https://www.nowcoder.com/practice/a8c89dc768c84ec29cbf9ca065e3f6b4?tpId128&tqId33769&ru/exam/oj &#xff08;简单题&#xff09;对题意进行简单模拟即可&#xff1a; class Solution { public:int dx[…

设计模式-中介者(调停者)模式(行为型)

中介者模式 中介者模式是一种行为型模式&#xff0c;又叫调停者模式&#xff0c;它是为了解决多个对象之间&#xff0c;多个类之间通信的复杂性&#xff0c;定义一个中介者对象来封装一些列对象之间的交互&#xff0c;使各个对象之间不同持有对方的引用就可以实现交互&#xf…

连山露【诗词】

连山露 雾隐黄山路&#xff0c;十步一松树。 树上惊松鼠&#xff0c;松子衔木屋。 松子青嫩芽&#xff0c;尖尖头探出。 卷挂白露珠&#xff0c;装映黄山雾。

Java面试八股之什么是反射,实现原理是什么

Java中什么是反射&#xff0c;实现原理是什么 Java中的反射&#xff08;Reflection&#xff09;是一种强大的特性&#xff0c;它允许程序在运行时检查和操作类、接口、字段和方法的信息。简而言之&#xff0c;反射机制使得程序能够在运行时动态地了解和使用自身或其他程序集中…

LDR6020一拖二快充线:高效充电的新选择

LDR6020一拖二快充线&#xff1a;高效充电的新选择 随着移动设备的普及和功能的日益增强&#xff0c;电池续航成为了用户关注的重点之一。为了满足用户对于快速充电的需求&#xff0c;各大厂商纷纷推出了各种快充技术和产品。在这个背景下&#xff0c;LDR6020一拖二快充线凭借…

Facebook与AI:探索人工智能在社交平台上的应用

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;社交媒体平台正利用这些先进技术为用户提供更为个性化和高效的体验。作为全球最大的社交媒体平台之一&#xff0c;Facebook在AI应用领域的探索和实践尤为引人注目。本文将深入探讨Facebook如何在其平台上应用…

Linux--标准IO库

一、标准IO简介 所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作&#xff08;譬如读文件、写文件等&#xff09;相关的一系列库函数的集合&#xff0c;通常标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中&#xff0c;所以我们需要在程序源码中包含 <s…

图片和PDF展示预览、并支持下载

需求 展示图片和PDF类型&#xff0c;并且点击图片或者PDF可以预览 第一步&#xff1a;遍历所有的图片和PDF列表 <div v-for"(data,index) in parerFont(item.fileInfo)" :key"index" class"data-list-item"><downloadCard :file-inf…

Java学习54-关键字this的使用

this是什么 this的作用&#xff1a; 它在方法(准确的说是实例方法或非static的方法)内部使用&#xff0c;表示调用该方法的对象 它在构造器内部使用&#xff0c;表示该构造器正在初始化的对象 this可以调用的结构&#xff1a;成员变量、方法和构造器 什么时候使用this 实…

83页 | 2024数据安全典型场景案例集(免费下载)

以上是资料简介和目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a;

深度学习——卷积神经网络(CNN)

深度学习 深度学习就是通过多层神经网络上运用各种机器学习算法学习样本数据的内在规律和表示层次&#xff0c;从而实现各种任务的算法集合。各种任务都是啥&#xff0c;有&#xff1a;数据挖掘&#xff0c;计算机视觉&#xff0c;语音识别&#xff0c;自然语言处理等。‘ 深…

【ARM Cache 与 MMU 系列文章 7.6 -- ARMv8 MMU 配置 寄存器使用介绍】

请阅读【ARM Cache 及 MMU/MPU 系列文章专栏导读】 及【嵌入式开发学习必备专栏】 文章目录 MMU 转换控制寄存器 TCR_ELxTCR_ELx 概览TCR_ELx 寄存器字段详解TCR 使用示例Normal MemoryCacheableShareability MMU 内存属性寄存器 MAIR_ELxMAIR_ELx 寄存器结构内存属性字段Devic…

TiDB-从0到1-配置篇

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇 一、系统配置 TiDB的配置分为系统配置和集群配置两种。 其中系统配置对应TiDB Server&#xff08;不包含TiKV和PD的参数&#xff0…

利用GPT和PlantUML快速生成UML图用于设计

在软件开发中&#xff0c;设计阶段可是关键的一步。UML&#xff08;统一建模语言&#xff09;图能帮我们更清晰地理解和规划系统结构&#xff0c;但手动画UML图有时会很费时费力。好消息是&#xff0c;通过结合使用ChatGPT和PlantUML&#xff0c;我们可以高效地生成UML图&#…

STM32_HAL库_外部中断

一、设置分组 stm32f1xx_hal_cortex.c 查看分组 五个形参&#xff0c;分组0~4 stm32f1xx_hal.c 设置了分组为2&#xff0c; 此工程就不需要再设置了 再回到stm32f1xx_hal_cortex.c 查看NVIC_SetPriorityGrouping的定义&#xff0c;若无法跳转&#xff0c;先编译一下&…

山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(二十七)- 微服务(7)

11.1 : 同步调用的问题 11.2 异步通讯的优缺点 11.3 MQ MQ就是事件驱动架构中的Broker 安装MQ docker run \-e RABBITMQ_DEFAULT_USERxxxx \-e RABBITMQ_DEFAULT_PASSxxxxx \--name mq \--hostname mq1 \-p 15672:15672 \-p 5672:5672 \-d \rabbitmq:3-management 浏览器访问1…

在网上赚钱,可以自由掌控时间,灵活的兼职副业选择

朋友们看着周围的人在网上赚钱&#xff0c;自己也会为之心动&#xff0c;随着电子设备的普及&#xff0c;带动了很多的工作、创业以及兼职副业选择的机会&#xff0c;作为普通人的我们&#xff0c;如果厌倦了世俗的朝九晚五&#xff0c;想着改变一下自己的生活&#xff0c;可以…

uc/OS移植到stm32实现三个任务

文章目录 一、使用CubeMX创建工程二、uc/OS移植三、添加代码四、修改代码五、实践结果六、参考文章七、总结 实践内容 学习嵌入式实时操作系统&#xff08;RTOS&#xff09;,以uc/OS为例&#xff0c;将其移植到stm32F103上&#xff0c;构建至少3个任务&#xff08;task&#xf…

就业班 第四阶段(k8s) 2401--6.4 day2 Dashboard+国产kuboard(好用)+简单命令

可视化部署Dashboard 昨天做一主两从飞高可用&#xff0c;出现浏览器那一行&#xff0c;是为啥 thisisunsafe kubectl get 获取资源 pod node svc -A 所有名称空间 -n 指定名称空间 -w 动态显示 kubectl edit 资源 pod node svc 官方的&#xff0c;毛坯房 国产 在哪找的…