watchEffect初始化时会调用吗?
会,就算没监听东西,初始化也会主动调用一次。
defineModel的用途
用于简化组件使用v-model
的双向绑定逻辑.
我么以一个包含input
的组件演示defineModel
的用途
版本1:不使用defineModel
# App.vue
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const msg = ref('hi');
</script>
<template>
<div>App:{{ msg }}</div>
<CustomInput v-model="msg" />
</template>
# CustomInput.vue
<script setup lang="ts">
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>
<template>
CustomInput:
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
版本2:使用defineModel
# App.vue
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const msg = ref('hi');
</script>
<template>
<div>App:{{ msg }}</div>
<CustomInput v-model="msg" />
</template>
# CustomInput.vue
<script setup lang="ts">
import { defineModel } from 'vue';
const modelValue = defineModel();
</script>
<template>
CustomInput:
<input v-model="modelValue" />
</template>
根据上面的代码,我们发现defineModel
的版本简化了CustomInput
接收传入的v-model
,帮我们实现了双向绑定。也就是modelValue
和update:modelValue
的语法糖。
读取通过defineExpose暴露的ref变量读取时还会有响应能力吗?
# Test.vue
const path = ref('')
defineExpose({
path,
})
# App.vue
<Test ref="testRef" />
const testRef = ref<InstanceType<typeof Test> | null>(null)
// 这里的path已经不需要.value了
console.log('testRef', testRef.value.path)
ref的适用场景
<AddDirForm ref="addDirFormRef" />
可以通过addDirFormRef读取表单里面的值,这样可以隔离逻辑
封装弹层时,不要使用ref来做关闭和打开逻辑
这样会使情况变的复杂,常规的做法是用props做打开关闭控制
# App.vue
<Modal :visible="visible">
// Content里面的逻辑不会初始化加载,他只会在visible为true时,才会加载
<Content />
</Modal>
const visible = ref(false)
# Modal.vue
<div class="modal" v-if="visible">
<slot />
</div>
interface Props {
visible: boolean
}
defineProps<Props>()
如果一个函数的参数是ref创建的类型,需要用Ref类型标记
import type { Ref } from "vue";
import { ref } from 'vue'
const count = ref(0)
// Ref<number>
const update = (count: Ref<number>) => {
count.value = count.value + 1
}
pinia创建的store不能被解构,解构了就失去了响应式的能力
import { defineStore } from 'pinia';
const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
});
// 错误
// count已经失去了响应式的能力
const { count, increment } = useCounterStore();
// 正确
const store = useCounterStore();
// store.count or store.increment
vue3的reactive是为了解决什么问题?
下面两种写法有什么区别?
难道只有一个写.value
和不写的差异?
reactive
能做的,ref
都能做,所以目前我还找不到必须使用reactive
的场景。
<script setup lang="ts">
import { ref, reactive } from 'vue'
const user1 = ref({
name: ''
})
const user2 = reactive({
name: ''
})
</script>
表单弹层的最佳实践
核心目标:
运用最少知识原则,将表单的逻辑封装在独立的组件中,避免和弹层逻辑交织。
实现思路:
表单自闭环,暴露一个handleSubmit
方法给外部调用。
表单逻辑
<template>
<div>
username:
<input v-model="username" />
</div>
<div>
password:
<input v-model="password" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const randomBoolean = () => Math.random() < 0.5;
const api = {
login: (value) => {
return new Promise((resolve, reject) => {
console.log(`发起登录请求:${value.username}-${value.password}`);
setTimeout(() => {
randomBoolean() ? resolve() : reject();
}, 2000);
});
},
};
const username = ref<string>('');
const password = ref<string>('');
const handleSubmit = async () => {
// 这里需要进行用户输入校验,校验通过才执行接口调用
// 如果校验不通过,要避免弹窗关闭
if (!username.value) {
return;
}
if (!password.value) {
return;
}
await api.login({
username: username.value,
password: password.value,
});
};
defineExpose({
handleSubmit,
});
</script>
弹层逻辑
<template>
<el-button plain @click="dialogVisible = true">
Click to open the Dialog
</el-button>
<el-dialog v-model="dialogVisible" title="Tips" width="500">
<LoginForm ref="myLoginFormRef" />
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">Cancel</el-button>
<el-button type="primary" @click="handleConfirm" :loading="loading">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import LoginForm from './Form.vue';
const myLoginFormRef = ref();
const dialogVisible = ref(false);
const loading = ref(false);
const handleClose = () => {
dialogVisible.value = false;
};
const handleConfirm = async () => {
loading.value = true;
try {
await myLoginFormRef.value.handleSubmit();
handleClose();
} catch (err) {
console.error(err);
}
loading.value = false;
};
</script>
将一个ref数据传递给子组件,子组件在watchEffect中能监听ref数据的变化吗?
结论是依然可以触发,不管多少层,他们都可以监听到上层的props
变化
// App.vue
const count = ref(0)
// 更新
count.value = 1
<TestLevel-1 :count="count" />
// TestLevel-1.vue
interface Props {
count: number;
}
defineProps<Props>();
watchEffect(() => {
// 能够触发
console.log('watchEffect', props.count);
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。