My recent work in Vue (we're still using Vue 2 unfortunately) has caused me to question my understanding of how Vue checks property values and re-renders.
I've got a couple of components on my page which have a v-show clause tied to a get statement in the code:
<my-component v-show="this.isRequired">
public get isRequired(): boolean {
if(this.model.myBooleanProperty == true && this.model.myNumberProperty > 0) {
return true;
}
if(this.model.myOtherBooleanProperty == true && this.model.myOtherNumberProperty > 0) {
return true;
}
return false;
}
Now, my understanding is that Vue would check this function whenever any of the involved properties changed. So if the values of any out of myBooleanProperty, myNumberProperty, myOtherBooleanProperty and myOtherNumberProperty changed then isRequired would be checked and the v-show clause would cause the component to show or not show depending on the outcome.
However, I've learned this isn't the case. By commenting out parts of the function, it seems that only changes to myBooleanProperty, myNumberProperty, myOtherBooleanProperty ever cause isRequired to be checked, even if they're taken out of the function. myOtherNumberProperty never causes it to be checked, even if it's directly manipulated in isRequired by setting it to zero or null.
Can someone please explain what, under these circumstances, causes Vue to reevaluate the value of isRequired?
Don't use this in the template, it's not necessary.
I understand you use vue-property-decorator library, right? Cause a get x property is a class getter, which is compiled down to a Vue computed property.
You are right, computed properties react and reevaluate whenever one of their reactive dependency updates. myOtherNumberProperty should also trigger the computed to reevaluate its value.
Maybe you have a reactivity problem with this property. Check that this value is properly initialized in your data function. If it's missing in the initial model object, Vue won't make it reactive and thus, its changes won't trigger anything.
I can't help further more without additional context on your code.
Related
Consider the following simplification of a problem I have:
export default {
data () {
return {
i_change_alot: 0,
};
},
mounted() {
setInterval(() => {
this.i_change_alot = Math.random();
}, 10);
},
computed: {
someComputedValue() {
this.i_change_alot;
return 'a';
}
}
}
I created a property i_change_alot which is changed every 10 milliseconds to a random value. This means the property becomes extremely reactive and thus it will trigger the computed property.
By simply calling this.i_change_alot I trigger a dependency (for the purposes of example), however the outcome of the computed property never changes.
The end result is that the computed property someComputedValue updates every 10 ms and this triggers a view rerender as far as I am aware.
How can I make it so that someComputedValue is only rerendered when the value/outcome changes?
(The original problem is about showing reactive relative dates, e.g. "1 second ago", "2 seconds ago" and so on. However, after some time, this becomes 30 minutes ago, 31 minutes ago which means for a full minute the string representation has not changed, but it is still rerendered every 10 ms because of the dependent date property).
According to https://github.com/vuejs/vue/issues/11399 I could create a structure with a watcher but it looks so counterintuitive.
Why does Vue trigger the computed when the value is unchanged?
Because Vue will never know if the final result is changed or not until it makes the re-calculation. So the computed variable will be re-calculated every time its dependencies changed and there is no way to avoid it.
The common misunderstanding is Vue cache the computed variables by their value but actually, Vue cache the computed variables by their dependencies' state.
To avoid re-render too frequently
You can create a watcher (as you already know) or wrap your template that uses the computed value into a component.
Why wrap into another component help here?
Because Vue will convert your template to a render function. This function will be re-calculated every time its dependencies change. Sound familiar? Right, it works like a computed variable. The dependencies of a render function are all the variables that you use in the template. So, if you wrap your frequent change variable into a component, Vue will re-render that component only and avoid re-rendering your big component. It will have a big impact on your performance
In beforeRouteEnter event I call to promise/http which take a second to get some data.
After I get the data I pass to the result prop in the component. (using next function).
Also I have a getter that take the value from the prop.
The problem is vue is invoke the getter before it set the value (by next function), so I got undefined.
How can I solve this error?
Here is an code example of the problem
Problem is that when you use next(vm ...) you work with context of page component where is your #Prop defined. You can't modify #Prop value this way. There is some hacks how you can do that but is it really necessary to use #Prop in this case? Why you simply don't use basic variable in page component?
If you replace
#Prop() result
with:
result: Object = {};
everything works great.
If you really need prop here maybe you can just copy prop value to some variable (use watch if you need detect changes) but i don't see any purpose for prop here.
Hope this answer helps you :)
I'm learning VueJS. I'm figuring out their currency validation example code.
Vue.component('currency-input', {
template: `
<span>
$
<input
ref="input"
v-bind:value="value"
v-on:input="updateValue($event.target.value)">
</span>
`,
props: ['value'],
methods: {
// Instead of updating the value directly, this
// method is used to format and place constraints
// on the input's value
updateValue: function (value) {
var formattedValue = value
// Remove whitespace on either side
.trim()
// Shorten to 2 decimal places
.slice(
0,
value.indexOf('.') === -1
? value.length
: value.indexOf('.') + 3
)
// If the value was not already normalized,
// manually override it to conform
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// Emit the number value through the input event
this.$emit('input', Number(formattedValue))
}
}
})
The $emit call at the bottom of the updateValue function, triggers an input event.
When I comment it out, the real time currency validation no longer works. So I realize it has a purpose.
But why trigger an input event inside an input event?
You'd think the input event would fire again, causing the updateValue handler to fire again, causing a stack overflow due to recursive calls.
I understand VueJS's much simpler $emit example code. It's just like Jquery's trigger function.
vm.$on('test', function (msg) {
console.log(msg)
})
vm.$emit('test', 'hi')
// -> "hi"
But in the currency validation example, I do not understand why $emit is used the way it's used, and why it works the way it works.
Can somebody explain?
The Emit call here is to allow you to hook into the event in parent contexts. The Input event is also used by the v-model directive to handle two way binding with components.
v-model='model' is essentially v-bind:value='model' v-on:input='model = $event.target.value' with some added bits to make it play nice. When you remove the this.$emit('input', Number(formattedValue)) You're removing the mechanism that updates the value outside the component.
EDIT: #Jay careful what you wish for sometimes
All elements in HTML have a series of native handlers for the common events; resize, load, unload, etc. These handle what to do when the page changes it's rendering and can be disabled or added onto, since the introduction of JavaScript browsers have used an event pump system that allows multiple functions to be attached to any event which run in sequence when the event is raised. An example being how you can have 3 functions run on resize to handle edge cases such as minimum/maximum size, screen orientation etc.
Form elements generally implement their own base event functions: keydown, keyup, mousedown, mouseup. These base functions invoke events to make our lives easier as developers, these being: input, blur, focus. Some have specialized events as in select elements implementing change, form tags implementing submit.
Input tags on focus capture keyboard input and display the text input cursor to indicate that it's ready to receive input. It adds in handlers for the tab keycode which finds the next available input and shifts focus to that element. The event pump style function system is great here as it allows you to bind to focus and do things like change the background color or border when the input is focused without having to implement the code for capturing input or displaying the cursor yourself.
Input tags also raise the input event when you type in them indicating that the input has changed, telling the browser to change the value and update the display so that the functionality expected by the user is consistent.
In the currency-input example we are adding the updateValue function to work with the native function and process the input value of the event, in the updateValue function we modify the string representation of the value and need someplace to put it. You could simply add a data property to hold the value and bind the input's value property to the data property allowing the currency-input to internally handle the display of the result but that would lock the value behind a private accessor and you would be unable to modify or retrieve the value of the resulting currency formatted value.
Using this.$emit('input', Number(formattedValue)) the updateValue function is acting similar to the native input tag by raising an event that can be captured by the parent context and worked with. You can store it in a value, use it as the basis for a function, or even ignore it completely though that may not help much. This allows you to keep track of the value of the input and modify it as needed or send it to the server, display it, etc.
It also ties into a few directives most pertinently v-model which is syntactic sugar to allow for a value property binding and an input event binding to a data property inside the current context. By providing a value prop and emitting an input event a custom element can act similar to a native form element in the systems of a Vue application. An extremely attractive feature when you want to package and distribute or reuse components.
It's a lot nicer to go:
...
<currency-input v-model='dollarValue'></currency-input>
<input v-model='dollarValue'>
...
Than to have to add in value and input bindings everywhere ergo:
...
<currency-input v-bind:value='dollarValue' v-on:input='updateDollarValue($event.target.value)'></currency-input>
<input v-bind:value='dollarValue' v-on:input='updateDollarValue($event.target.value)'>
...
Now that my weird rambling is done, I hope this helped with understanding some of the patterns and reasoning behind the currency-input example.
I have the following code in my Aurelia app:
import {digit} from "../util/random" // generates a random digit [0,9]
export class UnitedStates {
zipCode() {
return ""+digit(5);
};
zipPlus4() {
return this.zipCode()+"-"+digit(4);
};
...
}
Getting the error TypeError: Cannot read property 'zipCode' of null on the body of zipPlus4. Inspection in the developer tool reveals that this is null.
Why is this null, and how do I fix this?
This is an issue with the transpiler. Recall that the arrow function notation maintains lexical this (or read here for more). So, when Babel and other transpilers enter a new scope, they maintain a local this variable so that it will be accessible from any child scopes. Usually, this variable is _this.
Sometimes, debuggers like DevTools have a hard time understanding the sourcemaps, and they report a null this. Turns out, this is only when inspecting the code; when running the code, it works properly.
So how do you inspect the value of this when it is reported to be null? Inspect _this, usually from the console. That will give you the true this value that is being used.
OK, this should be an interesting one I think. I want to minimize the number of invocations to my Seam component inside an iteration. I am using Seam 2.2.1.CR1, Richfaces 3.3.3.Final, JSF 1.2 and Facelets.
Please take a look the following Facelet snippet:
<rich:datatable value="#{myBean.products}" var="prod">
<rich:column rowspan="#{rowspan.calcHomePageProductRowspan(prod)}">
#{prod.name}
</rich:column>
<rich:column rowspan="#{rowspan.calcHomePageProductRowspan(prod)}">
#{prod.number}
</rich:column>
...
<rich:column rowspan="#{rowspan.calcHomePageProductRowspan(prod)}">
#{prod.somethingElse1}
</rich:column>
<rich:column rowspan="#{rowspan.calcHomePageProductRowspan(prod)}">
#{prod.somethingElse2}
</rich:column>
...
<rich:column rowspan="#{rowspan.calcHomePageProductRowspan(prod)}">
#{prod.somethingElse3}
</rich:column>
</rich:datatable>
In the above code I am computing an attribute (here the rowspan, but that doesn't really matter, it could be any attribute or value at all, so please don't focus on that) by evaluating an EL expression. As you can see, the method that calculates the value takes the current prod as an argument.
I have made an internal optimization in the rowspan Seam component, and it keeps in a HashMap all the already computed values for products. So, when the the EL expression is evaluated at the second rich:column, the rowspan first looks up in the HashMap the already computed value, and returns that, instead of re-computing all over again.
Although this is better that re-computing all over again, I still have to make an invocation to a Seam component. Is there a way to somehow invoke the Seam component only once, and somehow retain the computed value for the current iteration?
The analogous to Java would be to define a variable inside the loop at each iteration, and reuse it throughout the iteration.
Note: I have already made other Seam-oriented optimizations such as #BypassInterceptors, the Seam component is in the EVENT scope, so no hierarchical lookups take place etc.
Is there a way to somehow invoke the Seam component only once, and somehow retain the computed value for the current iteration?
Sure, in theory.
But I am not sure I fully understand your question. Which seam component are you talking about? rowspan?
If that is the case, then yeah, its invoked each time you call it, which makes sense. You are looping through a dataTable, and for each row you call it.
Without knowing more details about what you are trying to do, its difficult to suggest an answer. Is the code slow? Is that why you need to optimize further?
Update
Try this, though I am not sure if it works
<rich:dataTable value="#{myBean.products}" var="prod">
<ui:param name="myrowspan" value="#{rowspan.calcHomePageProductRowspan(prod)}"/>
<rich:column rowspan="#{myrowspan}">
#{prod.name}
</rich:column>
</rich:dataTable>
Second update
So if you don't change your code to be a #Out, #Factory, #Unwrap or similar, then this will always be evaluated each time it runs. This is just how JSF works.
That's why they say that you should do this in your getters, because JSF will call this for each JSF Phase.
public List<Foo> getFoo() {
if(this.field != null) {
field = (List)entityManager.createQuery("from Foo").getResultList();
}
return this.field;
}
If you wouldn't have cached the list, and checking for null, JSF would hit the database for each phase and for each row in the data table.
Thus this is just something you have to live with.