2-3-3 Vue3 Reactive的值:Ref和Reactive

Posted 沿着路走到底

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2-3-3 Vue3 Reactive的值:Ref和Reactive相关的知识,希望对你有一定的参考价值。

一个是Reactive,那么:

- 它可以通知(trigger)

- vue更新

- vue做其他标准行为

- 完成自定义的行为

- 它可以监听(track)

- vue发生的变化(依赖)

ref - reference

`ref` 是一个工厂方法,它本质是创造一个`Ref`对象。`ref`的目标是管理值。

首先,`ref` 可以像一个正常值一样被使用:

import  ref  from "vue"

export default 
	setup() 
		const msg = ref("hello") // RefImpl
		return () => 
			return <div>msg.value</div>
		
	

上面程序中:

- setup是一个vue3新特性(帮助初始化组件)

- setup可以返回一个render函数,render函数返回的是VNode

jsx的语法`div` 会被babel翻译成`createVNode` 。

`ref` 和一个正常值看上去很像。

ref是值的代理

ref是一个`setter` 和`getter` ,如下面这段代码很好的栓释了ref的内部实现。

当然仅仅有`setter` 和`getter` 是不够的,还需要一些`reactive` 的机制:


function track()



function trigger()


function createRef<T>(val : T)
	let _val : T  = val
	const refObj = 
		set value(v : T)
			console.log('setter called')
			if(_val !== v) 
				trigger()
				_val = 	v
			
		,
		get value() 
			console.log('getter called')
			track()
			return _val
		
	
	return refObj


const a = createRef(0)
a.value = 10
a.value ++
console.log(a.value)

当set的时候trigger,get的时候track

- trigger:驱动vue更新

- track:跟踪vue的更新(Why?)

  - 一个ref可以给多个vue组件用(因此依赖是不确定的)

  - 为什么叫做依赖?(Deps)

    - 因为vue组件依赖ref,因此是ref的依赖

    - ref的依赖应该是一个数组(集合似乎更好!)

  - 为什么不能在ref的构造函数中确定依赖……?

  - 为什么不直接操作依赖而是封装一个`track`方法?

  - 为什么不是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>
    )
  ,

Reactive

Reactive和Ref类似,都是代理模式的Reactive值。代理一个值用`getter` 和`setter` 很方便,代理一个对象呢?——JS提供了Proxy类。

代理一个对象

function createReactive(obj : any) 

	return new Proxy(obj, 
    // get trap
		get : (target, name, receiver) => 
			console.log('get value', name)
			if(name === 'c') 
				return "this is a proxy value"
			
			return Reflect.get(target, name, receiver)
		,
    // set trap
		set : (target, name, value, receiver) :boolean => 
			if(!(name in target)) 
				return false
			

			Reflect.set(target, name, value, receiver)
			console.log('set value to', value, 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()

- 为什么用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)      
  
)
console.log(p.a)

上面程序会`Stack Overflow`

Reative是一个代理对象

const state = reactive(
    counter : 0
)

state.counter ++

上面的程序会触发代理对象的`getter` 然后`setter` ,因为`++` 不是`atomic` 原子操作(记住这个单词: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>
    )
  ,

Ref和Reactive

它们都是`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 ++ // 触发重绘

有一个函数叫做`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
		//const 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>
			</>
		
	
)

另外reactive会自动将ref拆包。

const r = reactive(
    a : ref(0)
)
// 等价于:
const r = reactive(a : 0)

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> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>;
                                        
                                                 
declare type UnwrapRefSimple<T> = 
  T extends 
    Function | 
    CollectionTypes | 
    BaseTypes | 
    Ref |                                                           RefUnwrapBailTypes[keyof RefUnwrapBailTypes] ? 
        T : 
        T extends Array<any> ? 
           
               [K in keyof T]: UnwrapRefSimple<T[K]>;
            : 
           T extends object ? UnwrappedObject<T> : T  

1

以上是关于2-3-3 Vue3 Reactive的值:Ref和Reactive的主要内容,如果未能解决你的问题,请参考以下文章

vue3的ref和reactive以及toRef和toRefs的区别。

Vue3 入门(ref)

vue3+ts 中 ref与reactive 如何指定类型

Vue3通透教程Vue3中的响应式数据 reactive函数ref函数

Vue3 中有场景是 reactive 能做而 ref 做不了的吗?

vue3.2 响应式之 ref reactive toRef toRefs