# Vue3 渲染函数
import { h } from 'vue
关于Vue3 setup返回render的一些细节学习。
h函数就是以前的createElement
, 返回一个VNode。 如果是optinos api我们可以使用render选项, 如果是setup可以返回渲染函数
<script lang="ts">
import { h } from 'vue';
export default {
name: 'Render2',
render() {
return h('h1', {}, 'render function');
}
};
</script>
也可以在setup里面使用, 我们就学如何在setup使用
<script lang="ts">
import { h } from 'vue';
export default {
name: 'Render2',
setup() {
return () => h('h1', {}, 'render function');
}
};
</script>
文档里都比较详细, 我们就实践一些比较麻烦的功能, 做一个尝试.
如果遇到这个警告, 应该的原因是Vue希望你在渲染自定组件时, 第三个参数是一些插槽内容, 这时候最好提供一个函数, 其返回值作为默认插槽的内容。 可以尝试把h的第三个参数使用下面的方式 () => '标题1'
WARNING
[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.
setup(props: Props) {
return () =>
h('div', null, [h(CustomTitle, { level: '1' }, () => '标题1')]);
}
// 或者 两者是等价的
setup(props: Props) {
return () =>
h('div', null, [h(CustomTitle, { level: '1' }, {
default: () => '标题1')
}]);
}
# 插槽Slots
子组件渲染插槽内容 通过SetupContext.slots 获取到插槽, default是默认插槽,也可以传递一些具名插槽, 作用域插槽
<script lang="ts">
import { h, ref, SetupContext } from 'vue';
interface Props {
level: string;
}
export default {
name: 'CustomTitle',
props: {
level: {
type: String,
required: true,
default: ''
}
},
// components: {},
setup(props: Props, context: SetupContext) {
// context.slots.default
const subTitle = ref('我是SubTitle');
setTimeout(() => {
subTitle.value = 'updated SubTitle';
}, 1000);
return () =>
h('h' + props.level, {}, [
h('span', { class: 'text-green-400' }, '前缀内容'),
// 渲染默认插槽的内容
typeof context.slots.default === 'function'
? context.slots.default()
: null,
// 渲染具名插槽的内容, 可以通过判断的方式添加插槽的备用内容
typeof context.slots.suffix === 'function'
? context.slots.suffix()
: '备用内容',
// 作用域插槽
typeof context.slots.subTitle === 'function'
? context.slots.subTitle({
text: subTitle.value
})
: '备用内容'
]);
}
};
</script>
父组件传递插槽
<script lang="ts">
import { h } from 'vue';
import CustomTitle from './components/CustomTitle.vue';
interface Props {}
export default {
name: 'Render',
// props: {},
// components: {},
setup(props: Props) {
return () =>
h('div', null, [
h(
CustomTitle,
{ level: '2' },
{
default: () => '标题1',
suffix: () => h('span', { class: 'text-red-600' }, '后缀内容'),
// 访问子组件传递过来的内容
subTitle: (props: { readonly text?: string }) =>
h('span', {}, props.text)
}
)
]);
}
};
</script>
# 渲染一个全局注册组件
需要借助 resolveComponent
函数
import { resolveComponent } from 'vue';
h(resolveComponent('MyHeader'), {
class: 'text-yellow-500',
title2: 'Header from render'
})
# 使用js代替模板功能
# v-if 、 v-for
这个很简单, 使用if/else、map、for循环就行,工厂函数循环生成VNode时, 最好还是可以加上key属性
const list = reactive<number[]>([1, 2, 3, 4, 5]);
const listVNode = () =>
h(
'ul',
{ class: 'bg-gray-300' },
list.map((num) => h('li', { key: num.toString() }, num))
);
const showMore = ref<boolean>(false);
const showMoreBtn = () =>
showMore.value ? h('button', { class: 'bg-red-500' }, 'show more') : null;
setTimeout(() => {
showMore.value = true;
list.push(100);
}, 2000);
如果是单独拎出来一个VNode, 可以采用工厂函数的方式, 这样子可以收集到依赖
# 渲染表单
const email = ref('');
h('input', {
class: {
'border-2': true,
'border-gray-500': true,
'border-solid': true
},
value: email.value,
onInput(e: Event) {
email.value = (e.target as HTMLInputElement).value;
}
}),
# v-model
这里其实传入什么样子的值都行了, 不过为了自定义Input组件兼容模板的写法, 还是取名modelValue
, onUpdate:modelValue
合适。
h(MyInput, {
modelValue: psd.value,
'onUpdate:modelValue': (value: string) => {
console.log(value);
psd.value = value;
}
}),
# v-on 事件处理
h(
'button',
{
class: { 'bg-red-300': true },
title: 'click me',
onClick: (e: Event) => {
// 这边需要断言一下 e.target
console.log('cc', (e.target as HTMLButtonElement).textContent);
},
onDblclick: (e: Event) => {
e.stopPropagation();
e.preventDefault();
console.log('onDblclick');
}
},
'click me'
),
# 内置组件
# <keep-alive>
import { ref, h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue';
export default {
name: 'KeepAliveDemo',
// props: {},
// components: {},
setup(props: Props) {
const trigger = ref(true);
return () =>
h('div', null, [
h('p', null, '==================keep-alive start======'),
h(KeepAlive, { max: 5 }, [trigger.value ? h(MyInput) : h(DomEvent)]),
h(
'button',
{
class: 'bg-red-100',
onClick: () => {
trigger.value = !trigger.value;
}
},
'trigger'
),
h('p', null, '==================keep-alive end=========')
]);
}
};
其他的可以查看文档内置组件 (opens new window)
# 函数式组件
const FunctionalComponent = (props: { message: string }) => {
return h(
'div',
{
style: { color: 'red' },
},
'FunctionalComponent ' + props.message
);
};
用法h(FunctionalComponent)