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
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>
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.
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.
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.
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