Vue
Binding
Assign that value of a class or id to a variable
<div v-bind:id="dynamicId">
<!-- or -->
<div :id="dynamicId">
Listening to Events
<a v-on:click="doSomething"> ... </a>
<!-- shorthand -->
<a @click="doSomething"> ... </a>
Binding Variable Events
<a v-on:[eventName]="doSomething"> ... </a>
<!-- shorthand -->
<a @[eventName]="doSomething"> ... </a>
Reactivity
<button @click="count++">
{{ count }}
</button>
Computed/Derived Properties
- Computed properties only get recomputed when their reactive dependencies change
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// a computed ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
Conditional Rendering
v-if
vs. v-show
. v-show
will always be in the dom, just with the display property visible or not
v-if
is more expensive to toggle, whereas v-show
is more expensive to render initially
Logic
<li v-for="item in items">
{{ item.message }}
</li>
<!-- or -->
<li v-for="{ message } in items">
{{ message }}
</li>
It's recommended to always use a :key
Listeners/Modifiers
<!-- the submit event will no longer reload the page -->
<form @submit.prevent="onSubmit"></form>
<!-- only call `submit` when the `key` is `Enter` -->
<input @keyup.enter="submit" />
Form Input Bindings v-model
<!-- instead of -->
<input
:value="text"
@input="event => text = event.target.value">
<!-- we can do -->
<input v-model="text">
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
<AssignmentTags
v-model:currentTag="currentTag"
/>
<!-- in AssingmentTags -->
<template>
<button
@click="$emit('update:currentTag', tag)"
>
</button>
</template>
props: {
currentTag: String
}
Throttling and Debouncing
Throttle requests to every 500ms (using lodash
<script setup>
watch(search, throttle(function (value) {
Inertia.get('/users')
}, 500))
</script>
Debounce - does the request after you've stopped typing for 500ms
<script setup>
watch(search, debounce(function (value) {
Inertia.get('/users')
}, 500))
</script>
Saving in Local Storage
<script setup>
let food = ref(localStorage.getItem("food"));
function write(key, val) {
localStorage.setItem(key, val)
}
</script>
<template>
<input type="text" v-model="food" @input="write('food', food)">
</template>
or as a compostable
import { ref, watch } from "vue";
export function useStorage(key) {
= localStorage.getItem(key);
storedValue let value = ref(storedValue);
function write() {
.setItem(key, value);
localStorage
}
watch(value, () => {
if (value.value === "") {
.removeItem(key);
localStorage
}
write(key, value.value);
;
})
return value;
}
Components
onMounted()
- runs after the component has finished rendering
Slots
<!-- The component -->
<button class="fancy-btn">
<slot />
</button>
<!-- Usage -->
<FancyButton>
Click me! <!-- slot content -->
</FancyButton>
Computed Properties (Watchers)
to watch a variable and call code whenever it changes
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)
// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes('?')) {
loading.value = true
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
} finally {
loading.value = false
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</template>
Emitting Messages to Parents
<!-- Checkbox Component -->
<script setup lang="ts">
import { computed } from 'vue'
// The update prefix is only used when you want two-way-binding in the parent component to the value of something in the child
const emit = defineEmits(['update:checked'])
const props = defineProps<{
checked: boolean
value?: any
}>()
const proxyChecked = computed({
get() {
return props.checked
},
set(val) {
// emit a message called update set to the value of checked
emit('update:checked', val)
},
})
</script>
<!-- In the parent - bind the value of the form to -->
<!-- Vue automatically includes the update: prefix when we're using v-model -->
<Checkbox name="remember" v-model:checked="form.remember" />
<!-- Example 2 -->
<textarea @keyup="emit('update:modelValue', e.target.value)"/>
Emitting the Value to the Parent
In the child:
<script setup>
const emits = defineEmits(['update'])
const internalValue = ref(42)
// Expose the internal value and allow parent to request it
const getInternalValue = () => {
emits('update', internalValue.value)
}
</script>
<template>
<div>
<p>Internal value: {{ internalValue }}</p>
<button @click="getInternalValue">Send to Parent</button>
</div>
</template>
In the parent:
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const receivedValue = ref(0)
const handleUpdate = (value) => {
receivedValue.value = value
}
</script>
<template>
<div>
<h2>Parent Component</h2>
<p>Received value: {{ receivedValue }}</p>
<ChildComponent @update="handleUpdate" />
</div>
</template>
v-model
Binding to the Child's State
In the child
<script setup>
const value = defineModel()
</script>
<template>
<div>
<input v-model="value" type="number">
<p>Child value: {{ value }}</p>
</div>
</template>
<script setup>
const parentValue = ref(42)
</script>
<template>
<div>
<p>Parent value: {{ parentValue }}</p>
<ChildComponent v-model="parentValue" />
</div>
</template>
Conditional Styling
<button
:class="{'bg-gray-200': type === 'gray'}">
Inertia Niceties
Displaying form errors
<script setup>
defineProps({
errors,
});
</script>
<template>
<input v-model="form.email" required/>
<div v-if="errors.email" v-text="errors.email"></div>
</template>
Forms
<script setup>
const loginForm = useForm({
email: '',
password: ''
});
const signupForm = useForm({
name: '',
email: '',
password: '',
password_confirmation: ''
});
</script>
<form @submit.prevent="loginForm.post(route('login'))" :disabled="loginForm.processing">
<input v-model="loginForm.email">
<input v-model="loginForm.password" type="password">
</form>
<form @submit.prevent="signupForm.post(route('register'))" :disabled="signupForm.processing">
<input v-model="signupForm.name">
<input v-model="signupForm.email">
<input v-model="signupForm.password" type="password">
<input v-model="signupForm.password_confirmation" type="password">
</form>
Composables
The naming convention is to prefix the names with use
// composables/useFlash.js
export function useFlash.js
Dependency Injection
provide('key', object)
// a few levels deep
let obj = inject('key')
Pinia Stores
export let useTeamStore = defineStore("team", {
state: () => ({
name: "",
sport: 0,
,
})
actions: {
async init() {
let r = await import("file.json");
this.$state = r.default;
,
},
}; })