1 前言
真的好久好久没写文章了,看了下发帖记录,上次还是半年前,这半年真的发生了太多的事,对这个社会有了新的理解和想法,也重新审视了下自己,决定重新出发,重拾起键盘,做个总结。
我毕业之后就开始使用React做为主要前端框架进行开发任务,中途跳槽到一家公司用了几个月的Vue2,当时整天就是写增删改查,觉得没意思就走了,下一家继续回归React,最近几个月由于公司业务需要,切换到Vue3开发,但是因为对Vue不熟悉,做一些"骚操作"的时候会感觉格外的力不从心,不知道这么写好不好,会不会出问题。之前看了类React框架的源码就让我对React有了深刻的理解(官方React还没看,打算之后有时间再看),所以源码还是要看的。
需要注意的是,看源码并不是为了让你有吹牛逼的资本,而是让你在遇到具体问题的时候有具体解决方案,并且知道这么干一定是没错的。而不是为什么会这样呢,不知道诶,反正做出来了,搞不懂,就这样吧。如果你看过源码,能非常自信的说,这么干一定行!什么,不行?我的前端崩塌了,尤雨溪又搞了什么骚操作!
之前写源码文章说实话我就是在写流水账,什么多少多少行实现一个啥,然后把代码贴出来,我现在感觉没啥必要,因为会去看源码的人不需要你的流水账,不会去看源码的人也看不懂或者也不关心你怎么实现的。所以现在,我打算写我看了一个库的源码,我学到了一些什么东西,因为我才用Vue3没多久,算是一个Vue小白,所以如果写了一些很基础的内容,请大神轻喷。那么今天先从pinia的源码开始
2 知识点
2.1 effectScope
2.1.1 什么是effectScope
能用于管理某个作用域下的所有副作用,并对其进行统一的处理
ts
let count1 = ref(0)
// 创建一个响应式作用域
const scope1 = effectScope()
scope1.run(() => {
watch(count1, v => {
console.log('xxx count1 changed', v)
})
})
// 触发watch
count1.value++
queueMicrotask(() => {
// 关闭监听
scope1.stop()
queueMicrotask(() => {
// 下面就不会触发监听了(全部解除绑定)
count1.value++
})
})
2.1.2 effectScope与effectScope嵌套
需要知道的是,effectScope
出现的目的是方便库给响应式做一次性接触监听绑定,那么如果scope
与scope
互相嵌套,那么只需要取消第一个scope
的监听,所有子scope
也会全部接触监听,类似子组件卸载
ts
let count1 = ref(0)
let count2 = ref(0)
// 创建一个响应式作用域
const scope1 = effectScope()
scope1.run(() => {
let scope2 = effectScope()
watch(count1, v => {
console.log('xxx count1 changed', v)
})
scope2.run(() => {
watch(count2, v => {
console.log('xxx count2 changed', v)
})
})
})
// 触发watch
count1.value++
count2.value++
queueMicrotask(() => {
// 关闭监听
scope1.stop()
queueMicrotask(() => {
count1.value++ // 不会触发watch
count2.value++ // 不会触发watch
})
})
2.1.3 detached
创建effectScope
作用域的时候可以传入一个参数,这个值默认是false
,这个参数可以让互相嵌套的scope
断开关系
ts
function effectScope(detached?: boolean): EffectScope;
ts
let count1 = ref(0)
let count2 = ref(0)
// 创建一个响应式作用域
const scope1 = effectScope()
scope1.run(() => {
let scope2 = effectScope(true)
watch(count1, v => {
console.log('xxx count1 changed', v)
})
scope2.run(() => {
watch(count2, v => {
console.log('xxx count2 changed', v)
})
})
})
// 触发watch
count1.value++
count2.value++
queueMicrotask(() => {
// 关闭监听
scope1.stop()
console.log('关闭监听')
queueMicrotask(() => {
count1.value++ // 不会触发watch
count2.value++ // 会触发watch
})
})
2.1.4 effectScope.run的返回值
effectScope
的目的是为了给setup
中的所有监听解绑,那么setup
有个特性,就是返回值可以被模板使用到,那么effectScope
同理,run
的返回值就是函数执行的返回值
ts
const scope1 = effectScope()
console.log(scope1.run(() => {
return 1
})) // 1
2.2 runWithContext
允许在非setup
中访问inject
,如果你需要开发Vue
相关的库,同一个页面中存在多个实例,但是你的方法却是公用的,那么这个api
会非常的有用,这防止了单例的诞生,而且全局可以公用同一个方法,我们来看下面的例子
ts
import { type App, createApp } from 'vue'
const testSymbol = Symbol()
const myPlugin = {
install(app: App) {
app.provide(testSymbol, { test: 'testData' })
console.log(inject(testSymbol)) // undefined
app.runWithContext(()=>{
console.log(inject(testSymbol)) // { test: 'testData' }
})
}
}
createApp(App)
.use(myPlugin)
.mount('#app')
2.3 es module变量可修改
其实这个特性我很早就知道,是为了让更多的人知道(为了凑字数),尤雨溪似乎特别喜欢使用这个特性,Vue源码中很多地方都用到
比如现在有下面一个模块,导出了一个 count 变量
- test.js
ts
export let count = 1
我们是不能在别的模块中去修改count的值的
ts
import { count } from './test.js'
try {
count++
} catch (e) {
console.log(e) // TypeError: Assignment to constant variable.
}
但是,我们却可以调用test.js中的方法,去修改count的值
js
export let count = 1
export function add(){
count++
}
js
import { count, add } from './test.js'
console.log(count) // 1
try {
count++
} catch (e) {
console.log(e) // TypeError: Assignment to constant variable.
}
add()
console.log(count) // 2