昨天参加了次视频面试,慢慢可以查漏补缺,继续学习Vue
相关的知识,考虑找个实际的开源项目。
逻辑复用
组合式函数
在 Vue
应用的概念中,"组合式函数"(Composables
) 是一个利用 Vue
的组合式 API
来封装和复用有状态逻辑的函数。
无状态的逻辑:它在接收一些输入后立刻返回所期望的输出
有状态的逻辑:负责管理会随时间而变化的状态,比如跟踪当前鼠标在页面中的位置
鼠标跟踪器示例
直接使用
js
<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>
组合式函数
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 }
}
在组件中使用的方式
js
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
可以嵌套多个组合式函数
js
// 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))
}
js
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
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 }
}
异步状态示例
带参数的组合函数
未抽取组合函数前
js
<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>
抽取组合逻辑
js
// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
外部使用
js
<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>
类似网络请求的API
封装
接收响应式状态
useFetch()
接收一个静态 URL
字符串作为输入------因此它只会执行一次 fetch
并且就此结束。如果我们想要在 URL
改变时重新 fetch
呢?为了实现这一点,我们需要将响应式状态传入组合式函数,并让它基于传入的状态来创建执行操作的侦听器。
传一个ref
js
const url = ref('/initial-url')
const { data, error } = useFetch(url)
// 这将会重新触发 fetch
url.value = '/new-url'
或者接收一个getter
函数
js
// 当 props.id 改变时重新 fetch
const { data, error } = useFetch(() => `/posts/${props.id}`)
可以用 watchEffect()
和 toValue()
API
来重构我们现有的实现
js
// fetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const fetchData = () => {
// reset state before fetching..
data.value = null
error.value = null
fetch(toValue(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
watchEffect(() => {
fetchData()
})
return { data, error }
}
toValue()
使3.3版本新增的API
。设计目的是将ref
或getter
规范化为值
是一个分段函数
- 当参数是
ref
类型,返回ref
的值 - 当参数是函数,调用函数并返回其返回值
- 返回原参数
watchEffect
是 Vue 3
引入的一个函数,它会立即执行传入的函数,并响应式地追踪函数内部依赖的所有响应式数据,这里就是会跟踪url
的变化
小结
- 带实现带参数的响应式组合函数,可以传
ref
或getter
toValue
函数可以接收静态URL
字符串、ref
和getter
watchEffect
函数会立即执行传入的函数,并响应式的追踪函数内部依赖的所有响应式数据
约定和最佳实践
命名
组合式函数约定用驼峰命名法命名,并以"use"作为开头。
输入参数
即便不依赖于 ref
或 getter
的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref
或 getter
而非原始值的情况。可以利用 toValue()
工具函数来实现:
js
import { toValue } from 'vue'
function useFeature(maybeRefOrGetter) {
// 如果 maybeRefOrGetter 是一个 ref 或 getter,
// 将返回它的规范化值。
// 否则原样返回。
const value = toValue(maybeRefOrGetter)
}
为了正确追踪,请确保要么使用 watch()
显式地监视 ref
或 getter
,要么在 watchEffect()
中调用 toValue()
。
返回值
组合式函数始终返回一个包含多个 ref
的普通的非响应式对象,而不是reactive
,这样该对象在组件中被解构为 ref
之后仍可以保持响应性
从组合式函数返回一个响应式对象会导致在对象解构过程中丢失与组合式函数内状态的响应性连接。与之相反,ref
则可以维持这一响应性连接。
ref() 与 reactive()
两者都用于跟踪其参数的更改。
不同之处
ref()
函数可以接受原始类型(最常见的是布尔值,字符串和数字)以及对象作为参数,而reactive()
函数只能接受对象作为参数ref()
有一个.value
属性,必须使用.value
属性获取内容,但是使用reactive()
可以直接访问- 使用
ref()
函数可以替换整个对象实例,但是在使用reactive()
函数时就不行
关于第2个的差异如下
js
// 有效
const x = reactive({ name: 'John'})
x.name = 'Amy'
// x => { name: 'Ammy' }
// 有效
const x = ref({ name: 'John'})
x.value.name = 'Amy'
// x.value => { name: 'Ammy' }
第3点就是返回值用ref
js
// 无效 - x 的更改不会被 Vue 记录
let x = reactive({name: 'John'})
x = reactive({todo: true})
// 有效
const x = ref({name: 'John'})
x.value = {todo: true}
我理解的就是把原始的数据给出去,交给接收者考虑就好
js
const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
副作用
- 如果你的应用用到了服务端渲染
(SSR)
,请确保在组件挂载后才调用的生命周期钩子中执行DOM
相关的副作用,例如:onMounted()
。这些钩子仅会在浏览器中被调用,因此可以确保能访问到DOM
。 - 确保在
onUnmounted()
时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在onUnmounted()
中被移除 (就像我们在useMouse()
示例中看到的一样)。当然也可以像之前的useEventListener()
示例那样,使用一个组合式函数来自动帮你做这些事
使用限制
<script setup>
或setup()
钩子中被调用- 同步
在选项式API中使用组合函数
组合式函数必须在 setup()
中调用,且其返回的绑定必须在setup()
中返回
js
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)
}
// ...其他选项
}