Closed DRoet closed 2 years ago
As said in the release notes:
// Note: scripts only, support in template expressions only available in Vue 3
ah derp, totally read over that comment
Technically, to support such syntaxes in Vue 2, we need to:
obj?.a
to obj ? .a
)We don't have the capacity to implement it yet. But contributions are welcome.
Potential workaround includes using lodash's get as stated in https://github.com/vuejs/vue/issues/4638#issuecomment-397770996
Another hack is to use eval
.
As $eval
was taken out in Vue 2, you'll have to create your own mixin so that it can be accessed in all components without having to import it into each one.
i.e.
Vue.mixin({
methods: {
$elvis: p => eval('this.'+p)
}
});
Example template
<template>
<span>{{ $elvis('foo?.bar') }}</span>
</template>
Although its still no substitute for the real operator, especially if you have many occurrences of it
Although its still no substitute for the real operator, especially if you have many occurrences of it
@McPo Just how safe do you think this is? I'm about to use it in production code because I do not want to set computed properties for the numerous nested fields that may be undefined
or null
Although its still no substitute for the real operator, especially if you have many occurrences of it
@McPo Just how safe do you think this is? I'm about to use it in production code because I do not want to set computed properties for the numerous nested fields that may be
undefined
ornull
As far as Im aware, it should be fine, as youre in control of the input. The only issues would be if the developer wasnt aware that its using eval and allows user input to it, but that doesn't really make sense in the case of the elvis operator.
It may have a performance impact though, in that Vue will probably recall the method on every update. I would also suspect theres some overhead in calling eval
in the first place. Providing its not in a tight loop, realistically you're not likely to notice a difference.
Please do not use eval
for that. It generally is bad practice and in this case specifically, it breaks in all browsers that do not support optional chaining. Whether or not you are using Babel or any other transpiler is irrelevant as they cannot transpile strings.
A better way to access properties in a fail-safe way (like with optional chaining) is the following:
<template><div>
{{getSafe(() => obj.foo.bar)}} <!-- returns 'baz' -->
{{getSafe(() => obj.foo.doesNotExist)}} <!-- returns undefined -->
</div></template>
<script>
export default {
data() {
return {obj: {foo: {bar: 'baz'}}};
},
methods: {getSafe},
};
function getSafe(fn) {
try { return fn(); }
catch (e) {}
}
</script>
The getSafe
function catches any exceptions and implicitly returns undefined
if the property access fails. You could also create a mixin (or use the new composition API) to reuse that function.
Please do not use
eval
for that. It generally is bad practice and in this case specifically, it breaks in all browsers that do not support optional chaining. Whether or not you are using Babel or any other transpiler is irrelevant as they cannot transpile strings.A better way to access properties in a fail-safe way (like with optional chaining) is the following:
<template><div> {{getSafe(() => obj.foo.bar)}} <!-- returns 'baz' --> {{getSafe(() => obj.foo.doesNotExist)}} <!-- returns undefined --> </div></template> <script> export default { data() { return {obj: {foo: {bar: 'baz'}}}; }, methods: {getSafe}, }; function getSafe(fn) { try { return fn(); } catch (e) {} } </script>
The
getSafe
function catches any exceptions and implicitly returnsundefined
if the property access fails. You could also create a mixin (or use the new composition API) to reuse that function.
True I forgot to highlight the issue that it requires optional chaining to be supported in the browser. (Which isn't a major issue in my case, although I probably will stop using it anyway)
For deeply nested complex objects, this seems really important. I have objects that are five layers deep. I like the getSafe() method described by troxler, but I would prefer that chaining just work. I mean... It wasn't "really important" to me before it became a feature in the language, but now that it's a feature, using getSafe() or other methods like that that I would use seems messy. So, I don't mean to nag, I'm just a user sharing my priorities. Thanks for all your hard work guys. Maybe I'll just try to prioritize the move to Vue 3.
Using the following for a TS project:
private s<T>(obj: T | undefined): T {
return obj || ({} as T);
}
with VUE expressions that look like:
<q-input
v-model="model.value"
label="Eyeballs"
type="number"
:min="s(s(model).pirate).min"
:max="s(model).max"
/>
Gets a bit nesty beyond the first property, but the linters are happy.
private s
(obj: T | undefined): T { return obj || ({} as T); }
@tvkit that's not exactly good implementation as it won't work with any other falsy
values like empty string, zero, false. So s('').length
will give me undefined
instead of zero.
Using a function is outside the scope of this issue and not a viable replacement. However if someone need this kind of hack, use this :point_down:
const reducer = source => (object, property) => object?.[property] ?? undefined
const optional_chain = (...parameters) => {
const [source, ...properties] = parameters
return properties.reduce(reducer(source))
}
<div :foo="optional_chain({}, 'foo', 'bar', 'baz')" />
@Juraj-Masiar Good point.
@Sceat I actually really like the idea.
I guess the idea is to use optional chaining in Single File Components. But, funny enough, if you import the template from an HTML or PUG file, it works. I'd guess it shouldn't work either.
Using a function is outside the scope of this issue and not a viable replacement. However if someone need this kind of hack, use this 👇
const reducer = source => (object, property) => object?.[property] ?? undefined const optional_chain = (...parameters) => { const [source, ...properties] = parameters return properties.reduce(reducer(source)) }
<div :foo="optional_chain({}, 'foo', 'bar', 'baz')" />
@Sceat It's not work.I fix it.
const optional_chain = (...parameters) => {
const [source, ...properties] = parameters
return properties.reduce((object, property) => object?.[property] ?? undefined, source)
}
const a={b:{c:{d:1}}}
console.log(optional_chain(a,'b','c','d'))
I'm on Vue 3.0.0, and it still doesn't work. Did it work on a later version?
I'm on Vue 3.0.0, and it still doesn't work. Did it work on a later version?
Same here. I thought this was getting fixed with the release of Vue 3. Guess not... 😔
Same here. I thought this was getting fixed with the release of Vue 3. Guess not... 😔
It does work in Vue 3
Same here. I thought this was getting fixed with the release of Vue 3. Guess not... 😔
It does work in Vue 3
Not for me! I'm using vue 3.0.4 and vue-loader 16.1.2 and I get the following compiler error:
Module parse failed: Unexpected token (27:70)
File was processed with these loaders:
* ./node_modules/vue-loader/dist/templateLoader.js
* ./node_modules/vue-loader/dist/index.js
You may need an additional loader to handle the result of these loaders.
| _createVNode("tr", null, [
| _hoisted_4,
> _createVNode("td", null, _toDisplayString(_ctx.currentTime ?? 'null'), 1 /* TEXT */)
| ]),
| _createVNode("tr", null, [
The breaking code in my template is the following:
<tr>
<td>current time</td>
<td>{{ currentTime ?? 'null' }}</td>
</tr>
Changing it to the following fixes the error:
<tr>
<td>current time</td>
<td>{{ currentTime || 'null' }}</td>
</tr>
As you can see in the error message, the template was correctly converted, the render function code contains the optional chaining character as it should.
The error seems to come from webpack, as it states "File was processed with these loaders:"
maybe your babel setup doesn't contain support for transpiling optional chaining operators? hard to say without knowing the setup & config.
Here's a codesandbox where it works fine: https://codesandbox.io/s/vigilant-bash-bjjvl?file=/src/App.vue
Strange. My code compiles just fine using the nullish coalescing operator in Typescript. I only encounter the issue when using the operator in templates.
Presumably because the Vue compiler generates JS and is never run through the TS compiler.
Anyways, thatbw9upe have to be discussed in the vue-next repo as that's about Vue 3
mark. Have the same problem with using ?. in template 2.x. Wait for support
This would definitely be great to have in Vue2! Will there be support added?
Same behaviour for me on the latest Chrome, loader 16, and vue@latest
optional chaining support for Vue2 please! If I were to take a jab at this, is this still the right path?
Optional chaining for Vue2 please!
Try vue-template-babel-compiler
It will enable Optional Chaining(?.)
, Nullish Coalescing(??)
and many new ES syntax for Vue.js SFC
based on Babel
.
Please refer to REAMDE for detail usage
Support for Vue-CLI, Nuxt.js, Webpack, vue-jest
, any environment use vue-loader
.
It is not correct to use the ?.
syntax. The Vue design is to view the data binding on the data already processed by Reactive. It is a stable data model.
But from the perspective of JavaScript grammar, such support should be provided. What is executed is an expression and does not depend on the data model. 😂
But from the perspective of JavaScript grammar, such support should be provided. What is executed is an expression and does not depend on the data model. 😂
/*
* Where to use: Use in vue templates to determine deeply nested undefined/null values
* How to use: Instead of writing parent?.child?.child2 you can write
* isAvailable(parent, 'child.child2')
* @author Smit Patel
* @params {Object} parent
* {String} child
* @return {Boolean} True if all the nested properties exist
*/
export default function isAvailable(parent, child) {
try {
const childArray = String(child).split('.');
let evaluted = parent;
childArray.forEach((x) => {
evaluted = evaluted[x];
});
return !!evaluted;
} catch {
return false;
}
}
<template>
<div>
<span :v-if="isAvailable(data, 'user.group.name')">
{{ data.user.group.name }}
<span/>
</div>
</template>
<script>
import isAvailable from 'file/path';
export default {
methods: { isAvailable }
}
</script>
developit/dlv
This will neglect any typing and therefore not an acceptable option
You can write a method
that returns its value.
example in under
@adjenks
I know that when you write a function, the amount of code written increases but each increase not bad
.
presentation
layercan test
it in the test unit.inside
a wrapper ( function ) , This helps to read the code and the others will understand the reason for this.Checking property presence hardly constitutes as application logic. Having the function as a workaround is one thing, but stating that it is the preferred approach for the sake of clean code is a bit absurd.
@mohamadrezahedayati Clean Code isn't just a book of hard and fast rules. By your logic, Vue components in themselves violate SRP.
@calevio is 100% correct.
In my opinion that is not logic by the mean of "no logic in views". v-if
and v-for
etc. are the type of logic that is meant by this sentence. And to create a method or calculated property is hiding what properties are used. It is therefore rather not clean code.. There is a reason behind the clean code policy and there are exceptions. If you couldn't write conditionals or loops in views you are back to static HTML. So it is common in every template language to write such logic in the view.
Please stop :stop_sign: the discussion about whether this feature is wanted or not. This is expected valid JS embedded in Vue templates and the JS/ECMA-version supported for embedded code in Vue template is just "outdated" and therefore doesn't support the optional chaining syntax.
I'm nut sure if we want to support it in Vue 2... I understand this. But in Vue 3 this is an essential need. (if not already possible, I'm sadly a bit behind due to lacking support of Vuetify
)
One more way to do this, didn't want to use lodash.
Add a mixin / function
Vue.mixin({
methods: {
$get(obj, path, defaultValue = null) {
let result = obj;
for (let piece of path.split('.')) {
result = result[piece];
if (!result || typeof result !== 'object') {
return result || defaultValue;
}
}
return defaultValue;
}
}
});
Use in templates
<template>
<p>{{ $get(company, 'representative.address.street', '(No address)') }}</p>
</template>
May not (probably does not) work for all scenarios, but helped me move on.
Later I'll search replace with \$get\((.*?), '(.*?)'\)
.
Try vue-template-babel-compiler
It will enable
Optional Chaining(?.)
,Nullish Coalescing(??)
and many new ES syntax forVue.js SFC
based onBabel
.Github Repo: vue-template-babel-compiler
DEMO
Usage
Please refer to REAMDE for detail usage
Support for
Vue-CLI, Nuxt.js, Webpack, vue-jest
, any environment usevue-loader
.
This works, thanks a lot! The only reply that didn't suggest various hacks and workarounds.
Let's support this issue to implement it in vite & vue 2 https://github.com/underfin/vite-plugin-vue2/issues/161
In the file node_modules\vue-template-babel-compiler\lib\index.js
184 line find errors
edit errors:[]
good job
The blog post on Vue 2.7 https://blog.vuejs.org/posts/vue-2-7-naruto.html says:
2.7 also supports using ESNext syntax in template expressions.
Will this release solve this?
@robert-niestroj I tested this today and it seems to work in v2.7 (make sure you have vue-loader >=v15.10 installed)
Be aware that you can't use typescript there with Vue 2.7. If you need it use Vue 3.x.
Version
15.8.3
Reproduction link
https://template-explorer.vuejs.org/#%3Cdiv%20id%3D%22app%22%20v-if%3D%22obj%3F.a%22%3E%7B%7B%20msg%20%7D%7D%3C%2Fdiv%3E
Steps to reproduce
Use a v-if that uses optional chaining w/
@vue/cli version 4.2.0
:v-if="test?.length > 0"
What is expected?
no error is thrown
What is actually happening?
following error is thrown: