Vue Latest
基本介绍
Vue 是一个让然充满惊喜的框架,也是目前国内使用人数最多的前端框架。
- 更快(https://github.com/vuejs/rfcs/issues/89);
- 更小(Treeshakable);
- 更好用、易维护(Composition API + reactive + JSX + typescript)。
在 vue 3 之后,写出的程序,很多时候已经和 react 看不出明显的区别。
例如下面这段程序:
type HelloWorldProps = {
msg: string
}
export default ({ msg }: HelloWorldProps) => {
return <h1>{ msg }</h1>
}
// babel/ts 支持 JSX
// JSX 并不意味着 react,JSX 是 HTTP 0.9 就设计出来的 HTML
// 程序语言本身扩展性是最好的
tsx
react 和 vue 并不决定你的前端架构,它只是作为一个工具去支持你的渲染。MVVM 和 函数式都是前端框架中重要的组成部分。
特性解读
vue 3,即 vue-next。
vue 3 可以说是对 vue 的程序应该如何写,重新下了定义:
- JSX
- Typescript
- Composition API
- reactivity
上面这几个更新并不全是 vue3 带来的,但是我们可以放到一起分析,算是对 vue-next 程序的定义。
why not sfc
可能有人会问?SFC 不香嘛?
首先我们来看一下,在 TS 环境下,SFC 需要一个 shim
文件:
declare module "*.vue" {
import { DefineComponent } from 'vue'
const Component: DefineComponent
export default Component
}
tsx
declare 的作用:告诉 TypeScript 编译器 declare 的部分在源代码之外提供,不需要编译器处理。当遇到 *.vue
文件的时候,TS 编译时会将他们当作一个会 export default Component
的类型。
如果用 tsx 写,就不需要这个 shim。不过多一个 shim 少一个 shim 重要吗?
从架构角度来说很重要。通常你的项目概念越多,意味着设计越差。
在 SFC 中编写 template 和 script 标签,这个方式有两个缺点:
- 不够灵活
- 需要 v-show/v-if/v-for 等;
- 关注点被分离(模板也好、script 也好,都是解决某个关注点的一部分,在 SFC 中被强行分离);
- ts 类型检查
- 函数组件可以在最大程度作用 TS 的类型检查(比如属性检查)
我们来看一个 vue 的计时器程序:
function useCounter(): [Ref<number>, () => void] {
const counter = ref(0)
function increment() {
counter.value++
}
return [counter, increment]
}
export default {
setup() {
const [counter, increment] = useCounter()
return () => <div>
count is: {counter.value}
<button onClick={increment}add></button>
</div>
}
}
tsx
在这个程序中,我们可以看到:
- counter 逻辑的集中管理;
- 强大的封装能力;
- 少量的记忆要求。
compostion api
composition api 是一系列函数式 API 的合集。
有用来初始化的、定义组件的:
- setup
- defineComponent
有支持响应式数据的:
- ref
- reactive
- toRefs
- computed
- watch
- watchEffect
有支持生命周期管理的:
- onMounted
- onUnmounted
总结来说,Composition API:
- 提升组合能力(自定义的 Composition API)
- 提供 Reactive Programming
- 提供函数式(简化 API 设计)
vue 3.0 性能
Vue3 并没有很很明显的提升性能,这个和 vue3 的渲染机制有关(没有时间切片)。
在一些大型前端系统,没有时间切片会导致页面打开执行周期过久。
Reactivity
R eactivity 是 Vue3 提供的核心能力,配合函数式的 Composition API 使用非常方便。
响应式编程
Reactive Programming - 让类型自发的响应环境的变化。
**Reactive:**一个值是 Reactive,那么这个值可以被监听;一个对象是 Reactive,那么这个对象可以被监听。一个函数是 Reactive,那么这个函数在提供 Reactive 的能力,比如创造一个 Reactive 的值或者对象。
Be Reactive !!
让程序变的 Reactive 是很好的一个思路。程序如果不是 Reactive,那么往往是 Passive(被动的)。响应的的反义词为什么是被动?因为 Reactive 代表一部分程序(类型)主动的去通知周边自己做了什么,另一部分类型主动监听变化,主动做出判断并完成操作。
程序变的 Reactive 之后,每个模块好像就活了一样,不需要程序主动下命令,而是程序主动完成工作。从这个角度来看 Reactive 的反义词就是 Passive。
声明式(Declarative)
Reactive 的程序往往就是声明式的。所谓声明式,就是程序员的声明要做什么?不重要做什么?而不是写一大堆计算逻辑。
声明式需要更好的封装:
const arr = []
for (let i = 0; i < 1000; i++) {
arr[i] = i
}
// 声明式
const arr = range(0, 1000)
typescript
声明式会创造语言:
const div = document.createElement('div')
div.style.width = 100
// 声明式
const div = <div style={{ width: 100 }} />
typescript
创造这个词并不够贴切。
声明式往往是 Reactive:
export default {
setup() {
const logined = ref(userStore.logined())
useStore.on(e) {
switch (e.type) {
case 'logined':
logined.value = true
break
case 'logout':
logined.value = false
break
}
}
return () => {
if (logined.value) {
return <div>您好,欢迎光临</div>
} else {
return <div>请登录...</div>
}
}
}
}
tsx
在上面的程序中组件在响应全局用户状态的变化,这样就将组件和全局状态解耦:
- 组件迭代不用关心 useStore 迭代;
- useStore 迭代,组件不需要调整。
其实上面代码还可以做优化,可以实现业务逻辑和组件的强分离:
function useLoginStatus() {
const logined = ref(userStore.logined())
useStore.on(e) {
switch (e.type) {
case 'logined':
logined.value = true
break
case 'logout':
logined.value = false
break
}
}
return login.value
}
export default {
setup() {
const loggedIn = useLoginStatus()
return () => <LoginStatus loggedIn={loggedIn} />
}
}
const LoginStatus = ({ loggedIn }) => {if
tsx
还有一个最大的好处就是声明式可以帮助我们更好的阅读代码。
最后也是最重要的、上升到哲学高度的思考:Reactive 让程序模块自己懂得如何做事。
总结
从前端看:如何理解 Reactivity?和 React 是一个词义吗?
从哲学角度去看,Reactivity 就是让程序知道如何自主做事,应该怎样去做。
个人认为 React 在设计之初就是期望程序可以 Reactivity,所以可以认为是一个词义。
组合能力是什么?Compostion API 提供更好的组合能力,这样说对吗?
组合能力其实就是最大能力的复用,即 “搭积木” 能力。这样肯定是对的。
Ref 和 Reactive
一个值如果是 Reactive,那么这个值应该:
- 可以通知(trigger)
- vue 更新
- vue 做其他标准行为
- 完成自定义行为
- 可以监听(track)
- vue 发生的变化(依赖)
ref(referece)
ref 是一个工厂方法,本质是创造一个 Ref 对象。ref 的目标是管理值。
首先,ref 可以像一个正常值一样被使用:
import { ref } from 'vue'
export default {
setup() {
const msg = ref('hello')
return () => {
return <div>{msg.value}</div>
}
}
}
tsx
在上面的程序中:
- setup 是一个 vue3 新特性(初始化组件);
- setup 可以返回一个 render 函数,render 函数返回的 VNode。
jsx 的语法 div 会被 babel 翻译成 createVNode。
ref 是值的代理
ref 是一个 setter 和 getter,例如下面这段代码就可以很好诠释 ref 的内部实现:
function createRef<T>(val: T) {
let _val: T = val
const refObj = {
set value(v: T) {
console.log('setter called')
_val = v
},
get value() {
console.log('getter called')
return _val
}
}
return refObj
}
const a = createRef(0)
a.value = 10
a.value++ // 非原子操作,一次读写操作
console.log(a.value)
tsx
不过仅有 setter 和 getter 是不够的,还需要一些 reactive 的机制:
function trigger() {}
function track() {}
// ...
const refObj = {
set value(v: T) {
console.log('setter called')
_val = v
trigger()
},
get value() {
console.log('getter called')
track()
return _val
}
}
tsx
当 set 的时候 trigger,get 的时候 track。
- trigger:驱动 vue 更新
- track:跟踪 vue 更新
- 一个 ref 可以给多个 vue 组件使用,因此依赖是不确定的;
- 依赖(Deps)
- vue 组件依赖 ref,因此是 ref 的依赖;
- ref 的依赖应该是一个数组。
- 为什么不能在 ref 的构造函数中确定依赖?
- 构造函数和实际使用可能并不是一个位置,或同一个组件。
- 为什么不自己操作依赖而是封装一个 track 方法?
- 只有 get 的时候才需要操作依赖,实际时机很重要。
- 为什么不是 vue 检查依赖,而是 ref track 更新依赖?
- 发现能力更出色,更 Reactive。
ref 驱动更新示例
import { ref } from 'vue'
export default {
setup() {
const counter = ref(0)
return () => (
<div>
{counter.value}
<button onClick={() => counter.value++}>add</button>
</div>
)
}
}
tsx
Reactive
Reactive 和 Ref 类似,都是代理模式的 Reactive 值。
代理一个值用 getter 和 setter 很方便,代理一个对象,js 提供了 Proxy 类。
如何代理一个对象
function createReactive(obj: any) {
return new Proxy(obj, {
get: (target, name, receiver) => {
if (name ==== 'c') {
return 'this is a proxy value'
}
return Reflect.get(target, name, receiver)
},
set: (target, name, value, receiver): boolean => {
if (!(name in target)) return false
Reflect.set(target, name, receiver)
return true
}
})
}
const o = createReactive({
a: 1,
b: 2,
foo: function() {
console.log('a is', this.a)
}
})
o.a = 100
console.log(o.c)
o.foo()
tsx
为什么用 Reflect.set、Reflect.get 而不用 target[name] 这种形式?
- 可以在 getter 和 setter 间同步 receiver(this 指针)
比较坑的地方:
const p = new Proxy({
a: 1
}, {
get(target, property, receiver) {
console.log('get trap', ...arguments)
return Reflect.get(receiver, property, receiver)
}
})
tsx
上面这段程序会造成栈溢出,因为 receiver 其实就是 proxy 代理对象,会造成循环读取。
Reactive 是一个代理对象
const state = reactive({
counter: 0
})
state.counter++
tsx
上面的程序首先会触发代理对象的 getter,然后再触发 setter,因为 ++ 不是 atomic 原子操作。
具体实现和 ref 一致,Reactive 也会在 getter 中 track,在 setter 中 trigger。
Reactive 实现 Counter
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
counter: 0
})
return () => {
<div>
{state.counter}
<button
onClick={() => {
state.counter++
}}
>
add
</button>
</div>
}
}
}
tsx
Reactive vs Ref
它们都是 vue 提供的 reactive 值,Ref 维护一个值/对象,Reactive 维护一个对象的所有成员。
const obj = ref({
a: 1,
b: 2
})
obj.value.a++ // 不会触发重绘
obj.value = { ...obj.value, a: 1 } // 触发重绘
const obj1 = reactive({
a: 1,
b: 2
})
obj1.a++ // 触发重绘
tsx
有一个函数叫做 toRef,还有一个函数叫做 toRefs,这两个函数可以将值转换为 ref。
import { defineComponent, reactive, toRef, toRefs } from 'vue'
export default defineComponent({
setup() {
const state = reactive({
a: 1,
b: '-'
})
const aRef = toRef(state, 'a')
const bRef = toRef(state, 'b')
// =>
// const refs = toRefs(state)
// const aRef === refs.a
// cosnt bRef === refs.b
return () => {
return <>
<h1>aRef: { aRef.value }</h1>
<h1>aRef: { aRef }</h1>
<h1>bRef: { bRef.value }</h1>
<h1>bRef: { bRef }</h1>
<button onClick={() => state.a++}>state.a++</button>
<button onClick={() => aRef.value++}>aRef.a++</button>
</>
}
}
})
tsx
Reactive 和 Ref 的类型
在 Reactive 和 Ref 中,通过 UnwrapRef 的定义配合 infer 关键字,可以做到两种类型合一。
因此从类型的角度来看 ref 和 reactive 是同一类东西。
export declare function ref<T>(value: T): Ref<UnwrapRef<T>>
export declare function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export declare type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
// 拆包
export declare type UnwrapRef<T> = T extends Ref<infer V> ? UnwrapRefsSimple<V> : UnwrapRefSimple<T>
declare type UnwrapRefSimple<T> =
T extends
Function |
CollectionTypes |
BaseTypes |
Ref |
RefUnwrapBailTypes[keyof RefUnwrapbBailTypes] ?
T :
T extends Array<any> ?
{
[K in keyof T]: UnwraoRefSimple<T[K]>
} :
T extends object ? UnwrappedObject<T>: T
tsx
总结
vue 3 提供的 Reactive 这种编程模式和之前 vue.observable 有什么区别?
其实是整体编程风格的转变。
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: {
click: () => { state.count++ }
},
`count is: ${ state.count }`
})
}
}
tsx
什么是 observable?reactive is observable?
- 可以被观察到的(observable);
- rxjs 中一个 observable 同时依然是 observer。
- 可以观察别人(observer)。