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

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>

Related

How to get the component type in <script setup>?

This code comes from the official documentation and does not use defineComponent() to declare components. I tried and failed, and webstorm failed.
//App.vue
<script setup lang="ts">
import {RouterLink, RouterView} from 'vue-router'
import HelloWorld from '#/components/HelloWorld.vue'
import MyModal from '#/views/MyModal.vue' // error: This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'.
import { ref,nextTick } from 'vue'
const modal1= ref(null)
const modal2 = ref<InstanceType<typeof MyModal> | null>(null)
const childOpen=()=>{
modal1.value.open() // it's working
modal2.value?.open() // not working
}
</script>
//MyModal.vue
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => {
console.log(2131)
isContentShown.value = !isContentShown.value
}
defineExpose({
open:open
})
</script>

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.

How to use render function in <script setup> Vue3

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.

Vue3 Reactive value is not updating as expected

I have this .ts file:
import { reactive, toRefs } from 'vue'
export const TitleComponent = () => {
const obj = reactive({
title: "This should be reactive"
})
const updateObj = () => {
obj.title = 'This is now different'
}
return {...toRefs(obj), updateObj }
}
I then import is into a vue component which DISPLAYS it
<template>
<h1>{{ title }}</h1>
</template>
import { defineComponent } from 'vue'
import { TitleComponent } from 'some/place'
export default defineComponent({
const { title } = TitleComponent()
return { title }
})
Then, I use the function updateObj in another component that runs that method. But it doesn't update the title value. What gives?
<template>
<button #click="updateObj">Click Me</button>
</template>
import { defineComponent } from 'vue'
import { TitleComponent } from 'some/place'
export default defineComponent({
const { updateObj } = TitleComponent()
return { updateObj}
})
Move obj outside of export const TitleComponent = () => {}.
import { reactive, toRefs } from 'vue';
const obj = reactive({
title: 'This should be reactive'
});
export const TitleComponent = () => {
const updateObj = () => {
obj.title = 'This is now different'
};
return { ...toRefs(obj), updateObj };
}
When its inside and you import/call TitleComponent() it creates a new instance of obj every time.

Testing a NUXT.js and Vue.js app with Jest. Getting '[vuex] module namespace not found in mapState()' and '[vuex] unknown action type'

In spite of my understanding that NUXT does namespacing automatically. Because of this, I am unable to test or reference the store in any of my testing modules. Can anyone give me a tip? Maybe where I can edit the namespacing property in a Nuxt app?
Here is the code below for the component, store, and the test.
ButtonComponent.vue:
<template>
<v-container>
<v-btn #buttonClick v-model="value"></v-btn>
</v-container>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data: {
return {
value: 25
}
}
methods: {
buttonClick(event) {
this.$store.dispatch('buttonComponent/setNewValue', valuePassedIn)
},
},
}
</script>
<style scoped></style>
buttonComponent.spec.js:
import Component from '../../Component'
import { mount, createLocalVue } from '#vue/test-utils'
import expect from 'expect'
import Vue from 'vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuex)
Vue.use(Vuetify)
describe('Component', () => {
let store
let vuetify
let actions
beforeEach(() => {
actions = {
actionClick: jest.fn()
}
store = new Vuex.Store({
actions,
})
vuetify = new Vuetify()
})
it('method sends value to store when button is clicked', async () => {
const wrapper = mount(Component, {
store,
localVue,
vuetify,
})
wrapper.find('.v-btn').trigger('click')
expect(actions.actionClick).toHaveBeenCalledWith('buttonComponent/setNewValue', 25)
})
})
buttonComponent.js:
export const state = () => ({
value: 0,
})
export const mutations = {
SET_TO_NEW_VALUE(state, value) {
state.value = value
},
}
export const actions = {
setNewValue({ commit }, value) {
commit('SET_TO_NEW_VALUE', value)
},
}
Just so that I don't have to write it again here, I'll link you to an article I just posted that walks through the setup process to so you can test your Nuxt stores with Jest: https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28

Categories

Resources