1

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.

8
  • Any additional info? Number of line of position in the code? npm run serve shows detailed statistic with full stacktrace. Commented Jun 9, 2019 at 20:57
  • 2
    The template in counter.vue has a floating apostrophe Commented Jun 9, 2019 at 20:59
  • I removed floating apostrophe, but still the same blank page and console log error. npm run serve shows standard message of Compiled successfully app running at: localhost:8080 Commented Jun 9, 2019 at 21:13
  • What happens if you change the id to "app" and change the mount to "#app". I'm pretty sure that's required in the default Vue CLI installation. Commented Jun 9, 2019 at 22:00
  • Also your script tag is in an invalid place in counter.html. It needs to go in the head or body, but can't go after the closing body tag. Commented Jun 9, 2019 at 22:03

1 Answer 1

1

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 :

  1. 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>
  1. 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>
  1. 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 :

  1. Don't mutate a prop and consider it unique source of truth
  2. 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,

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.