1. vue2.x的 h 函数(createElement)
- 使用方法及介绍:(参考官网提取)
- h函数第一个是标签名字 或者是组件名字,第二个参数是配置项,第三个参数是 innerText ,不会帮你转换节点,如果需要转换成节点(v-html)请去第二个参数中的 domProps 配置 innerHTML
- 当第二个参数是字符串的时候则会直接当 innerText 渲染(相当于配置项参数为空对象)【h(‘span’, ‘姓名’)】
- 第三个参数如果为字符串的时候可以理解为默认值!(innerText的默认值)如果同时设置了这两个则配置中的权重更高
- 第三个参数也是该元素的子集合、插槽设置的地方。
- 温馨提示:vue2.x的h函数跟vue3.x的有点不一样,第二个参数配置项格式变了,第三个参数为函数返回,具体情况看本文第二要点
- 以下为常见的常规配置
import SelectEdit from'./SelectEdit'exportdefault{data(){return{name:''}},render(h){// 如果使用原生的则// return h('div', {// 这个是挂载组件returnh(SelectEdit,{// 此处是给 SelectEdit 组件传值的(props传值)props:{value:1,type:'on'},// class可以数组的形式也可以对象的形式// class: ['speci-class'],class:{'speci-class':true},// 样式有-的注意小驼峰 或者使用 string 模式style:{color:'red',fontSize:'14px',// 或者这样'font-size':'14px'},// 普通的 HTML attributeattrs:{placeholder:'这是给原生html赋值placeholder属性'},// DOM propertydomProps:{innerHTML:'DOM property',// 这个参数等同于h函数的第三个参数innerText:'xxxxxxx'},// 这里是挂载方法的但不再支持如 `v-on:keyup.enter` 这样的修饰器on:{// 方法名可以自定义(组件内 $emit('xxxchange', {name: 'zs'}))'xxxchange':val=>{this.name = val.name;},'click':val=>{this.name = val.name;},},// 仅用于组件,用于监听原生事件,而不是组件内部使用// `vm.$emit` 触发的事件。nativeOn:{click:this.nativeClickHandler
},// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`directives:[{name:'my-custom-directive',value:'2',expression:'1 + 1',arg:'foo',modifiers:{bar:true}}],// 作用域插槽的格式为scopedSlots:{default:props=>createElement('span', props.text)},// 如果组件是其它组件的子组件,需为插槽指定名称slot:'name-of-slot',// 其它特殊顶层 propertykey:'myKey',ref:'myRef',// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,// 那么 `$refs.myRef` 会变成一个数组。refInFor:true},'这里是显示文本')}}
- 举例子了:如果你想要实现以下效果(v-model)
<div class="row zs active info" name="zs"><span style="background-color: red;font-size: 16px;" @click="handleName">姓名:</span><i >{{ name }}</i><input v-model="name"/></div>
目测没问题
data(){return{name:''}},render(h){returnh('div',{class:['row zs','active','info'],attrs:{name:'zs'}},[h('span',{style:{backgroundColor: red,'font-size':'16px'},on:{click: handleName
}},'姓名:'),h('i','张三'),h('input',{domProps:{value:this.name
},on:{input:function(event){this.$emit('input', event.target.value)}}})])}
2. vue3 h函数配置项
- 与2.x相比,第一个参数格式没有更变,第二个参数格式更变了,第三个参数变为建议使用函数返回了
- 第三个参数如果为字符串的时候可以理解为默认值!(innerText的默认值)如果同时设置了这两个则配置中的权重更高
- 第三个参数也是该元素的子集合、插槽设置的地方。
- 第三个参数不使用函数会有一个vue警告(所以说直接函数吧)
- 具体使用如下:
import{ h }from'vue';import{ ElButton }from'element-plus'h(
ElButton,{type:'primary',innerText:'修改11',onClick:()=>{
console.log(11);}},()=>'修改')
2.1 v-model实现(以下开始为官网实现)
props:['modelValue'],emits:['update:modelValue'],render(){returnh(SomeComponent,{modelValue:this.modelValue,'onUpdate:modelValue':value=>this.$emit('update:modelValue', value)})}
自己搞忘记后重新弄的踩坑记录:
- 双向绑定实现:2.x中是value,但是到了3.x中不是value了而是modelValue
- onUpdate:modelValue 相当于就是 v-model,只不过这个变成了函数,在这个函数里面需要你自己给绑定元素赋值。不赋值则会出现双向绑定失效的问题!
2.2 v-on
给定有效的事件名称,例如(onClick, onChange)或自定义的名称
render(){returnh('div',{onClick:$event=> console.log('clicked', $event.target)})}
2.3 事件修饰符
对于 .passive 、.capture 和 .once 事件修饰符,可以使用驼峰写法将他们拼接在事件名后面:
render(){returnh('input',{onClickCapture:this.doThisInCapturingMode,onKeyupOnce:this.doThisOnce,onMouseoverOnceCapture:this.doThisOnceInCapturingMode
})}
对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:
这里是一个使用所有修饰符的例子:
render(){returnh('input',{onKeyUp:event=>{// 如果触发事件的元素不是事件绑定的元素// 则返回if(event.target !== event.currentTarget)return// 如果向上键不是回车键,则终止// 没有同时按下按键 (13) 和 shift 键if(!event.shiftKey || event.keyCode !==13)return// 停止事件传播
event.stopPropagation()// 阻止该元素默认的 keyup 事件
event.preventDefault()// ...}})}
2.4 插槽
你可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:
render(){// `<div><slot></slot></div>`returnh('div',{},this.$slots.default())}
props:['message'],render(){// `<div><slot :text="message"></slot></div>`returnh('div',{},this.$slots.default({text:this.message
}))}
渲染函数将插槽传递给子组件
render(){// `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`returnh('div',[h(resolveComponent('child'),{},// 将 `slots` 以 { name: props => VNode | Array<VNode> } 的形式传递给子对象。{default:(props)=>h('span', props.text)})])}
插槽以函数的形式传递,允许子组件控制每个插槽内容的创建。任何响应式数据都应该在插槽函数内访问,以确保它被注册为子组件的依赖关系,而不是父组件。相反,对 resolveComponent 的调用应该在插槽函数之外进行,否则它们会相对于错误的组件进行解析。
// `<MyButton><MyIcon :name="icon" />{{ text }}</MyButton>`render(){// 应该是在插槽函数外面调用 resolveComponent。const Button =resolveComponent('MyButton')const Icon =resolveComponent('MyIcon')returnh(
Button,null,{// 使用箭头函数保存 `this` 的值default:(props)=>{// 响应式 property 应该在插槽函数内部读取,// 这样它们就会成为 children 渲染的依赖。return[h(Icon,{name:this.icon }),this.text
]}})}
如果一个组件从它的父组件中接收到插槽,它们可以直接传递给子组件。
render(){returnh(Panel,null,this.$slots)}
也可以根据情况单独传递或包裹住。
render(){returnh(
Panel,null,{// 如果我们想传递一个槽函数,我们可以通过header:this.$slots.header,// 如果我们需要以某种方式对插槽进行操作,// 那么我们需要用一个新的函数来包裹它default:(props)=>{const children =this.$slots.default ?this.$slots.default(props):[]return children.concat(h('div','Extra child'))}})}
2.5 component 和 is
在底层实现里,模板使用 resolveDynamicComponent 来实现 is attribute。如果我们在 render 函数中需要 is 提供的所有灵活性,我们可以使用同样的函数:
const{ h, resolveDynamicComponent }= Vue
// ...// `<component :is="name"></component>`render(){const Component =resolveDynamicComponent(this.name)returnh(Component)}
就像 is, resolveDynamicComponent 支持传递一个组件名称、一个 HTML 元素名称或一个组件选项对象。
通常这种程度的灵活性是不需要的。通常 resolveDynamicComponent 可以被换做一个更直接的替代方案。
例如,如果我们只需要支持组件名称,那么可以使用 resolveComponent 来代替。
如果 VNode 始终是一个 HTML 元素,那么我们可以直接把它的名字传递给 h:
// `<component :is="bold ? 'strong' : 'em'"></component>`render(){returnh(this.bold ?'strong':'em')}
同样,如果传递给 is 的值是一个组件选项对象,那么不需要解析什么,可以直接作为 h 的第一个参数传递。
与 < template > 标签一样,< component > 标签仅在模板中作为语法占位符需要,当迁移到 render 函数时,应被丢弃。
2.6 自定义指令
可以使用 withDirectives 将自定义指令应用于 VNode:
const{ h, resolveDirective, withDirectives }= Vue
// ...// <div v-pin:top.animate="200"></div>render(){const pin =resolveDirective('pin')returnwithDirectives(h('div'),[[pin,200,'top',{animate:true}]])}
resolveDirective 是模板内部用来解析指令名称的同一个函数。只有当你还没有直接访问指令的定义对象时,才需要这样做
2.7 内置组件
诸如 < keep-alive >、< transition >、< transition-group > 和 < teleport > 等内置组件默认并没有被全局注册。这使得打包工具可以 tree-shake,因此这些组件只会在被用到的时候被引入构建。不过这也意味着我们无法通过 resolveComponent 或 resolveDynamicComponent 访问它们。
在模板中这些组件会被特殊处理,即在它们被用到的时候自动导入。当我们编写自己的 render 函数时,需要自行导入它们:
const{ h, KeepAlive, Teleport, Transition, TransitionGroup }= Vue
// ...render(){returnh(Transition,{mode:'out-in'},/* ... */)}
2.8 渲染函数的返回值
在我们目前看过的所有示例中,render 函数返回的是单个根 VNode。但其实也有别的选项。
返回一个字符串时会创建一个文本 VNode,而不被包裹任何元素:
render(){return'Hello world!'}
我们也可以返回一个子元素数组,而不把它们包裹在一个根结点里。这会创建一个片段 (fragment):
// 相当于模板 `Hello<br>world!`render(){return['Hello',h('br'),'world!']}
可能是因为数据依然在加载中的关系,组件不需要渲染,这时它可以返回 null。这样我们在 DOM 中会渲染一个注释节点
2.9 JSX
如果你写了很多渲染函数,可能会觉得下面这样的代码写起来很痛苦:
h('anchored-heading',{level:1},{default:()=>[h('span','Hello'),' world!']})
特别是对应的模板如此简单的情况下:
<anchored-heading:level="1"><span>Hello</span> world! </anchored-heading>
这就是为什么会有一个 Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。
import AnchoredHeading from'./AnchoredHeading.vue'const app =createApp({render(){return(<AnchoredHeading level={1}><span>Hello</span> world!</AnchoredHeading>)}})
app.mount('#demo')
有关 JSX 如何映射到 JavaScript 的更多信息,请参阅使用文档 。
参考链接
- vue2.x官网渲染函数
- vue3.x官网dom-树
- vue3.x官网渲染函数
- 本文仅做于笔记
版权归原作者 闲鱼_JavaScript 所有, 如有侵权,请联系我们删除。