Vue vs Traditional HTML - Reusability & Components - Beginner's Guide
Tags: vue, html, javascript, webdev
Published: 2019-03-09 23:14:36

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/vue-vs-traditional-html). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.

If you want to follow along I recommend you to use [codesandbox](https://codesandbox.com).

In the world of HTML let's say we want to create a panel that consists of a header and text. You could create something like this

```html
<div class="panel">
    <div class="panel__header">Title</div>
    <div class="panel__body">
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium, sit!
    </div>
</div>
```

From here you can apply CSS and JavaScript to these classes. Then you can go ahead and reuse this HTML as often as you please. It became reusable thanks to the classes. This is the way how CSS frameworks like bootstrap were working for years.

Let's look at how Vue handles reusability:

The first difference is that we have to create a base class for panels, and we do so in a component.

So let's create the component `Panel.vue`

```vue
<template>
<div>
    <div class="header">Title</div>
    <div class="body">
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium, sit!
    </div>
</div>
</template>

<script>
export default {

}
</script>
```
Note how we can eliminate some classes since our CSS will be scoped to this component and it is clear that `header` refers to the panel header.

Now instead of repeating this HTML block over and over again, you can just go ahead and import the component whereever you need. 

Let's add two panels to the component `App.vue`
```vue
<template>
<div>
    <Panel />
    <Panel />
</div>
</template>

<script>
import Panel from './Panel.vue'

export default {
    components: { Panel },
}
</script>
```

This **seperation of concerns** is great, because instead of various nested `div` containers we simply end up with `Panel` making our template very easy to follow.
But wait! Like this, the title and body will always be the same. That's right, so what we need is to make these properties dynamic.

For that purpose we have to make the parent component (App.vue) pass down the title and body to the child component (Panel.vue). The child component defines what so called props it accepts.

### Props

`Panel.vue`
```vue
<template>
<div>
    <div class="header">{{ title }}</div>
    <div class="body">{{ body }}</div>
</div>
</template>

<script>
export default {
    props: {
        title: {
            type: String,
            required: true,
        },
        body: String,
    }
}
</script>
```

Our component accepts two props. The `title` which has to be a string and is required, and the body which is also a string, but not necessarily required.

And `App.vue` can now pass down the props to the panel.
```vue
<template>
<div>
   <Panel title="Lorem Ipsum" body="Lorem ipsum dolor sit amet" />
   <Panel title="Something else" />
</div>
</template>

<script>
import Panel from './Panel.vue'

export default {
    components: { Panel },
}
</script>
```

Props are quite similar to normal HTML attributes.
Take a look at the following element
```html
<input type="submit" value="Submit" />
```

The `input` element accepts the attributes `type` and `value`, among many others. It understands and can use these because they are defined on the `input` element itself.
If you were to write `<input color="primary" />` it would simply ignore the attribute `color` because it does not exist on `input`.

---

Now some panels are especially important and their background needs to be highlighted. In HTML you would now add a modifier class to the panel and style it.

Let's add the class `panel--primary`.
```html
<div class="panel panel--primary">
    <div class="panel__header">Title</div>
    <div class="panel__body">
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusantium, sit!
    </div>
</div>
```

CSS
```css
.panel.panel--primary .panel__header {
    background-color: #369;
    color: #fff;
}
```

In Vue this would simply become another prop.

`Panel.vue`
```vue
<template>
<div :class="{primary: isPrimary}">
    <div class="header">{{ title }}</div>
    <div class="body">{{ body }}</div>
</div>
</template>

<script>
export default {
    props: {
        title: String,
        body: String,
        isPrimary: {
            type: Boolean,
            default: false,
        },
    }
}
</script>

<style scoped>
    .primary {
        background-color: #369; /* you might as well have a global CSS rule for the background color */
    }
</style>
```

We add the `isPrimary` prop to our props list. Note how we default it to false. How convenient. Now we only need to pass the `isPrimary` prop when we actually want a primary panel.

We also add the class `primary` to the classlist of the root element with `:class="{primary: isPrimary}"`. If you are confused about that syntax be sure to check out [my previous article on CSS in Vue](https://dev.to/mzanggl/css---vue-vs-vanilla-javascript---beginners-guide-42le).

Back in `App.vue` we can simply add the `isPrimary` prop the same way you would add boolean like HTML attributes like `selected` or `checked`.
```vue
<template>
<div>
   <Panel isPrimary title="Lorem Ipsum" body="Lorem ipsum dolor sit amet" />
   <Panel title="Something else" body="Lorem ipsum dolor sit amet" />
</div>
</template>

<script>
import Panel from './Panel.vue'

export default {
    components: { Panel },
}
</script>
```

### Passing data as props

So far we have only passed strings to the child. But what happens when we have to pass any other data?

Back in `App.vue` let's define the title and body as actual data and try to pass it to the child.

```vue
<template>
<div>
   <Panel isPrimary title="title" body="body" />
</div>
</template>

<script>
import Panel from './Panel.vue'

export default {
    components: { Panel },
    data() {
        return {
            title: 'Lorem Ipsum',
            body: 'Lorem ipsum dolor sit amet',
        }
    }
}
</script>
```

The above will **not** work. It would literally pass the string `title` and `body` and not the contents of the variable. In order to fix that, we have to add a prefix to the prop. For that we only have to change the `<template>` part of `App.vue`.

```vue
<template>
<div>
   <Panel isPrimary v-bind:title="title" v-bind:body="body" />
</div>
</template>
```

You can and I recommend you to abbreviate the above to
```vue
<template>
<div>
   <Panel :title="title" :body="body" />
</div>
</template>
```

In fact `v-bind` allows any JavaScript expression.

```vue
<template>
<div>
   <Panel :title="title.toUpperCase() + ', ' + body.substr(0, 20)" />
</div>
</template>
```

Also, if you want to pass a number, boolean, array or object, you also have to do that through an expression.
```vue
<template>
<div>
   <Panel 
       :someNumber="1"
       :someBoolean="false"
       booleanThatEvaluatesToTrue
       :array="[1, 2, 3]"
       :object="{ key: 'value' }"
   />
</div>
</template>
```

--- 

Be aware that you not only create new components for reusability reasons. Whenever a component becomes too complex or you realize it is doing more than one thing, consider splitting it up into multiple components. It helps making the code organized.

Imagine our panel header becomes more complex and we want to split it up into its own component.


`Panel.vue`
```vue
<template>
<div>
    <div class="header">
        <PanelHeader :title="title" :isPrimary="isPrimary"/>
    </div>
    <div class="body">{{ body }}</div>
</div>
</template>

<script>
import PanelHeader from './PanelHeader'

export default {
    components: { PanelHeader },
    props: {
        title: String,
        body: String,
        isPrimary: {
            type: Boolean,
            default: false,
        },
    }
}
</script>

```
`PanelHeader.vue`
```vue
<template>
<div :class="{ primary: isPrimary }">
    {{ title }}
</div>
</template>

<script>
export default {
    props: {
        title: String,
        isPrimary: {
            type: Boolean,
            default: false,
        },
    }
}
</script>

<style scoped>
.primary {
    background-color: #369;
}
</style>
```

`Panel.vue` still receives the props `title` and `isPrimary` from `App.vue`. However, it is not really doing anything with them. It simply passes the props further down to `PanelHeader.vue`.

You can shorten `<PanelHeader :title="title" :isPrimary="isPrimary"/>` to `<PanelHeader v-bind="{ title, isPrimary }" />`.

Please note that `App.vue` has no idea and doesn't care that the panel header became its own component.


### Slots
Props are great but what if we want more than just basic text in our panel body. What if we want some HTML with specific styling and functionality. For this case we have slots.

We no longer need the `body` prop, so let's remove that. For the slot, let's add `<slot name="body" />`.

`Panel.vue`
```vue
<template>
<div>
    <div class="header">{{ title }}</div>
    <div class="body">
        <slot name="body" />
    </div>
</div>
</template>

<script>
export default {
    props: {
        title: String,
    }
}
</script>
```

Basically in the template all we do is replace `{{ body }}` with `<slot name="body" />`.

And in `App.vue` we can now add the button inside an element that has the attribute `slot="body"`.

```vue
<template>
<div>
    <Panel title="Lorem Ipsum" body="Lorem ipsum dolor sit amet">
        <div slot="body">
            <button @click="scream">Scream</button>
        </div>
    </Panel>
    <Panel title="Something else" body="Lorem ipsum dolor sit amet" />
</div>
</template>

<script>
import Panel from './Panel.vue'

export default {
    components: { Panel },
    methods: {
        scream() {
            alert('AAAAH')
        },
    },
}
</script>
```

What will happen is that `<div slot="body">` from `App.vue` will be placed in `<slot name="body" />` from `Panel.vue`.

### Events

Since we pass down the `title` as a prop we can not update it inside panel. If we want to update the title we have to fire an event from the child to parent.

> Props down, events up

For this let's choose a different example that makes a little more sense.

`AwesomeCounter.vue`
```vue
<template>
<div>
    <button @click="increment">{{ awesomeCount }}</button>
</div>
</template>

<script>
export default {
    props: {
        awesomeCount: Number
    },
    methods: {
        increment() {
            this.$emit('update:awesomeCount', this.awesomeCount + 1)
        },
    },
}
</script>
```

This is our counter, when we press the button it will send the event `update:awesomeCount` to the parent and passes the incremented value.

In the following code snippet you can see that we can listen to the event `update:awesomeCount` the same way we listen to normal events like `click` using `@click`. In the function we can access the new count using `$event`.

`App.vue`
```vue
<template>
<div>
    <AwesomeCounter :awesomeCount="count" @update:awesomeCount="count = $event"/>
</div>
</template>

<script>
import AwesomeCounter from './AwesomeCounter'

export default {
    components: { AwesomeCounter },
    data() {
        return {
            count: 10,
        }
    }
}
</script>
```

In fact, this is such a common scenario that you can abbreviate the above template to simply be
```vue
<template>
<div>
    <Counter :awesomeCount.sync="count"/>
</div>
</template>
```

For this to work the event has to have the name `update:{name of prop}`.

Events are not only used to update data from the child. You can do anything you want like making ajax calls for example.

### devtools
If you have problems understanding some of the concepts explained here, need help debugging or simply want to step up your vue game, check out [vue devtools](https://github.com/vuejs/vue-devtools). You will be able to see your components and how they are connected in a DOM like tree view, which data and props they own, what events have been fired etc. You can even manipulate data!

### Notes
- You don't necessarily have to define props in an object. You can also define them in an array `props: ['title', 'body']`
- We looked at some requirements to define props, like `type`, `default` and `required`. There is more [here](https://vuejs.org/v2/guide/components-props.html#Prop-Validation).