Недавнее предложение Vue3 для ref-sugar вызвало много дискуссий:Наггетс.Талант/пост/689417…
<script setup>
import Foo from './Foo.vue'
// declaring a variable that compiles to a ref
ref: count = 1
const inc = () => {
count++
}
// access the raw ref object by prefixing with $
console.log($count.value)
</script>
<template>
<Foo :count="count" @click="inc" />
</template>
Заинтересованные студенты могут прочитать обсуждение выше, это снова не будет обсуждаться. Предложение направлено на дальнейшее упрощение ref.Value формулировки, но поскольку поправка JS семантика самого языка, вызвала много споров.
В этой статье мы надеемся предоставить другое решение с возможно меньшей умственной нагрузкой наиболее консервативным способом.
1. Просмотрите vue-composition-api-rfc
Чтение и запись ссылки более избыточны, чем обычное значение, поскольку требуется доступ к .value. Youda очень осторожно относится к использованию синтаксического сахара во время компиляции для решения этой проблемы и ясно дал понять, что такая поддержка не предоставляется по умолчанию.v UE-состав-API-RFC.net show.app/this/#%E5%BC%…
Я полностью согласен с этим.
Однако .value должен быть добавлен в js, а .value не требуется в шаблоне, что, несомненно, вызывает определенную степень путаницы и фрагментации.
2. Решение: превратить в объекты новую строку ('foo'), а затем перехватить
Почему это происходит? По сути, мы не можем перехватывать значения базовых типов данных. Хорошо, тогда мы конвертируем базовый тип данных в соответствующий экземпляр класса-оболочки, а затем перехватываем его.
Например:let str = 'foo'переписать какlet strObj = new String('foo'),В настоящее времяstrObjэто объект, следующий мы пытаемся перехватить, я написал библиотекуre-primitive, пример использования следующий:
const { rePrimitive, watchEffect } = require('../dist/re-primitive.cjs')
// 用 rePrimitive 代替 ref; 并传入String的包装对象 new String('foo')
let proxy = rePrimitive(new String('foo'))
// let proxy = rePrimitive('foo') // 内部做了装箱,可简写去掉 new String()
// rePrimitive 的作用是将对象设置成响应式,并增加 setValue() 修改数据的方法
watchEffect(() => {
console.log('输出 proxy instanceof String:', proxy instanceof String) // true , 可以看出 proxy是String的实例,可以使用所有的String的原型方法
console.log('输出 proxy.valueOf():' + proxy.valueOf())
console.log('输出:proxy.substring(1): ' + proxy.substring(1))
})
console.log('==========')
proxy.setValue('bar') // 响应式修改数据,重新执行 effect 函数
// 输出结果
// 输出 proxy instanceof String: true
// 输出 proxy.valueOf():foo
// 输出:proxy.substring(1): oo
// ==========
// 输出 proxy instanceof String: true
// 输出 proxy.valueOf():bar
// 输出:proxy.substring(1): ar
- rePrimitive: Эквивалент реактивного, исх. Установите базовый тип данных как реактивный, если передается new String('foo'), то proxy по-прежнему является экземпляром String; Поддержка String | Number | Boolean трех типов, можно сократить, чтобы удалить активный бокс.
let proxyStr = rePrimitive('foo') // 等价于 let proxyStr = rePrimitive(new String('foo'))
let proxyNum = rePrimitive(123) // 等价于 let proxyNum = rePrimitive(new Number(123))
let proxyBool = rePrimitive(false) // 等价于 let proxyBool = rePrimitive(new Boolean(false))
- setValue: оперативно изменяет данные и повторно выполняет функцию эффекта.
3. Процесс осуществления восстановления
import { reactive, trigger, track, effect, TrackOpTypes, TriggerOpTypes } from '@vue/reactivity'
// ts 的类型定义可先忽略
interface ReString extends String {
setValue: (value: String) => void
}
interface ReNumber extends Number {
setValue: (value: Number) => void
}
interface ReBoolean extends Boolean {
setValue: (value: Boolean) => void
}
export const watchEffect = effect
export function rePrimitive(prim: String): ReString
export function rePrimitive(prim: Number): ReNumber
export function rePrimitive(prim: Boolean): ReBoolean
export function rePrimitive(prim: String | Number | Boolean) {
const obj = {}
setValue(prim)
function setValue(value) {
// 判断如果是基本数据类型则转成对应的包装对象
if (typeof value === 'string') {
prim = new String(value)
} else if (typeof value === 'number') {
prim = new Number(value)
} else if (typeof value === 'boolean') {
prim = new Boolean(value)
} else {
// 如果是包装对象则不作处理,例如 传的 new String('foo')
prim = value
}
// 这里比较巧妙,将prim设置为obj的原型对象,
// 这样 obj 就是 String 的实例对象, 可以使用 String的所有原型方法,包括 .valueOf()
Object.setPrototypeOf(obj, prim)
// 触发响应式更新
trigger(obj, TriggerOpTypes.SET, '__value__')
}
return new Proxy(obj, {
get(target, key, receiver) {
if (key === 'setValue') {
// 如果 key 是 'setValue', 则返回 setValue 函数,提供响应式更新数据的能力
return setValue
}
track(target, TrackOpTypes.GET, '__value__')
// track(target, 'get', '__value__')
let result = Reflect.get(target, key, receiver)
if (typeof result === 'function') {
// 这里判断如果是函数,则必须处理this指向
return result.bind(prim)
}
return result
},
}) as ReString | ReNumber | ReBoolean
}
В целом, это более изобретательно, потому чтоnew String('foo')Он неизменяем. Чтобы иметь возможность перехватывать и изменять данные через прокси, используйтеObject.setPrototypeOf(obj, prim)модифицироватьobjобъект-прототип.
4. Резюме
- Используйте .valueOf(), чтобы получить значение. Разработчики должны всегда обращать внимание на то, что прокси — это объект String, Number, Boolean, и не забывать вызывать метод .valueOf() для получения фактического значения. Работает с плагинами eslint, чтобы помочь проверить отсутствующие методы .valueOf().
- Изменено с помощью .setValue(). Это решение полностью сохраняет возможности языка js, но добавляет только метод .setValue(), который может быть чуть менее обременительным для разработчика, чем ref
- Эта статья является просто предложением предоставить другое решение, и дружеские обсуждения приветствуются. Реализуйте репозиторий исходного кода:GitHub.com/Rigo24601/…