You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This gist is a work in progress. It's goal is to collect information about possible pitfalls when using Vue 3's proxy-base reactivity and Typescript, including solutions/remedies/workarounds.
Ultimately, the goal is to turn this into a part of the Vue documentation.
TOC
Leaking this from the constructor, i.e. in async operations.
ref()/reactive() unwrapping loses private fields type information
Leaking this from the constructor, i.e. in async operations.
// Add initial explanation
Example
classPerson{name: stringconstrcutor(name: string){this.name='Default'// finesetTimeout(()=>{this.name=name// this won't be reactive},0)}}constperson=reactive(newPerson('Tom'))// using `ref()` will have the same problem/effect
Explanation
reactive() returns a new Proxy for the Person instance, and reactivity is only provided for any chnages that happen through the proxy.
Since the constructor will run before reacive_() can even create the proxy, this`` will still refer to the raw oriignal instance.
And any change to the raw, original instance, as opposed to the proxy, will not be reactive.
Solution
If you control the class, and want it to be reactive
Start the async behaviour from a method that you call afterreactive() as been applied.
classPerson{name: stringconstrcutor(name: string){this.name='Default'// fine}init(){setTimeout(()=>{this.name=name// this won't be reactive},0)}}constperson=reactive(newPerson('Tom'))person.init()
Since this change will happen through the proxy person,
this time the change in the timeout callback will be reactive as this
now refers to the proxy, not the raw instance.
If you can't control the code of the class / don't need it to be reactive
This is usually true for external dependencies.
Here, the solution is to either not use reactive() at all, or - when nesting the class instance in reactive state -
use markRaw() to tell Vue not to create a proxy object for this instance:
import{markRaw,reactive}from'vue'classPerson{name: stringconstrcutor(name: string){this.name='Default'// finesetTimeout(()=>{this.name=name// this won't be reactive},0)}}conststate=reactive({person: markRaw(newPerson('Tom'))})// you also need to apply this when you set `state.person` to a new value:state.person=markRaw(newPerson('Jerry'))
However, this means that mutating the class instance will not be reactive at all. In most scenarios with classes from external dependencies, that is a good idea anyway.
When creating an instance of this class, the variable will be of type Person, which does include the private field. However, the Unwraping behaviour of ref and reactive can'T pick that private field up, and consequently, the type returned by ref/reactive no longer matches he original Person class:
constperson=newPerson('Tom')// => type is `Person`constotherPerson=reactive(newPerson('Jerry'))// type is `UnwrapRef<Person>`/** UnwrapRef<Person> turns `Person` into an interface with this shape; { name: string setSecret(secret: string) getSecret(): string }**/
This seems fine, but when you i.e. try and pass this object to to funtion that expects an instance of Person, Typescript will complain:
constperson=newPerson('Tom')constotherPerson=reactive(newPerson('Jerry'))functionhandlePerson(person: Person){/*...*/}handlePerson(person)// => finehandlePerson(otherPerson)// => "missing property `private secret: string` on ...."// this will also fail:constotherPerson: Person=reactive(newPerson('Jerry'))
But when a ref is nested in a reactive object, it's value is unwrapped as a convenience, so using the .value property is not necessary:
interfaceState{switch: boolean}conststate: State=reactive({switch: ref(false)})state.switch// falsestate.switch=true// successfully sets the new value on the nested ref.
Note how the interface doesn't declare a nested Ref - the typings unwrao the value nested in the ref. This is straightforward for simple types, but can create some pitfalls when working with generic types.
// TODO: add examples for pitfalls and mitigation startegies.