# Vue3 渲染函数

import { h } from 'vue 关于Vue3 setup返回render的一些细节学习。

文档戳这里哦 (opens new window)

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)

上次更新: 1/22/2025, 9:39:13 AM