I'm new to Vue.js and I'm confused about file structure and how to build a simple application.
I have installed Vue CLI on my mac using this command:
npm install -g @vue/cli
Then I created a counter project and used the default option:
vue create counter
Then I started the application:
cd counter
npm run serve
The default application code seemed confusing to me so I want to create my own simple application that makes more sense to me:
I created counter.html inside the public folder:
<html lang="en">
<body>
<div id="counter"></div>
</body>
<script src="../src/counter.js" type="text/javascript"></script>
</html>
I created counter.js file inside the src folder:
import Vue from 'vue';
import Counter from './components/counter.vue';
new Vue({
render: h => h(Counter),
}).$mount('#counter')
I created counter.vue file inside the components folder:
<template>
<button v-on:click="count++">You clicked me {{ count }} times.</button>
</template>
<script type="text/javascript">
export default {
name: 'Counter',
props: [
'count'
],
}
</script>
Then I run:
npm run build
When I visit my page: http://localhost:8080/counter.html
I get a blank page and the console shows the following error: Uncaught SyntaxError: Unexpected token <
Any help on what I'm doing wrong is greatly appreciated.
First, As @Dan said, the script tag should be in <body>
, not after.
That said, there's something fundamentally flawed in your code : your button is mutating a property received in the Count component.
Imagine that you're using the Counter in a bigger application, and you want to initialize it with a count value. You'd write : <Counter count="3" />
, but then clicking would mutate this "3" into a "4", even if count="3"
is written statically ; there would be inconsistency between the public declaration of the count property and its actual value due to mutation of the property by the button.
Here, you have multiple choices :
- Don't use a prop, only use internal state of the count component. The advantage of this construction is that the component is independant ; the disadvantage is that you can't initialize "count" with a custom value when you will be creating a component.
<template>
<button v-on:click="count++">You clicked me {{ count }} times.</button>
</template>
<script type="text/javascript">
export default {
name: 'Counter',
data: function() { return { count: 0 } },
}
</script>
- Use events instead, and hold the count value outside the Counter component. This is probably the easiest to implement in the component, but then requires extra code in the parent. The advantage of this is that the value is held outside the component, so customization is possible ; the disadvantage is that, without proper binding to update the value, it won't work.
<template>
<button v-on:click="$emit('increment')">You clicked me {{ count }} times</button>
</template>
<script type="text/javascript">
export default {
name: 'Counter',
props: [
'count'
],
}
</script>
and then in your application:
<template>
<counter :count="count" @increment="count++" />
</template>
<script>
export default {
data: () => ({ count: 0 })
}
</script>
- A combination of the two previous solutions. Hold an internal state so the button can manage itself, but also synchronize properly with the outside world using watchers.
<template>
<button v-on:click="internal_count++">You clicked me {{ internal_count }} times</button>
</template>
<script type="text/javascript">
export default {
name: 'Counter',
props: [
'count'
],
watch: {
count(val) { this.internal_count = val },
internal_count(val) { this.$emit('update', val) },
},
}
</script>
and then in your app:
<template>
<counter :count="count" @update="v => count = v" />
</template>
<script>
export default {
data: () => ({ count: 0 })
}
</script>
Basically, the rule of thumb is :
- Don't mutate a prop and consider it unique source of truth
- If you want to mutate a prop, send an event instead and hope that the receiver will update the prop for you, then you can receive it back updated.
Hoping this help,