Description
I don't want to spam #222 with more comments unrelated to the RFC design than I already did.
If some admin wants to move the comments related toauto
here, please do.
Goal
Enable users to write JS code that is reactive, while remaining as "plain" as possible.
In other words: use toolchain to remove the need to manipulate refs and write .value
.
This is an alternative idea to ref:
which was introduced in #222.
As we shall see it is not an alternative to <script setup>
, as it is totally orthogonal to that proposal.
Example
Here's what the traditional Todo List would look like using this idea and the export
idea from <script setup>
:
<script setup>
import { auto, reactive } from "vue"
let todos = reactive([])
// checkbox showing/hiding completed todos
export let showCompleted = auto.ref(false)
// filtered list of todos
export let shownTodos = auto.computed(() => todos.filter(t => showCompleted || !t.completed))
// count of todos displayed
export let count = auto.computed(() => shownTodos.length)
// textbox for new todo
export let newTodo = auto.ref("")
// button to add said todo
export function add() {
if (newTodo == "") return
todos.push({ name: newTodo, completed: false })
newTodo = ""
}
</script>
Observe that outside of variable initializations, which uses auto
, it's plain JS code. You can read if (newTodo == "")
and write newTodo = ""
reactive variables normally, in fact newTodo
is typed as plain string in this code.
If you're familiar with Vue 3 reactivity, also observe that it's the same initialization, except we've swapped auto.ref
for ref
and auto.computed
for computed
.
API
This introduce a single new function called auto
:
function auto<T>(value: Ref<T>): T
It takes a ref and unwraps its value, except the result is still reactive.
Because auto(ref(x))
and auto(computed(() => y))
would be very common, two shortcuts functions are provided that perform just that: auto.ref
and auto.computed
.
Sometimes you need to access a ref directly, not its value. For example if you want to pass it to a function that expects reactive inputs. For this purpose, ref()
is re-purposed and will return the initial value
that was passed to auto()
.
// Example function that takes Ref as input
function useLength(text: Ref<string>) {
return computed(() => unref(text).length)
}
// How it can be called with auto refs:
let x = auto.ref("abcd")
let length = useLength(ref(x))
// Alternatively, you can keep the ref in another variable if you prefer:
let xRef = ref("abcd")
let x = auto(xRef)
let length = useLength(xRef)
Also, if you write advanced code that manipulates refs in weird ways, you can mix regular refs code with auto refs code to your heart's content, it's just javascript after all.
Implementation
Plain JS variables can't be reactive.
This all works because the code is instrumented by a JS transform that would be provided by a loader, e.g. vue-auto-loader
. In fact, this can all be done without core Vue support.
That JS loader will track variables that are initialized with auto
calls.
- The initializer
auto()
call is removed.let x = auto.ref(3)
becomeslet x = ref(3)
andx
is actually the ref at runtime. - Any read or write from such a variable
x
is replaced by a read or write tox.value
(except pattern 3 below). - Calls to
ref(x)
wherex
is a tracker auto-ref are removed and replaced byx
.
Those changes are visible when debugging code without source maps. It's pretty trivial transformations and the result is idiomatic Vue code.
Using auto()
in any place other than a variable initializer is an error that would be reported by the loader.
Tooling
The original code is valid Javascript, with the correct and expected semantics.
This means that you can edit this in any IDE: Atom, Sublime, VS Code, etc, without any plugin and still get complete language support (completion, refactorings, code navigation, etc).
You can use any language: Javascript, Typescript or even Coffeescript if you like, as the loader operates on the resulting JS code.
This works in any place, including .vue
SFC but also regular standalone JS/TS files, which is handy if you write composable functions or template-less components.