# 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的组件,主要是 modelValueupdate: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']
  ]);
};
上次更新: 1/22/2025, 9:39:13 AM