本文参考自哔哩哔哩李南江老师视频笔记
上一篇文章vue3项目搭建笔记
知识点
toRaw - 响应式对象转普通对象
如果我们的操作中操作了状态值,但我们不想要页面马上响应,可以使用该api
<div>
{{state}}
<el-button @click="onChangeState">点击state</el-button>
<el-button @click="onChangeRaw">点击raw</el-button>
</div>
setup () {
const init = {
a: 1,
b: 2
}
const state = reactive(init)
const stateRaw = toRaw(state)
console.log(stateRaw === init) //true,表示内存地址一致
function onChangeState () {
state.a++
console.log('init->', init)
console.log('state->', state)
console.log('stateRaw->', stateRaw)
}
function onChangeRaw () {
stateRaw.a++
console.log('init->', init)
console.log('state->', state)
console.log('stateRaw->', stateRaw)
}
return { state, stateRaw, onChangeState, onChangeRaw }
}
点击第一个按钮state发生变化,同时页面更新了,点击第二个,页面没有发生变化,但数据发生了改变。故我们可以使用该api做无需页面刷新的状态变更。
markRaw - 添加对象为不可转为响应式数据的标记
注意:是添加对象,基本数据类型不行,并且这种特性仅停留在根级别。
const data = { a: 1, b: { c: 2 } }
markRaw(data.b) // 该方法也返回原传入参数
const state = reactive(data)
function change () {
// state.b.c++ // 该操作无法更新视图
state.a++ // 该操作可以
}
const data = { a: 1, b: { c: 2 } }
markRaw(data)
const state = reactive(data.b)
function change () {
state.c++ // 该操作可以
}
readonly, shallowReadonly - 不可变数据
- readonly创建不可更改数据
const readonlyData = readonly({ a: 1, b: { c: 2 } })
那么你将无法修改readonlyData的任何参数
- shallowReadonly 只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,类似const
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
customRef 自定义ref
该方法一般推荐独立文件编写,以use开头。
track用于追踪,trigger用于触发响应,set传入新值。
注意get里面尽量不要调用trigger,因为trigger会触发页面更新,页面更新会调用值的get方法,从而出现死循环。这块的优化单独放下面。
function useDataRef (value) {
return customRef((track, trigger) => {
return {
get () {
console.log('get', value)
track(value.a)
return value
},
set (newValue) {
console.log('set', value)
value = newValue
trigger()
return true
}
}
})
}
const data = useDataRef({ a: 1 })
function change () {
// data.value.a++ // 无法更新页面
data.value = data.value.a + 1 // 可以更新页面
// (个人见解)由此可见customRef构造的对象应该是和shallowRef差不多
// 使用时请data.value = 来直接设置值
}
优化
逻辑抽离
- 最简单的(这块只做参考,实际中可能有大量业务逻辑,可以考虑将非业务的剥离出来,然后再对业务二次封装使用)
// 优化前
setup () {
const num1 = ref(0)
function change1 () {
num1.value++
}
const num2 = ref(100)
function change2 () {
num2.value++
}
return { num1, change1, num2, change2 }
}
// 优化后
// 这块代码可以单独文件编写、或者编写于script内
function useNum (init: number) {
const num = ref(init)
function change () {
num.value++
}
return { num, change }
}
// ...
setup () {
const { num: num1, change: change1 } = useNum(0)
const { num: num2, change: change2 } = useNum(100)
return { num1, change1, num2, change2 }
}
- 稍微复杂一点,参考自ahooks的useRequest
<el-button @click="run" :loading="loading">run</el-button>
// ...
import { ref, reactive, onMounted, watchEffect } from 'vue'
function useRequest<R = any> (url: string, option?: Request) {
let result = reactive<R>(null) // 管理数据
const loading = ref(false) // 管理loading
async function run (opt?: Request) { // 调用接口请求
loading.value = true
const response = await fetch(url, { // 请使用axios
...option,
...opt
})
// 测试loading的延迟,实际情况不要
// await new Promise(resolve => {
// setTimeout(() => {
// resolve()
// }, 2000)
// })
loading.value = false
// ...处理逻辑
result = response
return response
}
function reset () {
result.value = undefined
}
return { result, run, reset, loading }
}
// ...
setup () {
const { result, loading, run } = useRequest('/queryUserInfo', {
method: 'post'
})
// 初始话时就调用,其实也可以封装成useRequest的一个参数
onMounted(() => {
run().then(resp => {
console.log(resp)
})
})
watchEffect(() => {
console.log(loading.value)
console.log(result)
})
return { loading, run }
}
- 我们在使用element-plus(其它ui组件也一样)的部分组件时,由于官方封装要考虑大众,我们可以封装一层方便自己使用的hook,比如参考ahooks的useAntdTable来封装element-plus的hook,这里就不做演示
方法封装
- 按传统方式封装axios(这个大家都使用过,举例出来是不想大家受到hook的限制,有过一段时间使用react的hooks时,什么方法都是自定义hooks,这样其实思想就受到了限制。就当水字数)
import axios, { AxiosRequestConfig } from 'axios'
const instance = axios.create({
baseURL: '',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' }
})
const apiRequest = (url: string, options?: AxiosRequestConfig) => {
return instance.request({
url,
...options
})
}
export default apiRequest
- customRef的其它用处,参考自李南江老师视频的一个例子,搬砖
function useDataRef (value) {
return customRef((track, trigger) => {
const { run } = useRequest('/url') // 新增此处开始
run().then(resp => {
value = resp
trigger() // 可以在这调用用了更新页面
}) // 新增此处结束
return {
get () {
console.log('get', value)
track()
return value
},
set (newValue) {
console.log('set', value)
value = newValue
trigger()
}
}
})
}
结语
- 可以使用toRaw、markRaw、shallowReactive、shallowRef优化性能
- 可以使用readonly, shallowReadonly规范不变值
- 可以自定义hook优化代码的结构,使代码更加优雅