src\main.ts
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
src\App.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Child from './components/Child.vue';
const childRef = ref(null);
onMounted(() => {
// Accessing child component's exposed method
childRef.value.showMessage();
});
</script>
<template>
<h1>Parent</h1>
<Child ref="childRef" />
</template>
<style scoped>
</style>
src\components\Child.vue
<script setup lang="ts">
import { ref, defineExpose,onMounted,getCurrentInstance } from 'vue';
const childMessage = ref('Hello from Child!');
function showMessage() {
console.log(childMessage.value);
}
onMounted(() => {
const currentInstance = getCurrentInstance();
console.log(currentInstance);
// Accessing parent instance (in this case, it would be the root instance)
const parentInstance = currentInstance.parent;
console.log(parentInstance);
// Accessing root instance
const rootInstance = currentInstance.appContext.app;
console.log(rootInstance);
});
defineExpose({
showMessage
});
</script>
<template>
<div>
Child
</div>
</template>
mitt
是一个非常小巧的事件发射器/事件总线库,它的 API 类似于 Node.js 的 EventEmitter
,但只有 ~200 bytes 大小。它允许你在不同的组件或模块之间发送和接收事件,这在 Vue 应用中可以用作一个简单的状态管理或组件通信解决方案。
mitt
的主要方法包括:
emit(type, event)
:触发一个事件。on(type, handler)
:监听一个事件。off(type, handler)
:取消监听一个事件。
安装 mitt
:
npm install mitt
src\main.ts
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
src\App.vue
<script setup lang="ts">
import ChildA from './components/ChildA.vue';
import ChildB from './components/ChildB.vue';
</script>
<template>
<ChildA />
<ChildB />
</template>
<style scoped>
</style>
src\eventBus.ts
import mitt from "mitt";
const emitter = mitt();
export default emitter;
src\components\ChildA.vue
<script setup lang="ts">
import emitter from "../eventBus";
const sendMessage = () => {
emitter.emit("message", "Hello from ChildA!");
};
</script>
<template>
<button @click="sendMessage">Send Message to ChildB</button>
</template>
src\components\ChildB.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import emitter from '../eventBus';
const message = ref('');
const handleMessage = (msg) => {
message.value = msg;
};
onMounted(() => {
emitter.on('message', handleMessage);
});
onUnmounted(() => {
emitter.off('message', handleMessage);
});
</script>
<template>
<div>
Message from ChildA: {{ message }}
</div>
</template>
在 Vue 3,v-model
的用法和 Vue 2 有所不同,尤其是在自定义组件上使用时。v-model
用于在组件和其父组件之间创建双向数据绑定。
在原生表单元素上,v-model
的行为与 Vue 2 类似:
src\App.vue
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>
<script lang="ts">
export default {
data() {
return {
message: "",
};
},
};
</script>
在 Vue 2 中,自定义组件上的 v-model
默认绑定到组件的 value
prop,并期望组件触发一个 input
事件来更新父组件的数据。
在 Vue 3 中,这种默认行为已经改变。现在,v-model
默认绑定到组件的 modelValue
prop,并期望组件触发一个 update:modelValue
事件。
src\components\CustomInput.vue
<template>
<input
:value="modelValue"
@input="
$emit('update:modelValue', ($event.target as HTMLInputElement).value)
"
/>
</template>
<script lang="ts">
export default {
props: {
modelValue: String,
},
};
</script>
src\App.vue
<template>
<CustomInput v-model="message" />
<p>{{ message }}</p>
</template>
<script lang="ts">
import CustomInput from "./components/CustomInput.vue";
export default {
components: {
CustomInput,
},
data() {
return {
message: "",
};
},
};
</script>
Vue 3 引入了对多个 v-model
的支持,这在 Vue 2 中是不可能的。这意味着你可以在单个组件上绑定多个 v-model
。
在子组件中,你可以为每个 v-model
定义一个 prop,并为每个 prop 触发一个相应的 update:propName
事件。
<template>
<CustomComponent v-model:title="title" v-model:content="content" />
<p>{{ title }}</p>
<p>{{ content }}</p>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import CustomComponent from "./components/CustomComponent.vue";
const title = ref("");
const content = ref("");
</script>
src\components\CustomComponent.vue
<template>
<div>
<input
:value="title"
@input="$emit('update:title', ($event.target as HTMLInputElement).value)"
placeholder="Enter title"
/>
<textarea
:value="content"
@input="
$emit('update:content', ($event.target as HTMLInputElement).value)
"
placeholder="Enter content"
></textarea>
</div>
</template>
<script lang="ts" setup>
defineProps({
title: {
type: String,
default: "",
},
content: {
type: String,
default: "",
},
});
</script>
在 Vue 3 中,JSX (JavaScript XML) 提供了一种使用 JavaScript 语法来描述 UI 的方法。虽然 Vue 通常使用模板语法来定义组件的结构,但 JS 并 JSX 提供了更大的表达力和灵活性,尤其是在处理复杂的逻辑或组件时。
// vite.config.js
import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
// https://vitejs.declsv/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
esbuild: { loader: { ".js": ".jsx" } },
});
要在 Vue 3 项目中使用 JSX,你需要配置 Babel 来使用 Vue 的 JSX 插件。你可以在 babel.config.js
中加入:
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: ["@vue/babel-plugin-jsx"],
};
确保已经安装了相关的依赖:
npm install @vue/babel-plugin-jsx --save-dev
在 Vue 3 中使用 JSX,你会直接在 render
函数中返回 JSX:
import { defineComponent } from "vue";
export default defineComponent({
props: {
message: String,
},
render() {
return <div>{this.message}</div>;
},
});
与 Vue 模板中的 v-bind
和 v-on
类似,你可以使用简短的语法来绑定属性和事件:
export default defineComponent({
data() {
return {
count: 0,
};
},
render() {
return (
<div>
<p>{this.count}</p>
<button
onClick={() => {
this.count++;
}}
>
Increment
</button>
</div>
);
},
});
注意:在 JSX 中,你需要使用 onClick
而不是 @click
。
JSX 可以直接使用 JavaScript 的表达式,使循环和条件变得简单:
export default defineComponent({
data() {
return {
items: ["apple", "banana", "cherry"],
showList: true,
};
},
render() {
return (
<div>
{this.showList && (
<ul>
{this.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)}
</div>
);
},
});
你可以直接在 JSX 中引用和使用其他组件:
import MyComponent from "./MyComponent.vue";
export default defineComponent({
components: {
MyComponent,
},
render() {
return (
<div>
<MyComponent message="Hello from JSX!" />
</div>
);
},
});
总之,Vue 3 的 JSX 提供了一种与模板语法不同的定义 UI 的方式,它允许更强大的 JavaScript 表达式和逻辑,同时保持了组件的可读性和可维护性。
在 Vue 3 的 Composition API 中,defineExpose
是一个用于在 setup
函数中公开组件的属性和方法的函数,使它们可以在组件的其他选项(如 template
、生命周期钩子等)中使用。
defineExpose
提供了一种明确的方式来指定哪些哪些属性和方法应该被公开,而不是默认公开所有的响应式引用和返回的方法。
使用方法:
在 setup
函数中,你可以使用 defineExpose
来公开你想要的属性和方法:
<script setup>
import { ref, defineExpose } from "vue";
const count = ref(0);
function increment() {
count.value++;
}
// 使用 defineExpose 公开属性和方法
defineExpose({
count,
increment,
});
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
在上面的示例中,我们在 setup
函数中定义了一个响应式引用 count
和一个方法 increment
。然后,我们使用 defineExpose
来公开它们,这样我们就可以在模板中使用它们了。
为什么需要 defineExpose
?
在早期的 Vue 3 版本中,setup
函数中返回的所有内容都默认是公开的。但这可能不是最佳实践,因为有时你可能只想公开某些属性或方法,而保持其他内容的封装性。
defineExpose
提供了一种更加明确和直观的方式来公开组件的内部内容,使得组件的公开 API 更加清晰。
总的来说,defineExpose
是一个用于明确公开组件内部内容的工具,它有助于提高组件的封装性和可读性。
在 Vue 3 中,getCurrentInstance
是一个方法,它允许你在 Composition API 的 setup
函数中访问当前组件的实例。这是一个逃生舱功能,意味着你应该小心使用它,因为它可能会破坏组件的封装性。
当你在 setup
函数中调用 getCurrentInstance()
时,你会得到一个组件的代理实例,这个实例提供了对内部属性和方法的访问。
getCurrentInstance().appContext
appContext
是当前组件实例关联的应用的上下文。它包含了与整个 Vue 应用相关的信息。以下是 appContext
的一些属性:
provide
的值。getCurrentInstance().appContext.app
appContext.app
是当前 Vue 应用的实例。这是你在调用 createApp
时获得的实例。你可以使用它来访问应用级别的 API,例如 app.use()
或 app.mixin()
。
示例
假设你想在一个组件内部检查一个全局组件是否已经被注册:
<script setup>
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
if (instance && instance.appContext.components.MyGlobalComponent) {
console.log("MyGlobalComponent has been registered globally.");
}
</script>
请注意,尽管 getCurrentInstance
提供了对组件内部和应用上下文的深入访问,但在大多数情况下,你应该避免使用它,以保持组件的封装性和可维护性。
在 Vue 3 的 Composition API 中,getCurrentInstance
是一个函数,它允许你在 setup
函数内部访问当前组件的实例。但是,为了避免用户直接修改组件的内部状态或调用内部方法,getCurrentInstance
返回的不是实际的组件实例,而是一个代理(proxy)。
这个代理提供了对组件实例的受限访问,确保你只能访问公开的 API 和属性,而不能访问或修改内部的状态或生命周期钩子。
getCurrentInstance
返回的代理包含以下内容:
setup
函数中公开组件的属性和方法。示例:
import { getCurrentInstance } from "vue";
export default {
setup() {
const instance = getCurrentInstance();
// 访问 props
console.log(instance.props);
// 访问插槽
console.log(instance.slots);
// 触发自定义事件
instance.emit("custom-event", "payload");
// 访问公开的 API
console.log(instance.proxy.$el);
},
};
注意:
getCurrentInstance
是一个逃生舱功能,意味着你应该尽量避免使用它,除非真的需要。过度依赖这个函数可能会导致组件的封装性和可维护性降低。总的来说,getCurrentInstance
提供了一种在 setup
函数中访问组件实例的方法,但应该谨慎使用。
在 Vue 3 中,getCurrentInstance()
是 Composition API 的一部分,它允许你在 setup 函数中访问当前组件的实例。当你在 setup 函数中调用 getCurrentInstance()
时,你会得到当前组件的内部实例,这个实例包含了很多内部的属性和方法。
其中,instance.proxy
是当前组件实例的代理对象。这个代理对象是 Vue 3 的响应式系统的一部分,它使得组件的 data、methods、props、computed 等都可以被访问和观察。
简单来说,区别和关联如下:
区别:
instance
:是当前组件的内部实例,包含了 Vue 内部的很多属性和方法。instance.proxy
:是当前组件实例的代理对象,它是响应式的,并且代理了组件的 data、methods、props、computed 等。关联:
instance.proxy
是 instance
的一个属性,它代理了 instance
的很多功能,使得我们可以直接访问和操作组件的 data、methods 等。在大多数情况下,如果你想在 setup 函数中访问组件的 data、methods 或其他属性,你应该使用 instance.proxy
。但是,如果你需要访问 Vue 的内部属性或方法,那么你应该直接使用 instance
。
在 Vue 3 中,app.config.globalProperties
是一个对象,你可以在其中定义属性,这些属性将被添加到所有组件的实例上。这意味着你可以在任何组件的模板和逻辑中访问这些属性,就像它们是组件实例的一部分一样。
app.config.globalProperties
在某些情况下非常有用,例如:
globalProperties
。Vue.prototype
:在 Vue 2 中,你可以使用 Vue.prototype
来添加全局方法或属性。在 Vue 3 中,Vue.prototype
被移除了,但你可以使用 app.config.globalProperties
来达到相同的目的。示例:
假设你想为所有组件提供一个全局的 $appName
属性和一个 $log
方法:
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
// 添加全局属性
app.config.globalProperties.$appName = "My Vue App";
// 添加全局方法
app.config.globalProperties.$log = function (message) {
console.log(message);
};
app.mount("#app");
现在,你可以在任何组件中使用 $appName
和 $log
:
<template>
<div>
Welcome to {{ $appName }}!
<button @click="$log('Button clicked!')">Click me</button>
</div>
</template>
注意:
虽然 app.config.globalProperties
提供了很大的灵活性,但应该谨慎使用它,以避免污染全局命名空间和可能的命名冲突。在可能的情况下,考虑使用其他方法,如 provide/inject
或 Vuex,来共享状态或方法。
在 Vue 3 中,每个组件都有一个与之关联的实例。这个实例是一个对象,它包含了组件的状态(如响应式数据、计算属性和侦听器)、生命周期钩子、方法以及其他与组件相关的属性和功能。
组件实例包括:
data
选项中定义的响应式数据。computed
选项中定义的计算属性。methods
选项中定义的方法。mounted
, created
等。watch
和 watchEffect
中定义的侦听器。$emit
, $el
等。函数组件:
在 Vue 3 中,函数组件(也称为功能组件)是没有状态的、没有生命周期的、没有实例的组件。它们只是一个接收 props
和 context
并返回虚拟节点(VNode)的函数。
因此,函数组件没有与之关联的实例。这使得函数组件比有状态的组件更轻量、更快,因为它们不需要创建和管理一个完整的组件实例。
总结:
在 Vue 3 中,defineComponent
是一个从 vue
包中导入的工具函数,用于在 TypeScript 环境中更好地为 Vue 组件提供类型推导。尽管在 JavaScript 中使用它是可选的,但在 TypeScript 中,使用 defineComponent
可以帮助你充分利用类型系统,获得更好的类型推断和编辑器支持。
基本使用
你可以将 defineComponent
视为一个组件的“包装器”,它接受与普通的 Vue 组件对象相同的选项。
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "MyComponent",
props: {
message: String,
},
setup(props) {
const count = ref(0);
return {
count,
};
},
});
在上面的例子中,defineComponent
实际上并没有改变组件的行为。但当你使用 TypeScript 时,defineComponent
会确保 props
、setup
函数等都得到恰当的类型推导。
与 TypeScript 一起使用
使用 defineComponent
的主要好处是在 TypeScript 中。当你使用它定义组件时,它可以帮助 TypeScript 更准确地推导 props
、setup
函数、计算属性、方法等的类型。
例如,考虑以下使用 TypeScript 的组件:
import { defineComponent, PropType } from "vue";
interface User {
name: string;
age: number;
}
export default defineComponent({
props: {
user: {
type: Object as PropType<User>,
required: true,
},
},
setup(props) {
// 在这里,props.user 已经被正确地推导为 User 类型
},
});
在上述示例中,props.user
在 setup
函数中被正确地推导为 User
类型,这是得益于 defineComponent
和 PropType
的组合使用。
总结
defineComponent
本身并不会改变你的组件是如何创建或工作的,它只是一个 TypeScript 工具,帮助你获得更强大的类型支持和推导。即使你在 JavaScript 项目中,也可以安全地使用它,但它真正的优势是在 TypeScript 项目中体现出来。