How to use render function in <script setup> Vue3 - javascript

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>

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>

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

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>

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.

Related

Vue3: why a componment instance template ref unable to find defined function

LoginAccount.vue
<script setup lang="ts">
import { rules } from './config/AccountConfig'
import { reactive } from 'vue'
import { ref } from 'vue';
import { ElForm } from 'element-plus';
const account = reactive({
name: '',
password: ''
})
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = () => {
console.log("开始登录")
formRef.value?.validate((valid) => {
if (valid) {
console.log("登录逻辑")
}
})
}
// 规则
</script>
LoginPanel
<script setup lang="ts">
import LoginAccount from './LoginAccount.vue'
import LoginPhone from './LoginPhone.vue'
import { ref } from 'vue'
const isKeepPassword = ref(false);
const accountRef = ref<InstanceType<typeof LoginAccount>>()
const handleLoginClick = () => {
console.log("立即登录")
accountRef.value?.loginAction() No property exists " loginAction"
}
</script>
error
How do I type the imported component, so that when I call its methods through refs, it checks exposed method names and types of passed arguments?
accountRef.value?.loginAction() No property exists " loginAction"
how to solve it?
Docs
Components using <script setup> are closed by default - i.e. the public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside <script setup>
To explicitly expose properties in a <script setup> component, use the defineExpose compiler macro
<script setup lang="ts">
import { rules } from './config/AccountConfig'
import { reactive } from 'vue'
import { ref } from 'vue';
import { ElForm } from 'element-plus';
const account = reactive({
name: '',
password: ''
})
const formRef = ref<InstanceType<typeof ElForm>>()
const loginAction = () => {
console.log("开始登录")
formRef.value?.validate((valid) => {
if (valid) {
console.log("登录逻辑")
}
})
}
// 规则
defineExpose({
loginAction,
})
</script>

Why is this error not letting me async render Vue Components?

I'm quite new with Vue, and I'm trying to lazy load a Component, I'm getting this error which I don't understand, there should probably be a syntax error.
Here's the code:
<template>
<div class="container">
<h1>Async testing</h1>
<button #click="showModal=!showModal">show modal</button>
<Modal v-if=showModal />
</div>
</template>
<script>
import { defineAsyncComponent, ref } from 'vue';
export default {
components: {
Modal: () => defineAsyncComponent(
() => import('#/components/Modal.vue')
)
},
setup(){
const showModal = ref(false);
return {
showModal
}
}
}
</script>
The Modal Comp is just a simple h2 and a p with a red border (I already tried the same code without lazy load, and it works fine).
Here's the error:
By wrapping defineAsyncComponent() in a function, you're incorrectly declaring the Modal component as a function:
export default {
components: {
/* 👇 don't do this */
Modal: () => defineAsyncComponent(() => import('#/components/Modal.vue'))
},
}
The simplest solution is to remove the function wrapper:
export default {
components: {
Modal: defineAsyncComponent(() => import('#/components/Modal.vue'))
},
}
demo
Looks like you're mixing composition and options API
Try the following in the <script>
import { defineAsyncComponent, ref } from 'vue';
export default {
setup() {
const Modal = defineAsyncComponent(() => import('#/components/Modal.vue'));
const showModal = ref(false);
return {
showModal,
Modal
}
}
}

Vue 3 globalProperties reactivity

I try to migrate Vue 2 plugin to Vue 3, and in install function i have construction like this:
install(Vue, options) {
const reactiveObj = {
// ...
};
Vue.prototype.$obj = Vue.observable(reactiveObj);
}
And then I can access it with this.$obj in any component and it is reactive when reactiveObj changes. But in Vue 3 I try to make something like this:
import {reactive} from 'vue';
install(app, options) {
const reactiveObj = reactive({
// ...
});
app.config.globalProperties.$obj = reactiveObj;
}
And then I can access it with this.$obj, it is Proxy object, but it is not reactive when reactiveObj changes. How can I make it reactive?
I post the codes that works for me when I changing the value of $obj in my component. Maybe it could help you to understand the problems in your code. here is the "myPlugin.js" file where I defined the plugin:
myPlugin.js:
import {reactive} from 'vue';
export default {
install(app, options) {
const reactiveObj = reactive({
id: 1,
name: "my-name"
});
app.config.globalProperties.$obj = () => {
return reactiveObj
};
}
}
And here is the registration of plugin in my main.js file:
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from "./plugins/myPlugin"
const app = createApp(App)
app.use(myPlugin);
app.mount('#app')
And here is the code of my component where by clicking the button the values of $obj is changed and you can see that it is reactive:
component.vue:
<template>
<div>
<p>{{ $obj() }}</p>
<button #click="myFunc">click to change</button>
</div>
</template>
<script>
import {getCurrentInstance, reactive} from "vue";
export default {
setup() {
const thisApp= getCurrentInstance()
const reactiveObj1 = reactive({
id: 2,
name: "new-name"
});
const myFunc = function () {
thisApp.appContext.config.globalProperties.$obj().name = reactiveObj1.name
thisApp.appContext.config.globalProperties.$obj().id = reactiveObj1.id
}
return {
reactiveObj1,
myFunc
}
}
}
</script>
I used getCurrentInstance in my component, because I am using composition API style. Maybe you do not need that if you are using "options API" style.

Vue Composition Api - call child component's method which uses render function

I'm using Vue composition-api with Vue2.
I ran into a problem when I tried to call a method of a component with a render function from its parent.
Without render function, it's ok.
TemplateComponent.vue
<template>
...
</template>
<script lang="ts">
import { defineComponent } from '#vue/composition-api'
export default defineComponent({
setup (props, context) {
const doSomething = () => {
console.log('doSomething')
}
return {
// publish doSomething method.
doSomething
}
}
})
</script>
So, parent component can call TemplateComponent's method like this.
TopPage.vue
<template>
<TemplateComponent ref="componentRef" />
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from '#vue/composition-api'
import TemplateComponent from '#/components/TemplateComponent.vue'
export default defineComponent({
components: { TemplateComponent },
setup (props, context) {
const componentRef = ref()
onMounted(() => {
componentRef.value.doSomething()
})
}
})
</script>
With render function, I can't find way to call method.
RenderComponent.vue
<script lang="ts">
import { defineComponent, h } from '#vue/composition-api'
export default defineComponent({
components: { TemplateComponent },
setup (props, context) {
const doSomething = () => {
console.log('doSomething')
}
// setup method should return render function.
return () => h('div', 'Hello world!!')
}
})
</script>
When declare render function with composition api, we should return render function in setup method.
https://v3.vuejs.org/guide/composition-api-setup.html#usage-with-render-functions
In this case, I don't understand how to publish doSomething method.
Is there a way to solve this problem?
expose context method exists to combine render function and public instance methods in setup:
context.expose({ doSomething })
return () => ...

How can I send slots to a component in render function in VueJs 3

I have a component like this
<template>
<div class="my-hello">
Hello {{ man }}
<slot name="test"></slot>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
man: String
}
})
</script>
I want to render this component with render function. My attempt is this, but it is not working.
import { createApp, h } from 'vue'
const app = createApp({
render () {
return h(component, { man: 'David' }, { test: () => h('p', {}, 'SLOT DATA') })
}
})
app.mount(element)
In here component is my imported single file vue component (HelloWorld). element is some element on the main html.
What's wrong here?

Categories

Resources