How to use render function in <script setup> Vue3
Asked Answered
P

4

33

I use Vue 3.1.1

I am using script setup in the experimental stage with single file components. Using the script setup, I understand defineProps, defineEmit, and useContext, but I don't understand how to use the render function.

<script lang="ts" setup>
import { defineProps } from 'vue'

const props = defineProps<{
    text: string
}>()

const handleClick = () => {
    console.log('click!!')
}

// Render function...

/* The template I want to create.
    <button
        class="btn btn-primary"
        type="button"
        @click="handleClick"
    >
        {{ props.text }}
    </button>
*/
</script>
Pyrogallate answered 10/6, 2021 at 9:23 Comment(0)
M
30

try it.

<script lang="tsx" setup>
  import { h } from 'vue';

  const render = () => {
    return h('div', []);
  };

  const jsxNode = () => {
    return <div> text </div>;
  };
</script>
<template>
  <render />
  <jsxNode />
</template>

Mosenthal answered 29/11, 2021 at 8:45 Comment(2)
HMR doesn't work in this form when you change code inside render or jsxNodeAtherton
This answer is only a special implementation, and does not involve what effect HMR will show. But whether or not HMR is effective, this method should not be used in preference, because it will make the code less readable. Moreover, the appearance of setup script is to solve the troublesomeness that setup() needs to be exported. If your component doesn't need <template>, you don't need setup script at all, you should use jsx/tsx fileMosenthal
G
10

Try to use the h function to create your element then render it inside the template section as follows :

<script setup lang="ts">
import { ref,h } from 'vue'

const props = defineProps<{
    text: string
}>()

const handleClick = () => {
    console.log('click!!')
}

const root=h('button',
             {type:'button',onClick:handleClick,class:'btn btn-primary'},props.text)

</script>

<template>
  <root/>             
</template>

DEMO

Geopolitics answered 10/6, 2021 at 9:34 Comment(6)
This does not work.Slaty
@RafA. can you show how have you tried it?Geopolitics
Exactly like the that, except it was JS and not TS. Well I should probably state that this "kind of" works, but it does not convert dynamic components. So if you write in that h() innerHTML: "<router-link>...." - it will not work.Slaty
Do you mean that you wrote h('router-link',...)?Geopolitics
For component try to import it then use it as first parameter, import {RouterLink} from 'vue-router'; and h(RouterLink,...)Geopolitics
Works but the root component is not reactive. For reactivity you must do const root =computed(()=>h(...))Brouwer
G
8

You can assign the render function directly to your component instance via getCurrentInstance

Custom hook

// useRender.ts

import type { VNode } from 'vue';
import { getCurrentInstance } from 'vue';

export function useRender(render: () => Arrayable<VNode | null>): void {
    const vm = getCurrentInstance();

    if (!vm) {
        throw new Error('[useRender] must be called from inside a setup function');
    }

    /**
     * In development mode, assignment render property works fine
     * but in production SFC overwrites it with an empty function
     * because no <template> section defined.
     *
     * Filthy hack to avoid this in production.
     * https://github.com/vuejs/core/issues/4980
     */
    if (import.meta.env.DEV) {
        (vm as any).render = render;
    } else {
        Object.defineProperty(vm, 'render', {
            get: () => render,
            set: () => {},
        });
    }
}

Usage

<script setup>
import { h } from 'vue';

import { useRender } from './useRender';

useRender(() => h('span', 'Hello from script setup'));
</script>
Gelb answered 6/12, 2022 at 13:4 Comment(0)
O
-4

You may try an extra normal script.

<script lang="tsx" setup>
import { defineProps, defineExpose } from 'vue'

const props = defineProps<{
    text: string
}>()

const handleClick = () => {
    console.log('click!!')
}
defineExpose({
  handleClick,
  // other data,method
})
</script>

<script lang="tsx">
import { defineComponent } from 'vue'

export default defineComponent({
  render() {
    return (
      <button
        class="btn btn-primary"
        type="button"
        onClick={this.handleClick}
      >
        {{ this.text }}
      </button>
    )
  }
})
</script>

Any data, methods except the props should be exposed using defineExpose.

BTW, setup script and normal script should have the same lang attribute.

Oday answered 28/9, 2021 at 7:51 Comment(1)
this doesn't work in production mode.Voodooism

© 2022 - 2024 — McMap. All rights reserved.