其他章节请看:
vue3 快速入门 系列
他API
前面我们已经学习了 vue3 的一些基础知识,本篇将继续讲解一些常用的其他api
,以及较完整的分析vue2 和 vue3 的改变。
浅层响应式数据
shallowRef
shallow 中文:“浅层的”
shallowRef:浅的 ref()。
先用 ref 写个例子:
# 组件A
a: {{ a }}
o: {{ o }}
import {ref, shallowRef} from 'vue'
let a = ref(0)
let o = ref({
name: 'p',
age: 18
})
function change1 (){
a.value = 1
}
function change2 (){
o.value.name = 'p2'
}
function change3 (){
o.value.age = 19
}
function change4 (){
o.value = {name: 'p3', age: 20}
}
这4个按钮都会触发页面数据的变化。
现在将 ref 改成 shallowRef
,其他都不变。你会发现只有 change1 和 change4 能触发页面数据的变化:
// 不变
import {ref, shallowRef} from 'vue'
let a = shallowRef(0)
let o = shallowRef({
name: 'p',
age: 18
})
function change1 (){
a.value = 1
}
function change2 (){
o.value.name = 'p2'
}
function change3 (){
o.value.age = 19
}
function change4 (){
o.value = {name: 'p3', age: 20}
}
这是因为 change1 中的 a.value
是浅层,而 change2 中的 o.value.name
是深层。
对于大型数据结构,如果只关心整体是否被替换,就可以使用 shallowRef,避免使用 ref 将大型数据结构所有层级都转成响应式,这对底层是很大的开销。
shallowReactive
知晓了 shallowRef,shallowReactive也类似。
shallowReactive:浅的 reactive()。
请看示例:
现在3个按钮都能修改页面数据:
# 组件A
o: {{ o }}
import {reactive} from 'vue'
let o = reactive({
name: 'p',
options: {
age: 18,
}
})
function change2 (){
o.name = 'p2'
}
function change3 (){
o.options.age = 19
}
function change4 (){
o = Object.assign(o, {name: 'p3', options: {age: 20}})
}
将 reactive 改为 shallowReactive:
import {shallowReactive} from 'vue'
let o = shallowReactive({
name: 'p',
options: {
age: 18,
}
})
现在只有 change2 和 change4 能修改页面数据,因为 change3 是多层的,所以失效。
只读数据
readonly
readonly : Takes an object (reactive or plain) or a ref and returns a readonly proxy to the original.
readonly 能传入响应式数据,并返回一个只读代理
请看示例:
# 组件A
name: {{ name }}
copyName: {{ copyName }}
import {ref, readonly} from 'vue'
let name = ref('p')
// 传入一个响应式的数据,返回一个只读代理
// reactive 数据也可以
// name 数据的修改,也会同步到 copyName
let copyName = readonly(name)
// 类型“number”的参数不能赋给类型“object”的参数。ts
// let copyName = readonly(2)
function change1(){
name.value = 'p2'
}
function change2(){
// 通过代理修改数据
// vscode 报错:无法为“value”赋值,因为它是只读属性。ts
copyName.value = 'p3'
}
浏览器呈现:
# 组件A
name: p2
// 按钮1
change name
copyName: p2
// 按钮2
change copyName
点击第一个按钮,发现 copyName 的值也跟着变化了(说明不是一锤子买卖),但是点击第二个按钮,页面数据不会变化。浏览器控制台也会警告:
[Vue warn] Set operation on key "value" failed: target is readonly. RefImpl{__v_isShallow: false, dep: Map(1), __v_isRef: true, _rawValue: 'p2', _value: 'p2'}
readonly 只读代理是深的:任何嵌套的属性访问也将是只读的。对比 shallowReadonly 就知道了。
Tip:使用场景,比如同事A定义了一个很重要的数据,同事B需要读取该数据,但又担心误操作修改了该数据,就可以通过 readonly 包含数据。
shallowReadonly
readonly 只读代理是深层的,而 shallowReadonly 是浅层的。也就是深层的 shallowReadonly 数据不是只读的。
请看示例:
# 组件A
obj: {{ obj }}
import {ref, reactive, shallowReadonly} from 'vue'
let obj = reactive({
name: 'p',
options: {
age: 18,
}
})
let copyObj = shallowReadonly(obj)
function change1(){
// vscode 会提示:无法为“name”赋值,因为它是只读属性。ts
copyObj.name = 'p2'
}
function change2(){
copyObj.options.age = 19
}
通过 shallowReadonly 创建一个备份数据,点击第一个按钮没反应,点击第二个按钮,页面变成:
# 组件A
obj: { "name": "p", "options": { "age": 19 } }
shallowReadonly 只处理浅层次的只读。深层次的不管,也就是可以修改。
疑惑
:笔者的开发者工具中, copyObj -> options 中的 age 属性没有表示能修改的铅笔图标。应该要有,这样就能保持和代码一致
原始数据
toRaw
toRaw() can return the original object from proxies created by reactive(), readonly(), shallowReactive() or shallowReadonly().
用于获取一个响应式对象的原始对象。修改原始对象,不会在触发视图。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
比如这个使用场景:
# 组件A
obj: {{ obj }}
import {reactive, toRaw} from 'vue'
let obj = reactive({
name: 'p',
age: 18,
})
// 不用担心修改了数据从而影响到使用 obj 的地方
function handle1(o: any){
// 修改数据
o.age += 1
// o: {name: 'p', age: 19}
console.log('o: ', o)
// 例如发送请求
}
markRaw
Marks an object so that it will never be converted to a proxy. Returns the object itself.
标记一个对象
,使其永远不会被转换为proxy。返回对象本身。
- 有些值不应该是响应式的,例如一个复杂的第三方类实例,或者一个Vue组件对象。
import {reactive} from 'vue'
let o = {
getAge() {
console.log(18)
}
}
// Proxy(Object){getAge: }
let o2 = reactive(o)
- 当使用不可变数据源呈现大型列表时,跳过代理转换可以提高性能。
请问输出什么:
import {reactive} from 'vue'
let o = {
name: 'p',
age: 18,
}
let o2 = reactive(o)
console.log(o);
console.log(o2);
答案是:
{name: 'p', age: 18}
Proxy(Object){name: 'p', age: 18}
通过 reactive 会将数据转为响应式。
请看 markRaw 示例:
import {reactive, markRaw} from 'vue'
// 标记 o 不能被转成响应式
let o = markRaw({
getAge() {
console.log(18)
}
})
let o2 = reactive(o)
// {__v_skip: true, getAge: }
console.log(o2);
比如中国的城市,数据是固定不变的,我不做成响应式的,别人也不许做成响应式的。我可以这么写:
// 中国就这些地方,不会变。我自己不做成响应式的,别人也不许做成响应式的
let citys = markRow([
{name: '北京'},
{name: '上海'},
{name: '深圳'},
...
])
customRef
自定义 ref 可用于解决内置 ref 不能解决的问题。
ref 用于创建响应式数据,数据一变,视图也会立刻更新。比如要1秒后更新视图,这个 ref 办不到。
先用ref写个例子:input 输入字符,msg 立刻更新:
# 组件A
msg: {{ msg }}
import {ref} from 'vue'
let msg = ref('')
现在要求:input输入字符后,等待1秒msg才更新。
我们可以用 customRef
解决这个问题。
实现如下:
# 组件A
msg: {{ msg }}
import {ref, customRef, } from 'vue'
let initV服务器托管alue = ''
// customRef 传入函数,里面又两个参数
let msg = customRef((track, trigger) => {
return {
get() {
// 告诉 vue 这个数据很重要,要持续关注,数据一旦变化,更新视图
track()
return initValue
},
set(newValue) {
setTimeout(() => {
initValue = newValue
// 告诉vue我更新数据了,你更新视图去吧
trigger()
}, 1000)
}
}
})
customRef() 接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
track()
和 trigger()
缺一不可,需配合使用:
- 缺少 track,即使通知服务器托管vue 更新了数据,但不会更新视图
- 缺少 trigger,track 则一直在等着数据变,快变,我要更新视图。但最终没人通知它数据变了
实际工作会将上述功能封装成一个 hooks
。使用起来非常方便。就像这样:
// hooks/useMsg.ts
import { customRef, } from 'vue'
export function useMsg(value: string, delay = 1000) {
// customRef 传入函数,里面又两个参数
let msg = customRef((track, trigger) => {
// 防抖
let timeout: number
return {
get() {
// 告诉 vue 这个数据很重要,要持续关注,数据一旦变化,更新视图
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉vue我更新数据了,你更新视图去吧
trigger()
}, delay)
}
}
})
return msg
}
使用起来和 ref 一样方便。就像这样:
# 组件A
msg: {{ msg }}
import {useMsg} from '@/hooks/useMsg'
let msg = useMsg('hello', 1000)
Teleport
Teleport 中文“传送”
Teleport 将其插槽内容渲染到 DOM 中的另一个位置。
比如 box 内的内容现在在 box 元素中:
# 组件A
我是组件A内的弹框
我可以利用 Teleport 新增组件将其移到body下面。
# 组件A
{{ msg }}
import {ref} from 'vue'
let msg = ref('我是组件A内的弹框')
function handle1(){
msg.value += '~'
}
现在这段ui内容就移到了 body 下,并且数据链还是之前的,也就是 msg 仍受 button 控制。
Tip:to 必填,语法是选择器或实际元素
Suspense
suspense 官网说是一个实验性功能。用来在组件树中协调对异步依赖的处理。
我们首先在子组件中异步请求,请看示例:
# 父亲
import ChildA from '@/views/ChildA.vue'
# 组件A
import axios from 'axios';
// https://api.uomg.com/ 免费的 API 接口服务
let {data} = await axios.get('https://api.uomg.com/api/rand.music?sort=热歌榜&format=json')
console.log('data: ', data);
Tip:我们现在用了 setup 语法糖,没有机会写 async,之所以能这么写,是因为底层帮我们做了。
浏览器查看,发现子组件没有渲染出来。控制台输出:
// main.ts:14 [Vue 警告]: 组件 : setup 函数返回了一个 Promise,但在父组件树中未找到 边界。带有异步 setup() 的组件必须嵌套在 中才能被渲染。
main.ts:14 [Vue warn]: Component : setup function returned a promise, but no boundary was found in the parent component tree. A component with async setup() must be nested in a in order to be rendered.
data: {code: 1, data: {…}}
vue 告诉我们需要使用 Suspense。
假如我们将 await 用 async 方法包裹,子组件能正常显示。
# 组件A
data: {{ data }}
import {ref} from 'vue'
import axios from 'axios';
let data = ref({})
async function handle1(){
// https://api.uomg.com/ 免费的 API 接口服务
// 先安装:npm install axios
let response = await axios.get('https://api.uomg.com/api/rand.music?sort=热歌榜&format=json')
data.value = response.data
console.log('data: ', data);
}
handle1()
继续讨论异步的 setup()
的解决方案。在父组件中使用 Suspense 组件即可。请看代码:
# 父亲
// 组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。
Loading...
import ChildA from '@/views/ChildA.vue'
子组件也稍微调整下:
# 组件A
data: {{ data }}
import axios from 'axios';
// https://api.uomg.com/ 免费的 API 接口服务
let {data} = await axios.get('https://api.uomg.com/api/rand.music?sort=热歌榜&format=json')
console.log('data: ', data);
利用开发者工具将网速跳到 3G,再次刷新页面,发现先显示Loading...
,然后在显示
# 组件A
data: { "code": 1, "data": { "name": "阿普的思念", "url": "http://music.163.com/song/media/outer/url?id=2096764279", "picurl": "http://p1.music.126.net/Js1IO7cwfEe6G6yNPyv5FQ==/109951169021986117.jpg", "artistsname": "诺米么Lodmemo" } }
注
:数据是一次性出来的,不是先展示 {}
在展示 {...}
。所以我们再看官网,就能理解下面这段内容:
└─
├─
│ └─ (组件有异步的 setup())
└─
├─ (异步组件)
└─ (异步组件)
在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。如果没有 ,则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下,我们可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。
有了 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。
Tip: 在 React 中可以使用 Suspense 组件和 React.lazy() 函数来实现组件的延迟加载。就像这样:
import React, {Suspense} from 'react'
// 有当 OtherComponent 被渲染时,才会动态加载 ‘./math’ 组件
const OtherComponent = React.lazy(() => import('./math'))
function TestCompoment(){
return
loading
}>
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net