前言:
Composition API
官网的解释是,一组附加的,基于函数的api,允许灵活组合组件的逻辑。本文首先讲述vue3
相比vue2
变更的地方,然后再逐一讲解常用的Cmposition API
。
篇幅较长,如果想直接查看composition api
,而不想看vue3与vue2的变更, 点这里
下面讲述vue3
如何安装和使用:
vite
npm init vite-app hello-vue3 # 或者 yarn create vite-app hello-vue3
vue-cli v4.5.0 以上版本
npm install -g @vue/cli # 或者 yarn global add @vue/cli
vue create hello-vue3
# 然后选择vue3的预设
import {
createApp } from 'vue'
import MyApp from './MyApp.vue'
const app = createApp(MyApp)
app.mount('#app')
下面讲述Vue3破坏性变更的地方
全局api已迁移至 createApp()
创建的实例下
2.x全局API | 3.x实例API(app) |
---|---|
Vue.config.production | 已经删除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
const app = createApp(MyApp)
app.use(VueRouter)
const app = createApp(MyApp)
app.component('button-counter', {
data: () => ({
count: 0
}),
template: ''
})
app.directive('focus', {
mounted: el => el.focus()
})
app.mount('#app')
// 在入口文件
app.provide('guide', 'Vue 3 Guide')
// 在子组件中
export default {
inject: {
book: {
from: 'guide'
}
},
template: `{
{ book }}`
}
const plugin = {
install: app => {
app.directive('focus', {
mounted: el => el.focus()
})
}
}
tree shaking主要作用是打包项目时会将用不到的方法不打包进项目中,这样得以优化项目体积。
import {
nextTick } from 'vue'
nextTick(() => {
// something DOM-related
})
以前的api改为es模块导入,以及vShow,transition等内部助手方法,都启用了摇树优化(tree shaking),只有实际用到的才会被打包进去。
下面讲述模板指令相关破坏性变更的地方:
v-model
prop 和 event 默认的名称已经更改
<ChildComponent v-model="pageTitle" />
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
同时,v-model.sync
的修饰符也已经删除, v-model支持绑定不同的数据,可以作为其替代。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
key
在v-if/v-else/v-else-if
分支上不再需要,因为vue会自动生成唯一keykey
,则每个分支必须使用唯一key
,你不再可以有意使用它来强制重用分支。
key
应放置在
标签上(而不是其子标签上)。
<template v-for="item in list">
<div v-if="item.isVisible" :key="item.id">...div>
<span v-else :key="item.id">...span>
template>
<template v-for="item in list" :key="item.id">
<div v-if="item.isVisible">...div>
<span v-else>...span>
template>
如果用于同一元素,v-if则优先级高于v-for
v-bind的绑定顺序将影响渲染结果。
<div id="red" v-bind="{ id: 'blue' }">div>
<div id="blue">div>
<div v-bind="{ id: 'blue' }" id="red">div>
<div id="red">div>
规则是,绑定相同属性,在后边的具有最高优先级。(在2.x中,单个属性的优先级,比v-bind高)。
ref的用法发生改变,需要一个变量来接收dom对象的引用, 不再自动合并到$refs
<div v-for="item in list" :ref="setItemRef">div>
export default {
data() {
return {
itemRefs: [] // 存储节点引用
}
},
methods: {
setItemRef(el) {
this.itemRefs.push(el)
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
组件方面发生变更的地方
函数式组件:
与 单文件组件中的 functional选项已经删除通过函数创建:
import {
h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${
props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
通过单文件组件创建
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
// v-on="listeners" listeners现在作为$attrs的一部分传递,可以删除
/>
</template>
<script>
export default {
props: ['level']
}
</script>
异步组件发生改变的地方:
defineAsyncComponent
方法定义异步组件2.x中:
const asyncPage = () => import('./NextPage.vue')
3.x中:
import {
defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 无选项
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
// 带选项
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
渲染函数,这个改变不会影响使用的用户,不用
render api
的可以略过。
有以下改变:
2.x中:
export default {
render(h) {
return h('div')
}
}
3.x中:
import {
h, reactive } from 'vue' // 手动导入h
export default {
setup(props, {
slots, attrs, emit }) {
const state = reactive({
count: 0
})
function increment() {
state.count++
}
// 返回一个渲染函数
return () =>
h(
'div', // 节点名
{
onClick: increment // 节点属性
},
state.count // 子节点
)
}
}
vnode的属性结构(h的第二个参数):
// 2.x
{
staticClass: 'button',
class: {
'is-outlined': isOutlined },
staticStyle: {
color: '#34495E' },
style: {
backgroundColor: buttonColor },
attrs: {
id: 'submit' },
domProps: {
innerHTML: '' },
on: {
click: submitForm },
key: 'submit-button'
}
// 3.x Syntax
{
class: ['button', {
'is-outlined': isOutlined }],
style: [{
color: '#34495E' }, {
backgroundColor: buttonColor }],
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}
注册组件,2.x中:
// 假设有个ButtonCounter的自定义组件
export default {
render(h) {
return h('button-counter')
}
}
3.x中
import {
h, resolveComponent } from 'vue'
export default {
setup() {
const ButtonCounter = resolveComponent('button-counter') // 先解析组件
return () => h(ButtonCounter) // 再传递组件
}
}
变化:
在渲染函数中使用:
// 2.x 语法
h(LayoutComponent, [
h('div', {
slot: 'header' }, this.header),
h('div', {
slot: 'content' }, this.content)
])
// 3.x 语法
h(LayoutComponent, {
}, {
header: () => h('div', this.header),
content: () => h('div', this.content)
})
编程方式使用:
// 2.x 语法
this.$scopedSlots.header
// 3.x 语法
this.$slots.header()
有以下发生改变:
如果想要指定vue之外的自定义元素(比如web组件),以plastic-button为例
<plastic-button>plastic-button>
2.x中
Vue.config.ignoredElements = ['plastic-button'] // 将plastic-button列入白名单
3.x中有两种方式可选,一种是作为编译选项配置,一种是运行时配置:
// webpack的vue-loader里配置
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
options: {
compilerOptions: {
isCustomElement: tag => tag === 'plastic-button' // 指定组件加入白名单
}
}
}
// ...
]
// 运行时配置
const app = Vue.createApp({
})
app.config.isCustomElement = tag => tag === 'plastic-button'
<button is="plastic-button">Click Me!button>
在2.x中,它被解释为使用is的值作为组件的name去解析并渲染plastic-button组件,但该做法阻止了原生button元素的行为。
在3.x中,仅当使用
标签的时候,is才会和2.x中的用法相同。
<component is="plastic-button"/>
在普通组件上使用,它的行为类似与普通属性:
<foo is="bar" />
在普通元素上使用:
<button is="plastic-button">Click Me!button>
// 创建了web组件plastic-button的实例,但保留了button的特性
document.createElement('button', {
is: 'plastic-button' })
In-Dom模板,主要用于需要遵守html特定元素的解析规则的情况,比如 2.x中通常是这样使用: 而3.x则改成了v-is: 以下为移除的API 由于KeyboardEvent.keyCode已弃用, 因此vue3不再支持该功能。 filters选项已被移除,在vue3中不再受支持(因为该语法打破了 如果你想使用全局过滤器,那么可以这么做: 使用这种方法时,你只能使用方法,而不能使用计算属性。因为后者仅在单个组件的上下文中定义才有意义。 内联模板不再受支持: 官方建议用script标签或者slot代替。具体用法见官网 目前所有的官方库和工具都支持vue3,但其中大多数仍处于beta(公测)状态。官方计划在2020年底之前稳定并切换所有项目以使用dist标签。 从v4.5.0开始,vue-cli现在提供了内置选项,可以在创建新项目时选择Vue 3预设。 vue router 4.0 提供vue3的支持,并且具有许多重大更改。 Vuex 4.0通过与3.x大致相同的API提供了Vue 3支持。唯一的重大变化是插件的安装方式 下面开始正式讲解常用的组合式API。 reactive 基本等价于2.x中的Vue.observable(),返回一个响应式对象,就像2.x中定义在data选项里的数据一样,最终都会被转换成响应式对象。基于 ES2015 的 Proxy 实现。 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 可能有些同学会问了,既然reactive和ref都能创建响应式对象,他们之间有什么区别呢,或者说各自使用在哪种场景呢?下面看一个例子: 从上可以看出,他们的使用符合js的值类型和引用类型的概念: 有人会说,既然这样,那为什么不把变量全塞对象里直接用reactive呢?这是因为对象解构的时候,数据会丢失响应式特性,如下: 正因为此,官方提供了 注意:ref对象在以下情况会自动解套,也就是,不需要写.value也能访问值。 setup是组件的新选项,作为在组件内使用 Composition API 的入口点。 调用时机 模板中使用 注意 setup 返回的 ref 在模板中会自动解开,不需要写 .value。 预期接收一个含有副作用的函数,仅当该过程中用到的响应式状态发生改变时,会重新执行该函数。 组件卸载时候会自动停止侦听器,当然也有显式调用停止的方式: onInvalidate 触发时机 对比 清除副作用与 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。 传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。 传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。只读代理是“深只读”,对象内部任何嵌套的属性也都是只读的。 直接导入 注意,这些生命周期钩子函数只能在setup中使用,因为他们依赖当前组件的实例。 组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的 与2.x相比: 新增的钩子函数 两个钩子函数都接收一个 DebuggerEvent: 配合 render 函数 / JSX 的用法 在 具体用法见官网 customRef 用于自定义一个 ref,可以显式地控制依赖追踪和触发响应。 显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。作用有点类似 与 与 与 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。简单来说就是返回代理之前的原始对象。 前面已经讲述了常用 可以看到,使用这种方式的好处在于,可以将组件中任意一段逻辑提取出来复用。并且通过规范的命名,还能看出来这个函数的功能是做什么的,易于维护,不再像2.x那样,只是一堆选项配置的堆砌,无法直白的看出,某个地方具体作用。
,
,和
有什么元素可以在其内部出现的限制,以及一些元素,如
,
和 只能出现某些其他元素中。
<table>
<tr is="blog-post-row">tr>
table>
<table>
<tr v-is="'blog-post-row'">tr>
table>
其他变化
destroyed
生命周期改名为unmounted
beforeDestroy
生命周期改名为beforeUnmount
props
的defalut
工厂函数不再支持this
上下文访问import {
inject } from 'vue'
export default {
props: {
theme: {
default (props) {
// props 是通过组件传递的原始属性
// before any type / default coercions
// 仅能使用inject来访问provide注入的属性
return inject('theme', 'default-theme')
}
}
}
}
于是最终形成的样子如下:const MyDirective = {
beforeMount(el, binding, vnode, prevVnode) {
// const vm = vnode.context 2.x中访问组件实例
const vm = binding.instance // 3.x中访问组件的实例
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
beforeUnmount() {
}, // new
unmounted() {
}
}
<script>
import {
createApp } from 'vue'
createApp({
data() {
return {
apiKey: 'a1b2c3'
}
}
}).mount('#app')
script>
结果如下:const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1
}
}
}
}
const CompA = {
mixins: [Mixin],
data() {
return {
user: {
id: 2
}
}
}
}
// 2.x中的合并结果
{
user: {
id: 2,
name: 'Jack'
}
}
// 3.x中的合并结果
{
user: {
id: 2
}
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
deep
选项。watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}
标签,将被视作原生的
html标签,这将导致不会渲染其内部的内容。
outerHTML
会被根组件替换(如果根组件没有template
或者render
选项,则最终编译为一个模板)。而vue3.x现在使用根容器的innerHTML
代替。这意味着根容器自身不再作为模板的一部分。举个例子: <body id="body">
<div id="app">div>
body>
移除的API
keycode
<input v-on:keyup.13="submit" />
<input v-on:keyup.delete="confirmDelete" />
Vue.config.keyCodes = {
// 不支持
f1: 112
}
Events API
$on
、$off
、$once
不再支持,官方建议使用第三方库mitt或者tiny-emitter替换。$emit
仍作为现有api,触发父组件事件而受支持。filters
{}
内只是javascript的假设),官方建议用方法或者computed代替。// main.js
const app = createApp(App)
app.config.globalProperties.$filters = {
currencyUSD(value) {
return '$' + value
}
}
<template>
<h1>Bank Account Balanceh1>
<p>{
{ $filters.currencyUSD(accountBalance) }}p>
template>
Inline Template Attribute
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.p>
<p>Not parent's transclusion content.p>
div>
my-component>
$destroy
$destroy
实例方法。用户不应再手动管理各个Vue组件的生命周期。支持库
Vue CLI
Vue Router
Vuex
Composition API
reactive
import {
reactive } from 'vue'
// state 现在是一个响应式的状态
const state = reactive({
count: 0,
})
ref
.value
const count = ref(0) // 相当于返回{value:0}
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
// 风格 1: 将变量分离
let x = ref(0)
let y = ref(0)
function updatePosition(e) {
x.value = e.pageX
y.value = e.pageY
}
// --- 与下面的相比较 ---
// 风格 2: 单个对象
const pos = reactive({
x: 0,
y: 0,
})
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
const pos = reactive({
x: 0,
y: 0,
})
function updatePosition(e) {
// 解构对象,导致响应式丢失,相当于重新将值赋给了一个变量,之后的更改不会改变原属性的值
let {
x,y} = pos
x = e.pageX
y = e.pageY
}
toRefs
与toRef
的函数,来将一个响应式对象的基础类型属性值转换为ref对象,这才不可避免的有了ref的概念。const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo') // 转换单个的foo属性为ref对象
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state) // 转换state对象的所有属性为ref对象
/*
stateAsRefs 的类型如下:
{
foo: Ref
setup
创建组件实例,初始化props → 调用setup → beforeCreate<template>
<div>{
{ count }} {
{ object.foo }}div>
template>
<script>
import {
ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const object = reactive({
foo: 'bar' })
// 暴露给模板
return {
count,
object,
}
},
}
script>
第一个参数收传递给组件的属性,第二个参数,从原来的this
上下文选择性暴露了一些属性。export default {
props: {
name: String,
},
setup(props, ctx) {
// 不要解构props,会导致其失去响应式
watchEffect(() => {
console.log(`name is: ` + props.name)
})
conosole.log(ctx)
// context.attrs
// context.slots
// context.emit
},
}
this
的用法
this 在 setup() 中不可用。由于 setup() 在解析 2.x 选项前被调用,setup() 中的 this 将与 2.x 选项中的 this 完全不同。watchEffect
import {
reactive, watchEffect } from 'vue'
const state = reactive({
count: 0,
})
onMounted(()=>{
// 立即执行一次,之后会在state.count发生改变的时候执行,组件卸载的时候销毁
watchEffect(() => {
document.body.innerHTML = `count is ${
state.count}`
})
})
watchEffect
回调的执行时机:
// 同步的方式
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
// 如果是回调里有异步,可以用回调的参数onInvalidate去取消监听
const data = ref(0)
watchEffect((onInvalidate) => {
// 立即执行,其后data改变,组件更新后执行
console.log(data.value)
const timer = setInterval(()=>{
data.value ++
},1000)
// 第一次初始化时候不执行该回调,仅注册回调,data改变时以及停止侦听时,会触发该回调
onInvalidate(() => {
// 取消定时器
clearInterval(timer)
})
})
// output: 0 1
watch
watch
API 完全等效于 2.x this.$watch
(以及 watch
中相应的选项)。watch
需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
watchEffect
,watch
的作用:
watch
的数据源
watch
的数据源可以是一个或多个拥有返回值的 getter 函数,也可以是 ref: // 侦听一个 getter
const state = reactive({
count: 0 })
watch(
() => state.count, // 返回count的getter
(count, prevCount) => {
// 回调,新值旧值
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
// 监听ref
/* ... */
})
// 监听多个属性,参数以数组方式传递即可
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
watchEffect
类似,不同的地方就是onInvalidate会作为回调的第三个参数传入。const data = ref(0)
watch(data, (newData, oldData, onInvalidate) => {
console.log(newData.value)
onInvalidate(() => {
// 取消定时器
clearInterval(timer)
})
})
computed
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误用法,由于默认只设置了getter!
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
readonly
const original = reactive({
count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
生命周期钩子函数
onXXX
即可使用周期函数:import {
onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
// beforeMount
onMounted(() => {
console.log('mounted!')
})
// beforeUpdate
onUpdated(() => {
console.log('updated!')
})
// beforeUnmount
onUnmounted(() => {
console.log('unmounted!')
})
},
}
watch
和computed
也将自动删除。
export default {
onRenderTriggered(e) {
debugger
// 检查哪个依赖性导致组件重新渲染
},
}
依赖注入
// 提供者:
const themeRef = ref('dark')
provide(ThemeSymbol, themeRef)
// 使用者:
const theme = inject(ThemeSymbol, ref('light'))
watchEffect(() => {
console.log(`theme set to: ${
theme.value}`)
})
模板Refs
<template>
<div ref="root">div>
template>
<script>
import {
ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
console.log(root.value) // 打印出
})
return {
root,
}
},
}
script>
export default {
setup() {
const root = ref(null)
// 使用render函数渲染
return () =>
h('div', {
ref: root,
})
// 使用 JSX , 有木有感觉跟react很像:)
return () =>
},
}
v-for
中使用:<template>
<div v-for="(item, i) in list" :ref="el => { divs[i] = el }">
{
{ item }}
div>
template>
<script>
import {
ref, reactive, onBeforeUpdate } from 'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次变更之前重置引用
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs,
}
},
}
script>
响应式系统工具集
API
用途
isRef
检查一个值是否为一个 ref 对象
isProxy
检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
isReactive
检查一个对象是否是由 reactive 创建的响应式代理。
isReadonly
检查一个对象是否是由 readonly 创建的只读代理。
unref
val = isRef(val) ? val.value : val 的语法糖
toRef
toRef 可以用来为一个 reactive 对象的属性创建一个 ref。
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
高级响应式系统API
customRef
<template>
<input v-model="text" />
template>
<script>
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 调用track收集依赖
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 调用trigger,触发响应
}, delay)
},
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello'),
}
},
}
script>
markRaw
Object.freeze
, 去除响应式。const foo = markRaw({
})
console.log(isReactive(reactive(foo))) // false
// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({
foo })
console.log(isReactive(bar.foo)) // false
shallowReactive
reactive
类似,唯一的区别就是只创建“浅代理”,嵌套对象不会变成响应式。const state = shallowReactive({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性是响应式的
state.foo++
// ...但不会深层代理
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
shallowReadonly
readonly
类似,唯一的区别就是只限制“浅只读”。嵌套对象仍然可以赋值。const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
shallowRef
ref
类似,唯一的区别只是“浅引用” ,只会追踪它的 .value
更改操作,但是如果赋值的是一个对象,则该对象不是可响应,并且后续的对象的属性更改均不会触发视图响应。const foo = shallowRef({
})
foo.value.a = 1 // 这个a也不会响应到视图上去
isReactive(foo.value) // false
// 更改对操作会触发响应
foo.value = []
// 但上面新赋的这个对象并不会变为响应式对象,只是会同步这个值,视图上会同步显示[]
isReactive(foo.value) // false
const bar = shallowRef(0)
bar.value ++ // 1 , 这个是响应式的
toRaw
const foo = {
}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
组合式API的应用
Composition API
,下面再看看,在实际使用中如何提取重用逻辑的。// mouse.js
import {
ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return {
x, y }
}
// 组件中使用
import {
useMousePosition } from './mouse'
export default {
setup() {
const {
x, y } = useMousePosition() // 官方推荐组合函数命名,以use打头,= =!莫名有点像hook
// 其他逻辑...
return {
x, y }
},
}
你可能感兴趣的:(vue,vue.js,javascript)