Vue data not changing with Promise inside created [duplicate] - javascript

Basically, when I have a component, let’s call it “TransportComponenet.vue”, and in that component, I have a data() and My properties are carId, transportId. What vue does is makes getters and setters for these properties. Let’s say in this componenet’s view, I type {{carId + transportId}} and also {{carId * transportId}}.
As far As I know, Vue comes to my views, looks at them, and wherever I have getters ( {{carId+ transportId}} or {{carId * transportId}} ) are getters. So vue comes and registers them in component’s watcher. When I somewhere use setters such as this.carId = 5. Vue does the setter function for this property and reevalutes the functions (getters) that were saved before in the watcher. Is this the correct assumption?
What I don’t get is what relationship does exist between Dep class and Watcher class ? I know they both play the big role. I’d really honor the whole explanation “which thing goes where and when and why”.

Reactivity is automatic synchronization between the state and the DOM. That's what the view libraries like Vue and React try to do in their core. They do that in their own ways.
I see Vue's reactivity system as being two fold. One side of the coin is the DOM update mechanism. Let's look into that first.
Let's say you have a component with a template like:
<template>
<div>{{ foo }}</div>
</template>
<script>
export default {
data() {
return {foo: 'bar'};
}
}
</script>
This template gets converted into render function. This happens during build time using vue-loader. The render function for the template above looks something like:
function anonymous(
) {
with(this){return _c('div',[_v(_s(foo))])}
}
Render function runs on the browser and when executed returns a Vnode (virtual node). A virtual node is just a simple JavaScript object that represents the actual DOM node, a blueprint for the DOM node. The above render function when executed would return something like:
{
tag: 'div',
children: ['bar']
}
Vue then creates actual DOM node from this Vnode blueprint and places it into the DOM.
Later, let's say the foo's value changes and somehow the render function runs again. It will give a different Vnode. Vue then diffs the new Vnode with the old one and patches only the necessary changes required into the DOM.
This gives us a mechanism to update the DOM efficiently taking the latest state of things for a component. If every time the render function of a component gets called when any of its state (data, props etc) changes, we have the full reactivity system.
That's where the other side of Vue's reactivity coin comes in. And that is the reactive getters and setters.
This will be a good time to understand Object.defineProperty API if you are not already aware of that. Because Vue's reactivity system relies on this API.
TLDR; it allows us to override an object's property access and assignment with our own getter and setter functions.
When Vue instantiates your component, it walks through all the properties of your data and props and redefines them using Object.defineProperty.
What it actually does is, it defines getters and setters for each of the data and props properties. By doing so, it overrides the dot access (this.data.foo) and the assignment (this.data.foo = someNewValue) of that property. So whenever these two actions occur on that property, our overrides get invoked. So we have a hook to do something about them. We will get back to this in a bit.
Also, for each property a new Dep() class instance is created. It's called Dep because each data or props property can be a dependency to the component's render function.
But first, it's important to know that each component's render function gets invoked within a watcher. So a watcher has an associated component's render function with it. Watcher is used for other purposes as well, but when it is watching a component's render function, it is a render watcher. The watcher assigns itself as the current running watcher, somewhere accessible globally (in Dep.target static property), and then runs the component's render function.
Now we get back to the reactive getters and setters. When you run the render function, the state properties are accessed. E.g. this.data.foo. This invokes our getter override. When the getter is invoked, dep.depend() is called. This checks if there is a current running watcher assigned in Dep.target, and if so, it assigns that watcher as the subscriber of this dep object. It's called dep.depend() because we are making the watcher depend on the dep.
_______________ _______________
| | | |
| | subscribes to | |
| Watcher | --------------> | Dep |
| | | |
|_____________| |_____________|
Which is the same as
_______________ _______________
| | | |
| Component | subscribes to | it's |
| render | --------------> | state |
| function | | property |
|_____________| |_____________|
Later, when the state property gets updated, the setter is invoked and the associated dep object notifies its subscribers about the new value. The subscribers are the watchers which are render function aware and that's how the components render function gets invoked automatically when its state changes.
This makes the reactivity system complete. We have a way to call a component's render function whenever its state changes. And we have a way to efficiently update the DOM once that happens.
This way Vue has created a relationship between a state property and a render function. Vue knows exactly which render function to execute when a state property changes. This scales up really well and basically removes a category of performance optimization responsibility from the hands of developer. Devs don't need to worry about over rendering of components no matter how big the component tree. To prevent this, React e.g. provides PureComponent or shouldComponentUpdate. In Vue, this is just not necessary since Vue knows exactly which component to re-render when any state changes.
But now that we know how Vue makes things reactive, we can think of a way to optimize things a bit. Imagine you have a blog post component. You get some data from the backend and show them on the browser using Vue component. But there is no need for the blog data to be reactive because it most likely won't change. In such situation, we can tell Vue to skip making such data reactive by freezing the objects.
export default {
data: () => ({
list: {}
}),
async created() {
const list = await this.someHttpClient.get('/some-list');
this.list = Object.freeze(list);
}
};
Oject.freeze among other things disables the configurability of an object. You cannot redefine the properties of that object again using Object.defineProperty. So Vue skips the whole reactivity setup work for such objects.
Besides, going through the Vue source code yourself, there are two extremely good resources available on this topic:
Vue Mastery's Advanced component course
FrontendMaster's Advanced Vue.js Features from the Ground Up by Evan You
If you are curious about the internals of a simple virtual DOM implementation, check out the blog post by Jason Yu.
Building a Simple Virtual DOM from Scratch

Related

Is it good practice to pass component instance as props to another component in VueJs?

Assume that we are going to make a text edior.
When typing, Component C(Text input section) needs the state of Component B(Font stlye picker) to determine what style to use.
We can share that state by Vuex, but in OOP, related logic or variables are better to be put in same class, and when other components need the state of that object, just to refer it directly.
If I want to refer components directly, one way is to save some components when they are mounted, and pass them as props to other components as the following.
// in <template>
<FontStylePickerVue ref="stylePicker" />
<TextInputSectionVue
ref="textInputSection"
v-bind="{
tylePickerComp: stylePickerComp
}" />
//in Main Component <script>
mounted(){
this.tylePickerComp = this.$refs.stylePicker;
}
data(){
return { tylePickerComp : null }
}
In Vue, although there are many ways to communicate between components, I never seen any of them passes component instance as props directly like this.
(it works, and very convinient though.)
I know doing like this, in a way, it make system more complicated because components refer each others in a complex relation, but I'm just wondering if there is any design pattern like this in Vue, or doing like this has some cons like low perfermance ,etc.
In the end, in order to clarify the idea, I want to compare what if I pass component as props directly or use common methods in this example:
Pass component as props directly:
Need to save component reference on mounted, but type declartion is once and for all(ex. use InstanceType). Need to deal with null(it's null before mounted).
Event bus:
Emit an event with a promise resolve function as callback to make other components to resovle with their state. This is good because it will keep data or logic in the same class(compare to use Vuex), but you need to write your method in a promise style in order to read resolved result.
Vuex:
Split some variables into global scope, and this is not so compatible for cases which are better to gather logic and data at same place.
Just want to get some idea for this topic, sorry for long article.

What is the role and usage of the setup function provided by vue3's Composition API

I need to know the correct usage and the best practice of the setup function provided by vue3's Composition API.
I checked in my current project where developers actually use the setup function instead of creating the component with the traditional approach.
If it is just a design principle or improvement something then where we should apply these. I read the official documentation but instead, they didn't explain the concept, they just provided the list of arguments available in this function.
MyBook.vue
<template>
<span>Warning:- {{warning}}</span>
<button #click="warning = !warning">toggle</button>
</template>
<script>
import { ref } from 'vue'
export default {
props: ['warning'],
setup(props, context) {
const warning = ref(props.warning)
return {
warning,
}
},
}
</script>
<MyBook
:warning="true"
/>
As you can see above, I can't use the same name of a property to data attribute for a component but in the case of setup, we can do this and update the value. (as property should not change within component).
The Vue devtool is also showing the setup as a different category.
setup sets up an instance and returns properties that it should have. The purpose of Composition API, which setup is a part of, is to replace Options API, where an instance is determined by component options. So setup is the replacement for data, methods, computed, watch and lifecycle hooks.
As the reference explains, setup also replaces beforeCreate and created lifecycle hooks, the rest of hooks are set inside of it.
There is no conflict between data and props in setup function because props is accessible as setup parameter, i.e. warning and props.warning are accessible at the same time. In a template, they aren't and shouldn't be distinguished, they instance properties, the solution is to not allow name conflicts. They have been previously available with $data.warning and $props.warning magic keywords but their use wasn't encouraged. If warning value differs from a prop of the same name, and both should be available in a template, it should have a different name.

Sibling component does not receive emitted changes

In my Angular 9 project I have 2 components which are siblings and the parent component. On change in component A, I emit a value and it's set in the parent component and calls a method in component B. The method in component B emits another value and it's set in the parent component. The on change in component A continues, but the emitted value from component B that is set in the parent component (which is an input in component A) is not changed. I don't know why it's not the input for component A does not change even though the parent updates the value.
Parent Component
setSomeNum(someNum: number) {
// this is run on someNumberEmitter in Component A
this.num = someNum;
if (this.viewChildComponentB) {
this.viewChildComponentB.remove(someNum);
}
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum = someOtherNum
}
Component A
componentAOnChange(someNum: number) {
this.someNumberEmitter.emit(someNum);
// this.inputFromComponentB is the original value instead of the one emitted in Component B (this.someDiffNum)
this.someService.applyCalc(someNum, this.inputFromComponentB);
}
Component B
remove(someNum: number) {
this.someDiffNumEmitter.emit(this.someDiffNum);
this.someService.applyCalc(someNum, this.someDiffNum);
}
I'm using the OnPush change detection strategy, but nothing changed. How can the sibling component A run with the data changes from component B?
I'm not sure why you're using ViewChild there but if it is to update the child components manually when there's change then that should be a red flag something is being done wrong, if you have data that needs to be shared it should be shared across the board and update accordingly on the single source of data changes without having to manually update the rest of the places.
Now to your problem:
If you're using the OnPush change detection strategy you have to update your data in an immutable way or use Observables, otherwise the change detection won't trigger.
Some people will advice triggering change detection manually but I'd recommend avoiding that as the whole point of using OnPush is to avoid a whole page render unnecessarily.
A simple solution I like to use is to use a Subject or BehaviorSubject instead with the async pipe. This way it ensures smooth work with the OnPush change detection strategy because ChangeDetection will run when the Observable emits a new value, and the async pipe takes care of unsubscribing the Observable for you.
If I were to modify your current components, it'd look something like this:
Parent:
num$ = new Subject<number>();
otherNum$ = new Subject<number>();
setSomeNum(someNum: number) {
this.num$.next(someNum);
}
setSomeOtherNum (someOtherNum: number) {
// this is run on someDiffNumEmitter in Component B
this.otherNum$.next(someOtherNum)
}
Then in the HTML you can use the async pipe, like this:
<some-component [num]="num$ | async" [otherNum]="otherNum$ | async"></some-component>
(You could use the async pipe in the component itself, doesn't really matter).
And that's pretty much it. You should have a Subject as an Observable, then share it with child components, once the Observable is updated, the child components data will be updated as well.
One small caveat is that when using a Subject instead of a BehaviorSubject is to make sure to subscribe before emitting any values to the Subject, otherwise the data will not update. So for certain cases BehaviorSubject is a better fit.

Vue2 passing arbitrary named variable as prop

I am new to Vue and after checking the docs I can not figure out how to achieve the following:
pass an arbitrarily named variable as a prop to a component instance.
From my understanding, props are meant to be a way to allow data to be passed to a component and as it states on the website:
Passing Data to Child Components with Props:
Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Since props can be required, it would seem that we can design components under the assumption that some data would be there, and possible within certain parameters (if the validator option is specified).
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
This works if my named object of function has the exact same name as the prop to which I attempt to bind it. However, as I might have multiple instances of the Vue component and I might want to bind different data, I find using the same name for the variable less than ideal.
Now if I do as the Vue warning suggests, and name object / function the same as the prop, then the warning switches to that my data is not defined inside vue and to make sure it is reactive by reading: https://v2.vuejs.org/v2/guide/components-props.html
which, to be honest, doesnt really explain how to solve the issue,
or move the prop to the data level.
Which I can do (still gives the same warning), but kind of defeats the purpose of having props with my understanding of Vue.
This become more frustrating with anonymous vue instances.
e.g.
<script>
export default {
props: {
// records: {
// default: function(){return{}},
// type: Object
// }
},
data: function() {
return {
records: {} // define even an empty value in data for it to be 'reactive'
}
},
computed: {
fields: function() {
},
keys: function() {
return Object.keys(this.records)
}
},
methods: {
}
}
</script>
trying to use this as a component and set records to var myRecords = {"a": {}} fails:
<my-comp :records="myRecords"/>
So how exactly should I circumvent this? Where should I define my data then? and how should I handle the naming in the case of multiple instances?
A more fledged on example is found on a similar question:
Vue2: passing function as prop triggers warning that prop is already set
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
It's hard to give a definitive answer because I don't know the specifics of how you have organized your code. Are you using Webpack? Single file components (.vue)? If yes to any of these, then you needn't use global variables in the way you have described in your question.
Your entire Vue app should consist of a single root Vue instance (which you instantiate with new Vue(...), and from there each component is rendered within the root component's template, and templates of those components, and so on.
Looking at the following template:
<my-comp :records="myRecords"/>
myRecords must be a property on the Vue component instance whose template contains the above. It could be declared within the data block, or as a computed property, or a prop, it doesn't matter.
Here's a small example:
<div id="app">
<my-comp :records="myRecords"></my-comp>
</div>
// Obtain records in some way and store it in a global variable
var records = ...
// This is the root Vue instance
new Vue({
el: '#app',
data: {
// You must store the records array in the Vue component like this
// for it to be referenced within the template.
// You can optionally transform the data first if you want.
myRecords: records.filter(r => r.name.startsWith('Bob'))
// ^ ^
// | |
// | +--- the global variable
// |
// +---- the name of the property on the component instance
}
})
Note that MyComp component does not access the records global variable in any way, it only takes its input through the records prop.

Custom function for onPush change detection in Angular 2

{ id:123, version 1, ...}
I am writing an Angular 2 component that uses the onPush change detection strategy. I'd like to know if there is a way to use custom logic with onPush when determining if an object has changed. My understanding is that onPush checks the object reference an so only updates when a new instance of an object is sent.
My app tracks its own versioning for objects which means that every object has an id and version number. I periodically refresh data from the server which means that a new object is instantiated even though the data has not actually changed. This means there are cases with the default onPush that would see a new instantiated object and think it has to update. In actuality the id and version number are the same so even though the the object reference has changed I can skip updating the component.
I'd like to be able to provide a custom function to onPush that essentially uses a function like this to do change checking.
(obj1, obj2) => obj1.id === obj2.id && obj1.version === obj2.version
Is it possible to customize the onPush logic, or is there another way to achieve this so that I don't need to unnecessarily update the component when the object reference changes but the data does not.
I don't think that it's possible to create a custom changeDetectionStrategy . But if you do track the change on your objects by yourself you can do something (probably) even more optimized.
You can inject the ChangeDetectorRef and tell Angular that your component shouldn't be watched anymore (which is pretty cool and powerful !).
constructor(private _cd: ChangeDetectorRef)
ngOnInit() {
this._cd.detach();
}
ngOnChanges() {
// check if your objects have changed here
// if they've changed, tells Angular to check
// the bindings manually
this._cd.detectChanges();
}

Categories

Resources