Vue template vs Vue jsx - vue.js

could you explain Vue template vs Vue function jsx, what is different of it ? which one is good for use ?
Ex :
I have two components :
Component1.vue
<template>
<div>
<p>{{message}}</p>
</div>
<template>
<script>
export default {
name:'Component1',
data(){
return{
message:'This is component1'
}
},
}
</script>
Component2.vue
export default {
name:'Component2',
data(){
return{
message:'This is component2'
}
},
render(){
return(<p>{this.message}</p>)
}
}
Could I write like component2.vue ? How about performance of both ?

Both versions of writing the component will do the same thing. As far as the performance is considered, there would be no difference. Both are compiled into render function that returns Virtual DOM tree for your component.
The difference is the flavor and syntax of the implementation. Though with Vue, we mostly use templates as they are more readable over JSX, there are situation where JSX is more appropriate to use. For example, consider the case where you are trying to design a dynamic header component where level prop decides which <h1...h6> tag to use:
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
Same thing can be written more elegantly using render function or JSX:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name
this.$slots.default // array of children
)
},
props: {
level: {
type: Number,
required: true
}
}
});
Also, if you are using TypeScript, JSX syntax will provide you compile-time check for validating props and attributes, though setting that with Vue 2 is quite an hassle. With Vue 3, that is much simpler.
As far as dynamic loading of component is considered, you can use built-in <component /> component with is prop within the template as:
<component v-bind:is="someComponentToRenderDynamically"></component>
So, this brings the same advantages as JSX or direct render function based component. For more documentations see:
Dynamic Components
Render Function & JSX

First of all let's see what are Template syntax and JSX:
JSX: A syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Basically, JSX is a JavaScript render function that helps you insert your HTML right into your JavaScript code.
Template syntax: An HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data.
Using Vue templates is like using JSX in that they’re both created using JavaScript. The main difference is that Vue templates are syntactically valid HTML that can be parsed by spec-compliant browsers and HTML parsers.
What does it mean?
JSX functions are never used in the actual HTML file, while Vue templates are.
What is the difference? which one is better to use ?
According to the Vue.js documentation, Vue compiles the templates into highly-optimized JavaScript code.
But if you are familiar with Virtual DOM concepts and prefer the raw power of JavaScript, you can also directly write render functions instead of templates, with optional JSX support.
However, do note that they do not enjoy the same level of compile-time optimizations as templates.
So, we can conclude that writing template syntax with Vue is more optimized.

The vue template is much more readable and easier to understand than jsx functions.
It's much easier to save variables / properties and access them using "{{someVariables}}", rather than always telling a vue method to read them
In short, it's better to use the vue template because it's easier to implement dynamic pages with it (and other things).
Also, at this point it's not a very good idea to keep sending html code through methods.

Related

Precompile VueJS template only files

I am writing a single page app with VueJS. I have a lot of html elements sharing the same style across differents files. To simplify my job and clear the template section I've created .vue files to encapsulate commom html block.
For example I have files like AccentBox.vue:
<template>
<div class="bg-accent rounded-lg p-4 flex cursor-pointer">
<div class="m-auto flex flex-row">
<slot></slot>
</div>
</div>
</template>
The problem is that it increase the bundle size a lot and in the browser side it has a big performance overhead.
Example of the Home.vue:
<Box class="mt-5">
<GrowingAnimation>
<Header1>Titre:</Header1>
<Text class="mt-2">{{todo.title}}</Text>
<ClickScale>
<AccentBox class="mt-5 flex flex-row gap-4">
<TrashIcon class="h-10" />
</AccentBox>
</ClickScale>
</Animation>
</Box>
I want to know if there is a way to "precompile / transform" those component so that in the browser vuejs does not have to render the AccentBox.vue component and avoid having huge trees of nested vuejs components.
I already use the vuejs build command but browser side AccentBox.vue still is a component and take time to be processed by vue.
Transforming this:
<Header1>Titre:</Header1>
into
<h1 class="text-3xl font-bold">Titre:</h1>
Is there any way to do it? Thanks in advance.
Because your AccentBox.vue file represents a full-blown component, it does contain more overhead than simply writing out some divs. However, in Vue 3, this overhead is rather minimal, and there isn't a way to precompile something like a macro.
Vue allows us to write stateless components that simply render HTML, using something called a render function. We can rewrite your AccentBox component like this:
import { h } from 'vue'
const AccentBox = (props, context) => {
return h(
'div',
{ class: 'bg-accent rounded-lg p-4 flex cursor-pointer' },
h(
'div',
{ class: 'm-auto flex flex-row' },
context.slots
)
)
}
export default AccentBox
We can then import it in exactly the same way as a standard Vue file:
import AccentBox from 'AccentBox.js'

Inject Vue component from diet js file

I have a very simple app vue3:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
let currentApp = createApp(App);
(window as any).load(currentApp);
currentApp.mount('#app');
In the index.html file i import a dist lib:
<script src="http://localhost:5173/dist/assets/index.59eda240.js">
The content of dist JS is:
import HelloWorld from './components/HelloWorld.vue'
(window as any).load = (app: any) => {
app.component('hello-world', HelloWorld);
}
If i use a very simple HelloWorld.vue with just one line of text "Hello", all works fine, but if i put some css classes or more complicated component i obtain:
[Vue warn]: Invalid VNode type: Symbol() (symbol) at <HelloWorld>
at <App>
How can i solve this? Or is there another way to load component at runtime?
Thanks in advance.
P.s. I have no problem doing the same thing with Vue
--------- UPDATE MORE INFO ----------
Vue version
3.2.38
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-744fbh?file=src/main.js
Steps to reproduce
The base idea is to have a main app the create a Vue app, and another external lib the load component at runtime.
Open the sample project and show console log to see "Invalid VNode type: Symbol() (symbol)"
What is expected?
I expect to see the components rendered correctly
What is actually happening?
I don't see the component UI, but looking at the console I see the logic code works correctly but we have a warning: Invalid VNode type: Symbol() (symbol) Invalid VNode type: Symbol() (symbol)
System Info
No response
Any additional comments?
This warning appears only in some components, for example, fur a very simple component, it works fine, see here: https://stackblitz.com/edit/vitejs-vite-nueucw?file=src/App.vue.
You can see the source of helloworld component at the top of lib.js file, inside a comment block
The actual problem is that there are multiple Vue library copies per page that interact with each other. This should be avoided, preferably by bundling multiple parts of the application together, so multiple apps would reuse the same modules from vendors chunk. In case this isn't possible or practical, a simple workaround is to use Vue CDN for all applications, either by using window.Vue instead of vue import, or aliasing the import to global variable by means of a bundler.
The root cause is that Vue built-in element are specific to Vue copy in use - Fragment, Teleport, Comment, etc. They are defined with Symbol() to be distinguished from other elements and components and used in compiled component templates. Symbol is used in JavaScript to define unique tokens, Symbol('foo') !== Symbol('foo'). The symbols of built-in elements from one Vue copy mean nothing to another Vue copy, this is the meaning of this error message.
It can be seen in this analyzer that this component template:
<h1 style="background-color:Red; font-size: 44px;">{{ msg }}</h1>
<button #click="msg = 'click';">CLICK</button>
is compiled to this render function:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("h1", { style: {"background-color":"Red","font-size":"44px"} }, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createElementVNode("button", {
onClick: $event => {_ctx.msg = 'click';}
}, "CLICK", 8 /* PROPS */, ["onClick"])
], 64 /* STABLE_FRAGMENT */))
}
Notice that the use of multiple root elements results in wrapping them in Fragment, which won't be correctly processed by multiple Vue copies, etc.
Change hello world with another component:
<template>
<div class="relative flex items-top justify-center min-h-screen sm:items-center py-4 sm:pt-0">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="card rounded-lg shadow-lg" style="background-color: yellow;">
<div class="card-body p-0 m-0 pt-2" style="background-color: orange;">
<div class="row border-bottom p-0 m-0 pt-2" style="background-color: brown;">
<div class="bg-secondary">
<h5 class="bg-info">test</h5>
<p>
test
</p>
</div>
</div>
<div>
<div>
test2
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Only one root element, I have the same issue.

How to tell if an html tag in vue is a custom tag or not?

I'm new to Vue and I'm looking at an existing code that makes me question, where do the tags <u-col> and <u-row> come from?
My very little understanding of Vue so far is that you can create custom components and then use those components like html tags in the template. But my understanding is that if the component is to be used then it must be exported from where it is created and then imported at where it is being used.
Below is a part of the code that I'm not sure if <u-col> or <u-row> are custom made tags or if they're simply default vue tags? I don't think they're custom tags because I don't see any imports and I haven't found anything in the source code that tells me they are custom tags. But I don't think they are default vue tags either because I haven't seen them from my google searches. The closest I've come to is the <b-col> tag but I know that is from bootstrap.
<template>
<u-row class="mb-4 flex-nowrap">
<u-col class="text-sm text-700 text-right col-2 mr-4">
<span v-if="required" class="text-warning">*</span>{{label}}
</u-col>
<u-col>
<slot />
</u-col>
</u-row>
</template>
<script>
export default {
props: {
label: {
type: String
}
}
</script>
<style>
</style>
Any help is appreciated.

Multiple templates declared in one .vue file

I have a component inside a .vue file that can benefit from reusing a chunk of code. I know I can move that code to a separate .vue file and import it as a new component. However, this component would not be used anywhere else and I'd like to avoid cluttering the directory. Is it possible to declare this component's template inside the parent without using the in-code template:"<div>.....</div>" stuff?
This is the idea:
<template>
<div>
...some html here...
<div v-for="item in items">
{{item.name}}:
<div v-if="item.available">YES!</div>
<div v-else>NO :(</div>
</div>
...some other components and data here...
<div v-for="item in items">
{{item.name}}:
<div v-if="item.available">YES!</div>
<div v-else>NO :(</div>
</div>
</div>
</template>
I would like to be able to do something like this:
<template>
<div>
...some html here...
<div v-for="item in items">
<itemizer inline-template v-model="item">
{{value.name}}:
<div v-if="value.available">YES!</div>
<div v-else>NO :(</div>
</itemizer>
</div>
...some other components and data here...
<div v-for="item in items">
<itemizer v-model="item"/>
</div>
</div>
</template>
However, from what I understand this is not possible.
Unfortunately this pattern does not appear to be supported by the creator of Vue:
I personally feel the syntax is less maintainable than [Single File Components]
Note that we want to keep the SFC syntax as consistent possible, because it involves a big ecosystem of tools that need to support any new features added (e.g. Vetur would need to do something very different for handling SFCs with multiple scripts/templates). The proposed syntax, IMO, does not justify adding the new syntax.
https://github.com/vuejs/vue/pull/7264#issuecomment-352452213
That's too bad, as even beyond flexibility and developer choice, there is a good argument for inlining small functions that are not used by other components in order to reduce complexity. It's a common pattern in React and does not inhibit Single File Components when they're needed. In fact it allows gradual migration as inline components grow.
Here's one of the only resources currently that offers some potential workarounds:
https://codewithhugo.com/writing-multiple-vue-components-in-a-single-file/
I've tried them all and am not satisfied with any of them at this time. At best you can set runtimerCompiler: true and use template strings, but it'll add 10KB to your bundle and you'll likely miss out on full syntax highlighting available in the <template> element. Maybe you can hack Teleport, but I have not made a dedicated attempt.
Actually, this should work. Just register your Vue inline-template like this in the section of your parent .vue file:
<template>
<div v-for="item in items">
<test-template :item="item">
<h1>{{item.text}}</h1>
</test-template>
</div>
</template>
<script>
Vue.component('test-template', {
template:'#hello-world-template',
props: {
item: Object
}
});
export default {...}
</script>
In your parent HTML file, put this:
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
With vue3 there are multiple options:
with vue-jsx you can just declare a component in your script setup section and use that
const Named = defineComponent(() => {
const count = ref(0)
const inc = () => count.value++
return () => (
<button class="named" onClick={inc}>
named {count.value}
</button>
)
})
There is another option described by Michael Thiessen here
Also you can have multiple render function components in one file:
https://staging.vuejs.org/guide/extras/render-function.html
Although it is not supported in Vue core yet, there is a way to use this through vue macros project. See discussion here

VueJS - pass properties from one component to another in the template

I'm new to Vue, and am trying to build up a set of business components that rely on other lower-level components, and pass properties to them. None of the usual data-binding v-bind:propName="xxx" or :propName="xxx" seem to work from inside a component template however, and all the documentation treats prop passing from a top-level HTML page instead of from one component to another.
For example, with the below I have a lower-level component called "exchange-panel" which takes a "title" property. I want my higher-level component to use that generic exchange-panel and pass in a title to use. I get the following error:
[Vue warn]: Error compiling template:
<exchange-panel :title="The Panel Title">
<h1>test</h1>
</exchange-panel>
- invalid expression: :title="The Panel Title"
found in
---> <OrderbookDetail>
Here is the sample code (also at https://jsfiddle.net/ns1km8fh/
Vue.component("orderbook-detail", {
template:`
<exchange-panel :title="Public order book">
<h1>test</h1>
</exchange-panel>
`
});
Vue.component("exchange-panel", {
template:`
<div class="panel panel-default">
<div class="panel-heading">Title: {{ title }}</div>
<div class="panel-body">
<slot></slot>
</div>
</div>`,
props: ["title"]
})
new Vue({
el: "#exchange-container",
created: function () {
window.Vue = this
}
})
You need to quote Public order book.
<exchange-panel :title="'Public order book'">
The value of a bound attribute is treated as a javascript expression. Javascript strings need to be enclosed in quotes of some kind (single, double, backticks).
Updated fiddle.
As #RoyJ points out, you can also simply not bind it and use it as a normal attribute.
<exchange-panel title="Public order book">