-
Notifications
You must be signed in to change notification settings - Fork 546
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
prop "default" value based on other prop #106
Comments
I think that as long as we can ensure key order in objects across all browsers, this is feasible and it aligns with function usage: // both work
function something(a, b = a * 2, c = b * 2) {}
function something({ a, b = a * 2, c = b * 2) {} which would make sense as props are to components what arguments are to functions |
I don't think this would work... taking the example further: export default {
name: 'Knight',
props: {
name: {
type: String,
default: props => props.isKnight ? 'Sir Lancelot' : 'Lady Lancelot'
},
isKnight: {
type: Boolean,
default: props => props.name.includes('Sir'),
},
},
}
If I think you need to decide if |
@michaeldrotar |
In my opinion, the default value should not depend on anything else. It should be simple and clear. For everything else, there are computed properties For example, your problem should be solved as follows: export default {
name: 'Knight',
props: {
name: {
type: String,
default: 'Anonym'
},
isKnightProp: {
type: Boolean,
},
},
computed() {
isKnight() {
return this.isKnightProp !== undefined ? this.isKnightProp : this.name.includes('Sir'),
}
}
} This example is easier to read and understand by a third-party developer. UPD |
I don't think it's a foregone conclusion that computed props are easier to understand than dependent prop defaults. Using a computed prop is logically straightforward from a code point of view but conceptually complex and introduces a lot of unpredictability Firstly and mainly, as mentioned, it matches native javascript's own native function behaviour where function parameter defaults can use previous arguments, and thinking of components as functions and props as parameters is a pretty reasonable mental model. If we're relying on a computed to do this, the mental model of the component becomes more confusing, because you have to name a computed prop that is conceptually identical to the prop it reflects. In the code @cawa-93 posted it's So if we want to keep the API of the component "clean".. what do you call the If we go with a weirdly named computed property instead of a weirdly named prop, developers will have to know to always reference the computed prop rather than the 'smart' prop itself which is counterintuitive. So really, renaming the prop makes more sense from an internal point of view, but less sense from an external point of view — nobody wins. If we could just reference a prop in a default the entire conundrum vanishes, and it doesn't really 'change' anything, so much as it removes a current limitation. We already have This solution is elegant because it doesn't really need its own syntax or API*, just a change to how props are evaluated, and suddenly we have a new ability to express our component logic more succinctly (and if it seems 'wrong' to do it, there can be an Eslint rule called * If it's complex/slow to force evaluation order of props to be based on component options instead of template, then adding an additional prop option that makes the prop evaluated later could be a workaround, but maybe making prop evaluation follow order of prop declaration rather than the template would be better (taking into account props passed through v-bind as well).. that might be a way to backport it into v2 more easily. |
Agree with the confusing names.. that's something I've been struggling with in Vue recently and ties into a similar thread that was recently opened about how to handle casting. I think Ember actually solves this better.. Ember doesn't differentiate what can be passed to a component vs internal data... this can have downsides, but it also lets you simply have a computed with a getter and setter and whether the value is passed externally or handled internally it doesn't matter.. you can do any casting/converting/defaulting without having to have differently named versions of the same thing. So to me.. that's the missing piece. I guess I want computeds to be externally settable? Or I want props to have computed functionality? props: {
isKnight: {
type: Boolean,
getter(self) { // using self instead of just `props` cause maybe you need computeds or methods too?
return self._isKnight !== undefined ? self._isKnight : self.name.includes('Sir');
},
setter(self, value) {
self._isKnight = value;
}
},
age: {
type: Number,
getter(self) {
return self._age;
},
setter(self, value) {
self._age = Number(value);
if (isNaN(self._age) || self._age < 0) {
self._age = undefined;
}
}
} Or an alternate pattern... if we assume the return value of props: {
isKnight: {
type: Boolean,
default(self) {
return self.name.includes('Sir'); // re-compute when `name` dep changes.. once explicitly set, this is never called again
}
},
age: {
type: Number,
setter(self, value) { // maybe a better name than `setter`
const age = Number(value);
if (isNaN(age) || age < 0) {
return undefined;
}
return age;
}
} |
I assume you don't want a simple data: {
isKnight: this.initKnight || this.name.includes('Sir'),
},
props: {
name: String,
initKnight: {
type: Boolean,
default: false
},
} |
@michaeldrotar what you describe sounds a lot like the It's an interesting approach and potentially powerful.. (it would certainly solve everything I'm concerned about) but part of me also thinks that it adds a level of confusion in that the props you pass in can be completely butchered, and that while useful does seem like a potential reduction in simplicity. (ie I would be happy to see that feature though.. but there is a lot of overlap with computed (and is there a reason coercion is synonymous with lying and generally frowned upon culturally? 😅). Basing defaults off other props doesn't seem like overlap with computed and doesn't actually change the API, so it seems rather nice. @Meekohi that is an interesting approach, though my main beef is the external prop called |
I think it is important for this conversation to add that this is currently already possible to try to do. But it is not supported (I think?). Here is a demonstration of doing it where it seems to be working even though I thought #4 would not work, because my understanding was that props are evaluated in the order they are passed to the component. So maybe we could avoid all this conceptual back and forth and do it and call it a bug fix if there are cases where it doesn't work as expected? 🤔(I could have sworn there were, I just can't repro it not working right now.) |
i think the 1:1 comparism with function calls does not fit. while arguments to functions have a strict order that is represented in all its serializations (parameter list on definiton, argument list on call), this is not the case for props, which have no explicit orders:
|
@thedamon - Agree coercion could be abused, but in practice I've never had that issue. It'd be like calling As far as simplicity... I'm torn. Again, I agree on the theory.. but if the "simpler" solution is to have a Common scenario: I'm on a large team where maybe it used to be As to computeds providing the same functionality.. totally agree, issue is that they can't be assigned externally like props. So my other idea was to let computeds be assignable (like Ember does).. (This assumes computeds work.. or could work.. the same as how I proposed earlier.. that they only call get/default when not assigned and then cache the return value of computed: {
isKnight: {
prop: true, // needs a better name but something to say it's assignable like other props
get: (self) => self.name.includes('Sir') // trying a non-this syntax
},
age: {
prop: true,
set(self, value) {
const age = Number(value);
if (isNaN(age) || age < 0) {
return undefined;
}
return age;
}
} Personally I think this is fine too, but it does mean having two places to check in order to know the full scope of what can be passed in... which isn't ideal. Here's an off-the-wall idea.. again inspired by Ember... maybe computed being a section is too limiting and it should be more of a function that can be applied data: function() {
return {
someNumber: 1,
somePlural: computed((self) => self.someNumber === 1 ? 'thing' : 'things')
};
},
props: {
isKnight: computed((self) => self.name.includes('Sir'))
} |
I kinda love the potential ability of having (Though allowing default to be a function achieves almost the same thing, but it’s not clear if it will be re-evaluated when the prop changes... or rather logically it wouldn’t but practically it probably should). (The tricky part of the “working” example I posted above is that it never recomputes)) |
This use case is actually also solved by the suggestion to add 'as' as a prop option for internal naming. But is it also worth addressing that this already sometimes works (at least in Vue 2) and is necessary for using i18n inside a string default? I think the current behaviour should either work, or not work, or should maybe be added to default eslint |
Currently we can set default values like so:
In this example we'd have to pass if a person is a knight or not:
However, very often I need to look at other props and calculate a props' default state based on these other props.
A nice addition to default values for props could be:
The text was updated successfully, but these errors were encountered: