一、声明式渲染
声明式渲染 模板语法:就是在vue单文件里面写代码,然后通过声明式的方式将数据绑定到dom。然后被编译成js。经过优化后减少dom操作。 文本插值:{{}}, vue单文件组件:SFC,一种可复用代码组织形式。 声明式渲染:通过改变状态,触发html的更新渲染。 响应式状态:声明方式(reactive只适用于对象、数组,ref适合任何对象)
const counter = reactive({
count: 0
})
console.log(counter.count)
const a =reactive(['1', '2'])
a.push('1')
const message = ref('Hello World!')
console.log(message.value)
1. 在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。
2. reactive 返回的是一个原始对象的 Proxy
3. reactive 响应式对象的属性赋值或解构,会失去响应性
4. reactive 不可以随意地“替换”一个响应式对象
5. ref 将传入参数的值包装为一个带 .value 属性的 ref 对象
6. ref 可以响应式地替换整个对象
7. ref 是模板渲染上下文的顶层属性时才适用自动“解包”
8. Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新push、pop、shift、unshift、splice、sort、reverse
二、Attribute 绑定
v-bind
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
<h1 :class="title">Make me red</h1>
1. 绑定多个参数
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>
2. 使用 JavaScript 表达式
<div :id="`list-${id}`"></div>
3. 调用函数
{{ formatDate(date) }}
4. 动态参数
<a v-bind:[attributeName]="url"> ... </a>
5. 修饰
事件修饰符:.stop阻止冒泡,.prevent阻止默认事件,.self只触发它自己,.capture,.once,.passive
<form @submit.prevent="onSubmit">...</form>
v-model修饰符
.lazy 每次change事件后更新数据。
.number 自动转为数字。
.trim 去除前后空格。
6. class与style绑定
<div :class="{ active: isActive }"></div>
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
7. 透传 attribute 传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器, 禁用 Attributes 继承 inheritAttrs: false
<script>
export default {
inheritAttrs: false
}
</script>
<script setup>
</script>
8. 应用在内部的 <button> 上而不是外层的 <div> 上 v-bind="$attrs"
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>
事件监听
v-on
<button v-on:click="increment">{{ count }}</button>
<button @click="increment">{{ count }}</button>
1. 监听对象:ref、reactive对象、getter函数或多个数据源组成的数组。
const x = ref(0)
const y = ref(0)
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
2. 不能直接侦听响应式对象的属性值
const obj = reactive({ count: 0 })
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
3. watchEffect 在创建侦听器时,立即执行一遍回调
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
4. watch 只追踪明确侦听的数据源;watchEffect,则会在副作用发生期间追踪依赖
5. 如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项
表单绑定
v-model
<input v-model="text">
v-model="text" 是v-bind:value="text" 和v-on:input="onInput" 的语法缩写
<input :value="text" @input="onInput">
自定义组件
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
条件渲染
v-if、v-else、v-else-if、v-show
<h1 v-if="awesome">Vue is awesome!</h1>
若 awesome 更改为假值 (Falsy),它将被从 DOM 中移除。
1. v-show 仅切换了该元素上名为 display 的 CSS 属性。
2. v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
列表渲染
v-for
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
更新列表:
1. 在源数组上调用变更方法: todos.value.push(newTodo)
2. 使用新的数组替代原数组: todos.value = todos.value.filter()
3. v-for 来遍历一个对象的所有属性
4. v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名
5. Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素。重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute
计算属性
computed 如果在模板中写太多逻辑,会让模板变得臃肿,难以维护 computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref
const filteredTodos = computed(() => {
})
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
1. 计算属性值会基于其**响应式依赖**被缓存;方法调用总是会在重渲染发生时再次执行函数
2. 通过同时提供 getter 和 setter 来创建可写计算属性
生命周期和模板引用
<p ref="p">hello</p>
const p = ref(null)
import { onMounted } from 'vue'
onMounted(() => {
})
onMounted 组件完成初始渲染并创建dom节点后。
当调用 onMounted 时,Vue 会自动将回调函数注册到当前正被初始化的组件实例上。这意味着这些钩子应当在组件初始化时被同步注册
侦听器
watch
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newCount) => {
console.log(`new count is: ${newCount}`)
})
watch() 可以直接侦听一个 ref
watch() 也可以侦听其他类型的数据源
组件
import ChildComp from './ChildComp.vue'
<ChildComp />
1. 创建.vue 文件。这就是单文件组件。
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
}
2. 动态组件
<component :is="tabs[currentTab]"></component>
3. 全局注册
4. 异步组件
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
const AsyncComp = defineAsyncComponent({
loader: () => import('./Foo.vue'),
loadingComponent: LoadingComponent,
delay: 200,
errorComponent: ErrorComponent,
timeout: 3000
})
6.
组合式函数
组合式函数:用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
1. 组合式 API 会给予你足够的灵活性,让你可以基于逻辑问题将组件代码拆分成更小的函数
Props
子组件可以通过 props 从父组件接受动态数据
<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
msg: String
})
</script>
<ChildComp :msg="greeting" />
1. 单向数据流 props 都遵循着单向绑定原则 props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递
2. Prop 逐级透传 多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据
<script setup>
import { provide } from 'vue'
provide( 'message', 'hello!')
</script>
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
3. 当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
4. 如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly()
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
5.
Emits
子组件还可以向父组件触发事件
<script setup>
const emit = defineEmits(['response'])
emit('response', 'hello from child')
</script>
<ChildComp @response="(msg) => childMsg = msg" />
1. 使用$emit
<button @click="$emit('someEvent')">click me</button>
2.
插槽
1.默认插槽
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
2. 通过使用插槽,<FancyButton> 组件更加灵活和具有可复用性。现在组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式。
3. 默认内容
<button type="submit">
<slot>
Submit <!-- 默认内容 -->
</slot>
</button>
4. 具名插槽
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
5. 作用域插槽
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
6. 具名作用域插槽
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
<slot name="header" message="hello"></slot>
7.
自定义指令
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑 组件是主要的构建模块,而组合式函数则侧重于有状态的逻辑
<script setup>
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
1. 局部注册
export default {
setup() {
},
directives: {
focus: {
}
}
}
2. 全局注册
const app = createApp({})
app.directive('focus', {
})
TIP
3. 指令钩子
const myDirective = {
created(el, binding, vnode, prevVnode) {
},
beforeMount(el, binding, vnode, prevVnode) {},
mounted(el, binding, vnode, prevVnode) {},
beforeUpdate(el, binding, vnode, prevVnode) {},
updated(el, binding, vnode, prevVnode) {},
beforeUnmount(el, binding, vnode, prevVnode) {},
unmounted(el, binding, vnode, prevVnode) {}
}
4. 插件
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
})
一个插件可以是一个拥有 install() 方法的对象
const myPlugin = {
install(app, options) {
}
}
Transition
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册 进入或离开可以由以下的条件之一触发:
由 v-if 所触发的切换 由 v-show 所触发的切换 由特殊元素 切换的动态组件
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
你可以通过监听 <Transition> 组件事件的方式在过渡过程中挂上钩子函数
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
function onBeforeEnter(el) {}
function onEnter(el, done) {
done()
}
function onAfterEnter(el) {}
function onEnterCancelled(el) {}
function onBeforeLeave(el) {}
function onLeave(el, done) {
done()
}
function onAfterLeave(el) {}
function onLeaveCancelled(el) {}
是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
KeepAlive
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
<component :is="activeComponent" />
Teleport
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
<Teleport> 接收一个 to prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
单文件组件
Vue 的单文件组件是一种特殊的文件格式,使我们能够将一个 Vue 组件的模板、逻辑与样式封装在单个文件中
前端开发的关注点不是完全基于文件类型分离的。前端工程化的最终目的都是为了能够更好地维护代码。关注点分离不应该是教条式地将其视为文件类型的区别和分离,仅仅这样并不够帮我们在日益复杂的前端应用的背景下提高开发效率。
|