-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Advanced Reactivity API #24
Comments
Love it! <3
deprecate or remove ? We should deprecate them, but not remove them. While removing them would make them tree-shakable, we are forcing everyone who is currently using these old APIs to switch to the new one (so no advantage of tree-shaking here - they are already using the feature). Forcing people to switch to the new API partially collides with this statement:
And it will make migrating to a 3.0 unnecessarily harder. However, I would be willing to consider a removal if we can make the old API exist in the compatibility build, but even then I'm a bit torn about forcing people to forget about the old API and learn the new API when they start their next project that doesn't require the compatibility build. By deprecating these APIs, we leave people the choice to use this if they feel more comfortable with it, accepting the risk of having to refactor for Vue 4.0. |
Naming of "refs"
Can we call this "reactive ref" or something? or drop in a note about not to confuse them with template refs?
|
|
If I don't misunderstand, the const objRef = value({ value: 123 })
objRef.value.value // very strange As an alternative, if the user can only use the const obj = state({ value: 123 })
obj.value // clear
const countRef = value(0)
countRef.value // clear
const objRef = value({ foo: 1 }) // error: use state() instead of value() for non-primitive values |
The implementation of |
Also thougth about this, but this would make it harder for people to write code like this: const options = state('default')
// something happens:
options.value = {
// options object for more specific setup
}
// whereas this would work:
options.value = 500 // that's a prmitive If we limit refs to aceppt only primitive values, people would have to use (and possibly expose on the component) two refs instead of one.
For one, the object returned by We need to be able to check for this so we can expose refs as component properties directly: beforeCreate() {
return {
name: state('Linus')
}.
methods: {
changeName(name) {
this.name = name // we can internally translat this to `name.value = name` if we kow that `name` is a ref
}
} Also, we could |
@LinusBorg The following are allowed: const obj = state({ value: 123 })
obj.value // clear
const countRef = value(0)
countRef.value // clear But not allowed: const countRef = value({ foo: 1 }) // error: use state() instead of value() to handle non-primitive values
const obj = state('primitiveValue') // error: you should use value() to handle primitive values |
but would you allow this? const countRef = value(0)
countRef.value // clear
countRef.value = [1,2]
|
I got your point, this is really a problem. |
If What has to be returned exactly by the watcher? Is it like a computed property created on the fly and observing the resulting value? I'm asking because it already causes some misconceptions with current I like the effect cleanup! How do you know if the watcher is about to be ran or to be stopped instead How often are watchers called? Are calls still delayed every tick so that changing the same value twice will only trigger the watcher once? Is this something we want to support trought an option? Why would someone use |
|
A watcher getter can return anything. The getter fires whenever any dependency changes, and the callback only fires when the return value changes. If the user wants to see if the object has been mutated, they should use a deep watcher (just like with
I can't really think of case where this matters. Is it really necessary?
This is a good point. Currently,
They should not use them directly inside |
It's clearer now but there are still some things not clear for me regarding the usage of state and value. About the watcher firing synchrously by default, putting a nexttick inside the callback would still run the function twice though, wouldn't it? |
Why? |
If the watcher fires synchrously, we may queue up multiple callbacks with nexttick, right? We are delaying the execution but still executing multiple times |
@posva it doesn't matter. Implicit nextTick has to fire a internal sync callback anyway. |
How would you write that? If we do watch(() => obj.a, value => {
nextTick(() => {
console.log(`obj.a is: ${value}`)
})
}) and then |
@posva ah that's right. I guess we do need an option for it then. |
Published: vuejs/rfcs#22 |
One thing we left behind is 2.x support. If |
Don't think so. |
would still dependency tracking work? |
I don't see why not. We do it with |
Review note: I've split this part out of the React hooks like composition API because it is not strictly coupled to that proposal.
Summary
Provide standalone APIs for creating and observing reactive state.
Basic example
Motivation
Decouple the reactivity system from component instances
Vue's reactivity system powers a few aspects of Vue:
Tracking dependencies used during a component's render for automatic component re-render
Tracking dependencies of computed properties to only re-compute values when necessary
Expose
this.$watch
API for users to perform custom side effects in response to state changesUntil 2.6, the reactivity system has largely been considered an internal implementation, and there is no dedicated API for creating / watching reactive state without doing it inside a component instance.
However, such coupling isn't technically necessary. In 3.x we've already split the reactivity system into its own package (
@vue/observer
) with dedicated APIs, so it makes sense to also expose these APIs to enable more advanced use cases.With these APIs it becomes possible to encapsulate stateful logic and side effects without components involved. In addition, with proper ability to "connect" the created state back into component instances, they also unlock a powerful component logic reuse mechanism.
Detailed design
Reactive Objects
In 2.6 we introduced the
observable
API for creating reactive objects. We've noticed the naming causes confusion for some users who are familiar with RxJS or reactive programming where the term "observable" is commonly used to denote event streams. So here we intend to rename it to simplystate
:This works exactly like 2.6
Vue.observable
. The returned object behaves just like a normal object, and when its properties are accessed in reactive computations (render functions, computed property getters and watcher getters), they are tracked as dependencies. Mutation to these properties will cause corresponding computations to re-run.Value Refs
The
state
API cannot be used for primitive values because:Vue tracks dependencies by intercepting property accesses. Usage of primitive values in reactive computations cannot be tracked.
JavaScript values are not passed by reference. Passing a value directly means the receiving function will not be able to read the latest value when the original is mutated.
The simple solution is wrapping the value in an object wrapper that can be passed around by reference. This is exactly what the
value
API does:The
value
API creates a wrapper object for a value, called a ref. A ref is a reactive object with a single property:.value
. The property points to the actual value being held and is writable:Refs are primarily used for holding primitive values, but it can also hold any other values including deeply nested objects and arrays. Non-primitive values held inside a ref behave like normal reactive objects created via
state
.Computed Refs
In addition to plain value refs, we can also create computed refs:
Computed refs are readonly by default - assigning to its
value
property will result in an error.Computed refs can be made writable by passing a write callback as the 2nd argument:
Computed refs behaves like computed properties in a component: it tracks its dependencies and only re-evaluates when dependencies have changed.
Watchers
All
.value
access are reactive, and can be tracked with the standalonewatch
API.NOTE: unlike 2.x, the
watch
API is immediate by default.watch
can be called with a single function. The function will be called immediately, and will be called again whenever dependencies change:Watch with a Getter
When using a single function, any reactive properties accessed during its execution are tracked as dependencies. The computation and the side effect are performed together. To separate the two, we can pass two functions instead:
Watching Refs
The 1st argument can also be a ref:
Stopping a Watcher
A
watch
call returns a stop handle:If
watch
is called inside lifecycle hooks ordata()
of a component instance, it will automatically be stopped when the associated component instance is unmounted:Effect Cleanup
The effect callback can also return a cleanup function which gets called every time when:
Non-Immediate Watchers
To make watchers non-immediate like 2.x, pass additional options via the 3rd argument:
Exposing Refs to Components
While this proposal is focused on working with reactive state outside of components, such state should also be usable inside components as well.
Refs can be returned in a component's
data()
function:When a
ref
is returned as a root-level property indata()
, it is bound to the component instance as a direct property. This means there's no need to access the value via.value
- the value can be accessed and mutated directly asthis.count
, and directly ascount
inside templates:Beyond the API
The APIs proposed here are just low-level building blocks. Technically, they provide everything we need for global state management, so Vuex can be rewritten as a very thin layer on top of these APIs. In addition, when combined with the ability to programmatically hook into the component lifecycle, we can offer a logic reuse mechanism with capabilities similar to React hooks.
Drawbacks
Alternatives
N/A
Adoption strategy
This is mostly new APIs that expose existing internal capabilities. Users familiar with Vue's existing reactivity system should be able to grasp the concept fairly quickly. It should have a dedicated chapter in the official guide, and we also need to revise the Reactivity in Depth section of the current docs.
Unresolved questions
watch
API overlaps with existingthis.$watch
API andwatch
component option. In fact, the standalonewatch
API provides a superset of existing APIs. This makes the existence of all three redundant and inconsistent.Should we deprecate
this.$watch
andwatch
component option?Sidenote: removing
this.$watch
and thewatch
option also makes the entirewatch
API completely tree-shakable.We probably need to also expose a
isRef
method to check whether an object is a value/computed ref.The text was updated successfully, but these errors were encountered: