# vue@3.4.10 版本的渲染函数内容
[toc]
# 1 渲染基础
下面的代码基本包含了所有的基础知识点,配合API文档即可
<script lang="ts">
import { defineComponent, h, onMounted, ref, resolveComponent, withDirectives, withModifiers } from 'vue';
import type { Directive, ObjectDirective, DirectiveBinding } from 'vue';
import ChildA from './ChildA.vue';
import ChildB from './ChildB.vue';
import ChildC from './ChildC.vue';
import MyInput from './MyInput.vue';
import MyInput2 from './MyInput2.vue';
const UlStyle = {
border: '1px solid green',
width: '200px'
};
const color = {
mounted: (el: HTMLElement, binding: DirectiveBinding<string>) => {
if (binding.value) {
el.style.color = binding.value;
}
}
};
const fontSize = {
mounted: (el: HTMLElement, binding: DirectiveBinding<string>) => {
if (binding.value) {
el.style.fontSize = binding.value + 'px';
}
}
};
export default defineComponent({
name: 'RenderH',
props: {
hello: String
},
// emits: [],
// components: {},
// directives: {
// color: color
// },
setup(props, context) {
// 渲染列表 v-for
const list = ref<number[]>([0, 1, 2, 3]);
const renderList = () => {
return h(
'ul',
{
style: UlStyle,
class: ['ul-wrapper']
},
list.value.map((item, index) => {
return h(
'li',
{
key: item, // 记得添加key属性
'data-index': index
},
item
);
})
);
};
// 条件渲染 v-if
const show = ref(false);
const renderTitle = () => (show.value ? h('h3', '条件渲染v-if') : null);
setTimeout(() => {
show.value = true;
}, 1000);
// 条件渲染 v-show
const show2 = ref(false);
const renderTitle2 = () =>
h(
'h4',
{
style: {
display: show2.value ? '' : 'none'
}
},
'条件渲染v-show'
);
setTimeout(() => {
show2.value = true;
}, 1000);
// 鼠标事件v-on
const renderBtn = () =>
h(
'div',
{
onClick: () => {
console.log('click div');
}
},
[
h(
'button',
{
// onClick
// onClickCapture 使用修饰符
onDblclick: () => {
show2.value = !show2.value;
console.log('click btn');
},
// 使用 withModifiers 也可以应用修饰符
/* onClick: withModifiers(() => {
show2.value = !show2.value;
console.log('click btn withModifiers');
}, ['stop']) */
onClick: (e: Event) => {
e.stopPropagation();
e.preventDefault();
console.log(e.currentTarget);
show2.value = !show2.value;
console.log('click btn withModifiers');
}
},
'显隐标题' + show2.value
)
]
);
// 渲染组件
const renderComponent = () =>
h(ChildA, {
msg: '消息',
foo: 1
});
// 渲染全局注册组件
const renderGlobalComponent = () => {
return h(
resolveComponent('my-button'),
{
onClick: withModifiers(
(e: Event) => {
console.log(e);
},
['native']
)
},
{
default: () => '点击我'
}
);
};
// 关于插槽
const renderSlots = () =>
h(
ChildB,
{},
{
header: (msg: string) => h('h4', {}, msg),
default: (num: number) => h('div', num),
footer: (obj: { footerMsg: string }) => h('div', obj.footerMsg)
}
);
const renderSlots2 = () =>
h(ChildC, null, {
header: (msg: { message: string }) => h('div', null, [h('pre', {}, JSON.stringify(msg, null, 2)), h('p', null, msg.message)])
});
const myName = ref('xdyuan');
const renderCustomInput = () =>
h('div', null, [
h('p', null, 'myName = ' + myName.value),
h(MyInput, {
modelValue: myName.value,
'onUpdate:modelValue': (val: string) => {
myName.value = val;
}
})
]);
const myName2 = ref('');
const renderCustomInput2 = () =>
h('div', null, [
h('p', null, 'myName2 = ' + myName2.value),
h(MyInput2, {
modelValue: myName2.value,
'onUpdate:modelValue': (val: string) => {
myName2.value = val;
}
})
]);
// 渲染自定义指令
const renderCustomDirective = () => {
// <div v-pin:top.animate="200"></div>
// const vnode = withDirectives(h('div'), [[pin, 200, 'top', { animate: true }]]);
return withDirectives(h('p', null, 'my color is red'), [
[color, 'red'],
[fontSize, '20']
]);
};
// 模板引用
const divRef = ref(null);
const renderDivRef = () => h('div', { ref: divRef }, 'test ref 模板引用');
onMounted(() => {
if (divRef.value) {
(<HTMLDivElement>divRef.value).style.color = 'green';
(divRef.value as HTMLDivElement).style.color = 'green';
}
});
return () =>
h('div', {}, [
h('h4', 'render function'),
renderList(),
renderTitle(),
renderTitle2(),
renderBtn(),
renderComponent(),
renderGlobalComponent(),
renderSlots(),
renderSlots2(),
renderCustomInput(),
h('hr'),
renderCustomInput2(),
renderCustomDirective(),
renderDivRef()
]);
}
});
</script>
<style scoped lang='stylus'>
.ul-wrapper {
padding: 12px;
li:nth-child(2n) {
color: red;
}
}
</style>
# 2 渲染插槽
# 1、使用Render函数消费插槽,提供回参,定义 slots类型
下面是一个子组件, 再调用父组件提供的slot回调
<script lang="ts">
// ChildB.vue
import { defineComponent, h, ref, SlotsType } from 'vue';
export default defineComponent({
name: 'ChildB',
// props: {},
// emits: [],
slots: Object as SlotsType<{
default: number;
header: string;
footer: { footerMsg: string };
}>,
// components: {},
setup(props, context) {
const message = ref('hello childB');
const footerMsg = '底部消息';
const mainContent = 1234;
return () =>
h(
'div',
{
style: {
border: '2px dashed red'
}
},
[
h('header', {}, context.slots.header(message.value)),
h('main', {}, context.slots.default(mainContent)),
h('footer', {}, context.slots.footer({ footerMsg: footerMsg }))
]
);
}
});
</script>
<style scoped lang='stylus'></style>
提供插槽函数,可看做父组件
const renderSlots = () =>
h(
ChildB,
{},
{
header: (msg: string) => h('h4', {}, msg),
default: (num: number) => h('div', num),
footer: (obj: { footerMsg: string }) => h('div', obj.footerMsg)
}
);
# 2、使用模板消费插槽
下面是子组件的代码
<template>
<main>
<header>
<slot name="header"
message="header message"></slot>
</header>
</main>
</template>
提供插槽内容,下面是父组件的代码
const renderSlots2 = () =>
h(ChildC, null, {
header: (msg: { message: string }) => h('div', null, [
h('pre', {},JSON.stringify(msg, null, 2)),
h('p', null, msg.message)
])
});
# 3 渲染函数 v-model逻辑
使用渲染函数编写一个支持自定义v-model的组件,主要是 modelValue
和 update:modelValue
<script lang="ts">
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'MyInput',
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue'],
// slots: Object as SlotsType<>,
// components: {},
setup(props, context) {
return () =>
h('div', null, [
h('input', {
value: props.modelValue,
style: 'border: none;outline: none;border: 2px solid red;',
onInput: (e: Event) => {
const val = (e.target as HTMLInputElement).value;
context.emit('update:modelValue', val);
}
})
]);
}
});
</script>
<style scoped lang='stylus'></style>
使用setup语法糖 defineModel 编写自定义input组件
<template>
<div>
<input v-model="model"
type="text">
</div>
</template>
<script lang="ts" setup>
// import { ref } from 'vue'
defineOptions({ name: 'MyInput2' });
const model = defineModel({ default: '' });
</script>
使用render函数渲染这个支持v-model的组件
const myName = ref('xdyuan');
const renderCustomInput = () =>
h('div', null, [
h('p', null, 'myName = ' + myName.value),
h(MyInput, {
modelValue: myName.value,
'onUpdate:modelValue': (val: string) => {
myName.value = val;
}
})
]);
# 4 渲染自定义指令
import { defineComponent, h, ref, resolveComponent, withDirectives, withModifiers } from 'vue';
import type { Directive, ObjectDirective, DirectiveBinding } from 'vue';
const color = {
mounted: (el: HTMLElement, binding: DirectiveBinding<string>) => {
if (binding.value) {
el.style.color = binding.value;
}
}
};
const fontSize = {
mounted: (el: HTMLElement, binding: DirectiveBinding<string>) => {
if (binding.value) {
el.style.fontSize = binding.value + 'px';
}
}
};
const renderCustomDirective = () => {
// <div v-pin:top.animate="200"></div>
// const vnode = withDirectives(h('div'), [[pin, 200, 'top', { animate: true }]]);
return withDirectives(h('p', null, 'my color is red'), [
[color, 'red'],
[fontSize, '20']
]);
};