Recently Vue3’s proposal for Ref-sugar has been widely discussed: juejin.cn/post/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>
Copy the code

Those who are interested can read the above discussion first, which will not be repeated in this article. The purpose of the proposal is to further simplify the writing of ref.value, but because of the modification of the semantics of JS language itself, caused a lot of debate.

This paper hopes to offer an alternative solution that may be less mentally taxing in the most conservative way possible

1. Review the vue – composition – API – RFC

Reading and writing ref is more redundant than normal values because of the need to access.value. Utah is very cautious about using compile-time syntactic sugar to solve this problem, having made it clear that such support is not provided by default. Vue-composition-api-rfc.net lify. App/useful/BC E5 # % % %…

I couldn’t agree more.

However, it is necessary to add.value in JS and not in templates, which undoubtedly creates a degree of confusion and fragmentation.

New String(‘foo’)

Why is that? Fundamentally, we cannot intercept underlying data type values. Ok, so we convert the underlying data type into the corresponding wrapper class instance, and then intercept. For example, let STR = ‘foo’ is rewritten to let strObj = new String(‘foo’), where strObj is the object. Next, we try intercepting. I wrote a library re-primitive as follows:

const { rePrimitive, watchEffect } = require('.. /dist/re-primitive.cjs')

// replace ref with rePrimitive; And pass in a wrapper for String new String('foo')
let proxy = rePrimitive(new String('foo'))
// Let proxy = rePrimitive('foo')
// The function of rePrimitive is to set the object to be responsive and add setValue() to modify the data

watchEffect(() = > {
  console.log(Output proxy instanceof String:, proxy instanceof String) // true, proxy is an instance of String, and you can use all of String's prototype methods
  console.log('output proxy.valueof () :' + proxy.valueOf())
  console.log(Output: proxy.substring(1): ' + proxy.substring(1))})console.log('= = = = = = = = = =')
proxy.setValue('bar') // Execute effect function again

// Output the result

// output proxy instanceof String: true
// output proxy.valueof () : foo
Substring (1): oo
/ / = = = = = = = = = =
// output proxy instanceof String: true
// output proxy.valueof () : bar
Substring (1): ar
Copy the code
  • RePrimitive: Equivalent to Reactive, ref. Set the base data type to reactive, and if new String(‘foo’) is passed, proxy is still an instance of String; Support String | Number | Boolean three types of packing can be abbreviated to remove the initiative.
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))
Copy the code
  • SetValue: Reexecutes the effect function by modifying the data in a responsive manner

3. Undo the implementation process

import { reactive, trigger, track, effect, TrackOpTypes, TriggerOpTypes } from '@vue/reactivity'
// The type definition of ts can be ignored
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) {// Determine if it is a basic data type to the corresponding wrapper objectif (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 {
      // If the object is wrapped, it is not processed, such as passing new String('foo').
      prim = value
    }
    // Set prim as the prototype object of obj,
    // Obj is then an instance of String and can use all of String's prototype methods, including.valueof ().
    Object.setPrototypeOf(obj, prim)
    // Trigger a reactive update
    trigger(obj, TriggerOpTypes.SET, '__value__')}return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === 'setValue') {
        // If the key is 'setValue', the setValue function is returned, providing the ability to update data in a responsive manner
        return setValue
      }
      track(target, TrackOpTypes.GET, '__value__')
      // track(target, 'get', '__value__')
      let result = Reflect.get(target, key, receiver)
      if (typeof result === 'function') {
        // If it is a function, it must handle the this pointer
        return result.bind(prim)
      }
      return result
    },
  }) as ReString | ReNumber | ReBoolean
}
Copy the code

The new String(‘foo’) is immutable. In order to intercept and modify data by proxy, object.setProtoTypeof (obj, prim) is used to modify the prototype Object of obj.

4. To summarize

  • The value is.valueof (). Developers need to be aware that proxy is a String, Number, or Boolean object, and remember to call.valueof () to get the actual value. The esLint plugin helps check for missing writes. ValueOf () methods
  • Change it to.setValue(). This solution keeps the js language features intact, just adds the.setvalue () method, and may be slightly less mentally taxing on the developer than ref
  • This article is just to provide another way to solve the problem, welcome friendly discussion. Implementation source repository: github.com/ruige24601/…