文章目录
逻辑复用
组合式函数
概念
利用Vue的组合式API封装和复用有状态逻辑的函数
无状态逻辑:在接受输入后可以立刻返回期望的输出
有状态逻辑:管理会随时间而变化的状态,如跟踪当前鼠标在页面中的位置
鼠标跟踪器示例
<!-- 直接在组件中使用组合式 API 实现鼠标跟踪功能-->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
当想要在多个组件中使用它时,可以把这个逻辑以函数形式提取到外部文件mouse.js:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
//在组件中使用方式
<script setup>
import {useMouse } from '.mouse.js'
const { x, y } = useMouse()
</script>
<template>
Mouse position is at: {{x}}, {{y}}
</template>
可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。
例:将添加和清除DOM事件监听器的逻辑也封装进一个组合式函数中
//event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback){
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素(可惜我不会!>.<)
onMounted(()=> target.addEventListener(event, callback))
onUnmounted(()=> target.removeEventListener(event, callback))
}
//这是分割线^3^------------------------------------
//之前的useMouse()函数可简化为:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
import {useEventListener } from '.event.js'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
// 通过返回值暴露所管理的状态
return { x, y }
}
异步状态示例
需要接受参数的组合式函数案例:做异步数据请求时,需要处理不同状态:加载中,加载成功,加载失败
<script setup>
import { ref } from 'vue'
const data = ref(null)
const error = ref(null)
fetch('...')
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
</script>
<template>
<div v-if="error">Oops! Error encountered: {{ error.message }}</div>
<div v-else-if="data">
Data loaded:
<pre>{{ data }}</pre>
</div>
<div v-else>Loading...</div>
</template>
当有多个组件需要获取数据时,这样很繁琐,把它抽取成组合式函数:
//fecth.js
import { ref } from 'vue'
export function useFecth(url){
const data = ref(null)
const error = ref(null)
fecth(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
在组件中:
<script set up>
import { useFetch } from './fetch.js'
const {data, error} = useFetch('...')
</script>
此时fetch接收静态url,只执行一次请求就完成,如果想要在每次url变化时都能重新请求,可让它同时允许接受ref作为参数:
// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
function doFetch() {
// 在请求之前重设状态...
data.value = null
error.value = null
// unref() 解包可能为 ref 的值
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
if (isRef(url)) {
// 若输入的 URL 是一个 ref,那么启动一个响应式的请求
watchEffect(doFetch) //立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
} else {
// 否则只请求一次
// 避免监听器的额外开销
doFetch()
}
return { data, error }
}
约定和最佳实践
命名
驼峰命名法,“use”作为开头
输入参数
最好在输入参数时兼容ref,使用unref()工具函数:
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 确实是一个 ref,它的 .value 会被返回
// 否则,maybeRef 会被原样返回
const value = unref(maybeRef)
}
如果接受ref做参数,产生响应式effect,确保使用watch()显示监听ref,或在watchEffect()中调用unref()进行正确追踪
返回值
在组合式函数中使用ref而不是reactive,在对象被解构为ref后仍具有响应性。
副作用
例如,添加DOM监听事件或请求数据
使用限制
组合式函数应当被同步调用(我的理解:import一个组合式函数后必须要调用)
目的:让Vue确定当前正在被执行的是哪个实例,从而:1.将生命周期钩子注册到该实例;2.将计算属性和监听器注册到该组件实例上,以便在卸载时停止监听,避免内存泄漏。
通过抽取组合式函数改善代码结构
组合式API有足够的灵活性,基于逻辑问题将代码拆分成更小的函数:
<script setup>
import { useFeatureA } from './featureA.js'
import { useFeatureB } from './featureB.js'
import { useFeatureC } from './featureC.js'
const { foo, bar } = useFeatureA()
const { baz } = useFeatureB(foo)
const { qux } = useFeatureC(baz)
</script>
在选项式API中使用组合式函数
组合函数必须在setup()中调用,返回的绑定必须在setup()中返回, 以便暴露给 this
及其模板
import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
setup() {
const { x, y } = useMouse()
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
// setup() 暴露的属性可以在通过 `this` 访问到
console.log(this.x)
}
// ...其他选项
}
与其他模式比较(略略略)
自定义指令
介绍
回顾:两种在vue中重用代码的方式:
组件:主要的构建模块
组合式函数:侧重于有状态的逻辑
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
在中,任何以v开头的驼峰式命名变量都可被用作一个自定义指令;在没有的情况下,自定义指令需通过directives选项注册:
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
/*将自定义指令全局注册到应用层级*/
const app = createApp({})
//使v-focus 在所有组件可用
app.directive('focus',{
/* ... */
})
指令钩子
//一个指令的定义对象可以提供几种钩子函数 (都是可选的)
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
钩子参数
el
: 指令绑定到的元素。这可以用于直接操作 DOMbinding
:一个对象,包含以下属性value
:传递给指令的值 , 例如在v-my-directive="1 + 1"
中,值是2
oldValue
:之前的值,仅在beforeUpdate
和updated
中可用arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是"foo"
modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
instance
:使用该指令的组件实例dir
:指令的定义对象vnode
:代表绑定元素的底层 VNodeprevNode
:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用
除了 el
外,其他参数都是只读的
简化形式
对于自定义指令来说 ,有时 仅仅需要在 mounted
和 updated
上实现相同的行为,除此之外并不需要其他钩子 ,则 可以直接用一个函数来定义指令 :
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
对象字面量
如果指令需要多个值,可以向它传递一个 JavaScript 对象字面量
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
在组件上使用
<MyComponent v-demo="test" />
<!-- MyComponent 的模板 -->
<!-- 这是分割线--------------------------- -->
<!-- MyComponent 的模板 -->
<div> <!-- v-demo 指令会被应用在此处 -->
<span>My component content</span>
</div>
组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过
v-bind="$attrs"
来传递给一个不同的元素。总的来说,不推荐在组件上使用自定义指令
插件
介绍
能为Vue添加全局功能的工具代码,安装示例:
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})
一个插件可以是一个拥有 install()
方法的对象,也可以直接是一个安装函数本身 , 安装函数会接收到安装它的应用实例和传递给 app.use()
的额外选项作为参数
const maPlugin = {
install(app, options){
//配置此应用
}
}
插件发挥作用的常见场景 :
- 通过
app.component()
和app.directive()
注册一到多个全局组件或自定义指令。 - 通过
app.provide()
使一个资源可被注入进整个应用。 - 向
app.config.globalProperties
中添加一些全局实例属性或方法 - 一个可能上述三种都包含了的功能库 (例如 vue-router)
编写一个插件
//从设置插件对象开始
// plugin/i18n.js
export default{
install:(app, options) =>{
//编写插件代码
}
}
希望有一个翻译函数,这个函数接收一个以 .
作为分隔符的 key
字符串,用来在用户提供的翻译字典中查找对应语言的文本。期望的使用方式 :
<h1>{{ $translate('greetings.hello') }}</h1>
这个函数应当能够在任意模板中被全局调用。这一点可以通过在插件中将它添加到 app.config.globalProperties
上来实现 :
// plugin/i18n.js
export default{
install: (app, options) =>{
// 注入一个全局可用的 $translate() 方法
app.config.globalProperties.$translate = (key) => {
// 获取 `options` 对象的深层属性
// 使用 `key` 作为索引
return key.split('.').reduce((o, i) => {
if(o) return o[i]
}, options)
}
}
}
用于查找的翻译字典对象则应当在插件被安装时作为 app.use()
的额外参数被传入:
import i18nPlugin from './plugins/i18n'
app.use(i18nPlugin, {
greetings: {
hello: 'Bonjour!'
}
})
这样,一开始的表达式 $translate('greetings.hello')
就会在运行时被替换为 Bonjour!
了 !
插件中的Provide / Inject
可以将插件接收到的 options
参数提供给整个应用,让任何组件都能使用这个翻译字典对象
// plugins/i18n.js
export default {
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
app.provide('i18n', options)
}
}
插件用户就可以在他们的组件中以 i18n
为 key 注入并访问插件的选项对象了
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
console.log(i18n.greetings.hello)
</script>
ect
可以将插件接收到的 options
参数提供给整个应用,让任何组件都能使用这个翻译字典对象
// plugins/i18n.js
export default {
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
app.provide('i18n', options)
}
}
插件用户就可以在他们的组件中以 i18n
为 key 注入并访问插件的选项对象了
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
console.log(i18n.greetings.hello)
</script>
总结
今天进一步深入vue学习,了解了通过组合式函数,插件,自定义指令(好像不常用)进一步改善代码,但对于vue逻辑还不是很熟悉,在后续学习中要通过实战操作巩固,学习怎样对网站美化,路由的原理(不知道会不会用到计算机网络),还要能接入后端接口。