Ściągamy Vue CLI i odpalamy, patrzymy jak wygląda „prawdziwy” projekt w w Vue.js. Do dzieła.
Ok, zakładam, że ściągnąć Vue CLI na tym poziomie już sobie potrafimy i takich rzeczy już tłumaczyć nie będziemy. Odpalamy nowy projekt.
Teraz patrzymy na main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Niby wszystko fajnie, taki odpowiednik Vue.createApp, które robiliśmy jak raczkowaliśmy we frontendzie. Tylko gdzie tu komponenty? Cóż, app.component tworzy komponent o zasięgu globalnym, tutaj takich nie ma.
Ok, idziemy do App.vue:
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Mamy tutaj klasyczny dla nowoczesnego Vue (i każdego Vue poza raczkowaniem z „Vue.createApp”) podział:
- tag template – markup komponentu/apki
- tag script – logika JS
- tag style – CSSy
Tutaj w script mamy ściągnięcie komponentu HelloWorld (także w components jest określony) oraz export default (poznamy jeszcze script setup, to nie będziemy musieli nic eksportować).
Ok, swoją drogą templatka apki nie jest ograniczona typowym ograniczeniem komponentów, czyli jeden top-level element. Tak jak nasze #app, też nie było.
Idziemy do komponentu HelloWorld:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
//(...)
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
Templatka komponentów ma ograniczenie jeden top-level element. Komponenty nie mają data, tylko propsy przekazywane z góry. Poza template, w script mamy nazwę komponentu i przyjmowane propsy.
Tag style ma atrybut scoped, aby ograniczyć te style do komponentu, aby się nie gryzły przypadkiem z tak samo nazwanymi stylami w innym komponencie.
Swoją drogą odpalmy naszą apkę:
npm run serve;
Kliknijmy h3 i podejrzyjmy style:
h3[data-v-469af010] {
margin: 40px 0 0;
}
Tak wygląda scoped style po stronie klienta. Komponent HelloWorld i każde jego dziecko ma ustawiony atrybut „data-v-469af010”. W ten sposób działają scoped styles.
Swoją drogą, zdziwiło mnie to, HTML5 wspiera coś takiego, jak scoped style tags (czyli tag style zagnieżdżony wewnątrz tagu HTML o ograniczonym do niego zasięgu), choć to niszowa rzecz, myślałem, że z tego korzysta Vue.
Cóż, Vue pewnie istniało zanim to wprowadzili do HTMLa a sposób w jaki podchodzą do scoped styles się sprawdza.
Ok, jedna rzecz do zapamiętania – script setup:
<script setup>
const props = defineProps(['foo'])
</script>
Czyli taki bardziej Reactowy i mniej „export defaultowy” sposób podejścia do tagu script. Dokumentacja jest pełna przykładów z tym podejściem, ono jest dominujące, będziemy musieli się z nim polubić.
Pozwolę sobie wrzucić Modal.vue utworzony przez Laravel Breeze:
<script setup>
import { computed, onMounted, onUnmounted, watch } from 'vue';
const props = defineProps({
show: {
type: Boolean,
default: false,
},
maxWidth: {
type: String,
default: '2xl',
},
closeable: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['close']);
watch(
() => props.show,
() => {
if (props.show) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = null;
}
}
);
const close = () => {
if (props.closeable) {
emit('close');
}
};
const closeOnEscape = (e) => {
if (e.key === 'Escape' && props.show) {
close();
}
};
onMounted(() => document.addEventListener('keydown', closeOnEscape));
onUnmounted(() => {
document.removeEventListener('keydown', closeOnEscape);
document.body.style.overflow = null;
});
const maxWidthClass = computed(() => {
return {
sm: 'sm:max-w-sm',
md: 'sm:max-w-md',
lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl',
}[props.maxWidth];
});
</script>
Tak wyglądają nowoczesne komponenty. Nie musimy tego rozumieć od ręki, ale jest trochę inaczej, niż w naszych poprzednich zabawach. Mimo wszystko mam nadzieję, że co nieco ogarniamy.