创建Vue项目
创建Vue项目
$ npm create vue@latest
后面有提示教你咋用
启动Vue项目
npm run dev
基础
创建一个应用
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
})
基本结构
App (root component)// 这是根组件,其他都是它的子组件
├─ TodoList
│ └─ TodoItem
│ ├─ TodoDeleteButton
│ └─ TodoEditButton
└─ TodoFooter
├─ TodoClearButton
└─ TodoStatistics
挂载应用
// 创建应用实例,还要挂载到指定的容器中
<div id="app"></div>
app.mount('#app')// 对应的css选择器的语法
// 挂载到哪个容器下,该组件的内容将会显示在该容器内
多个应用实例
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
// 每个应用实例有自己的资源和配置等
// 可看做独立的个体
模板语法
文本插值
<span>Message: {{ msg }}</span>
// {{ 这里可以写一些表达式,还可以使用js中的变量 }}
// 如果不适用v-html的话,那么msg将会以纯文本的形式显示,即使它是由html标签组成
原始html(慎用)
<span v-html="msg"> 这里不能有内容,不然报错 </span>
// 如果有内容那么span这个标签会存在一个text的孩子节点,
// 而使用v-html会重写span的孩子节点,所有如果有内容就会报错
Attribute 绑定
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用
v-bind
指令:
<div v-bind:id="dynamicId"></div>
// v-bind:属性名="变量名" 这样该属性的值就动态的跟变量的值一致
// 属性名如常见的id, class, style等等
简写
<div :id="a"></div>
// 如果绑定的变量名与属性一致还能进一步简化
<div :id="id"></div> ==> <div :id></div>
动态绑定多个属性
const objectOfAttrs = {
id: 'container',
class: 'wrapper',
style: 'background-color:green'
}
<div v-bind="objectOfAttrs"></div>
// 使用不带参数的v-bind,不能使用:简化
// 那么那个变量就要用对应的键值对
指令 Directives
以v-开头的,诸如v-bind, v-html等等
v-bind:绑定属性值
v-html:渲染html文档
v-if:添加渲染
v-on:绑定事件,如v-on:click:点击事件
可以简写成@事件名:@click
指令结构
响应式基础
ref
在template中使用ref对象时,不需要.value,直接变量名就行
因为自动解包了,但在script中需要带.value
万物皆可ref
import {ref} from 'vue'
const a = ref(1)
// ref()函数会返回一个带有.value属性的ref对象
// 通过修改a.value,就可以改变a的值
reactive
ref是基本类型具有响应性,而reactive能够使对象具有响应性
但ref也能放对象,因为它会让reactive实现对象的响应
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// 返回的是该对象的Proxy对象
计算属性
监听的对象得是响应式的
可以设置其值,通过提供getter和setter方法,最好不要
<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'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
// 返回的对象会当author.books这个发生变化时进行该函数
// 多用于需要多次计算的时候,可以监听某个属性
// 计算的值是有缓存的,相对于直接写个函数好得多
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
类与样式绑定
绑定HTML class
- 基本用法
<div :class="{ active: isActive }"></div>
active是要添加的类名, isActive是一个响应式对应,数值类型为布尔型,
当为true,active就会动态添加到class中
- 与原生class共存
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
:class 与 原始的class可以共存
:class的对象,看做要动态添加的类型,值就是控制它添加或去除
- 使用计算属性
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
使用计算属性,可以在值变更的时候,自动更新对应的DOM元素,
常见的技巧
- 绑定数组
const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>
还可以有条件
<div :class="[isActive ? activeClass : '', errorClass]"></div>
绑定内联样式
const activeColor = ref('red')
const fontSize = ref(30)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
条件渲染
v-if
不会保留所有标签,只会存在条件为真的
<h1 v-if="awesome">Vue is awesome!</h1>
就像正常的if-else使用,但有前缀v-
=里面就是表达式
v-else
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
v-else-if
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
template也能使用v-if
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-show
<h1 v-show="ok">Hello!</h1>
只是把display置为none
列表渲染
循环渲染从写有v-for的标签开始
v-for
对数组遍历
let a = [1, 2, 3]
<p v-for="(item, index) in a">
{{ item }}
</p>
a中的值依次赋值给item,index是对应的索引,
let a = [{a:1}]
第一个参数就是a数组的元素,可以对其进行结构{b}// 1
对对象遍历
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<ul>
<li v-for="(value, key, index) in myObject">
{{ value }}
</li>
</ul>
value对应属性值
key对应属性名
index对应索引
事件处理
表单输入绑定
v-model
数据双向绑定
仅能使用在能输入内容的组件,如input, select,textarea
input
<input v-model="text">
input输入的内容会绑定在text变量,得是响应式,不然没效果
textarea
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
select
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
生命周期
注册期钩子
在组件被渲染完成后,触发该函数
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
生命周期图示
侦听器
watch
let a = [1, 2, 3]
let b = ref(1)
watch(()=>b.value, (newValue, oldValue)=>{
console.log(newValue, oldValue)
})
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:watch的第二个参数是一个回调函数,有两个输入,第一个为新的值,第二个为老的值
watch第三个参数是一些对watch的配置
watch( source, (newValue, oldValue) => { // 当 `source` 变化时,仅触发一次 }, { once: true ,// 只执行一次 immediate: true// 创建的时候立刻执行一次 } )
watchEffect
不用提供侦听对象,由函数自行判断
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
模板引用
属性ref
相当的好用
<input ref="a">
let a =ref()
将input组件与a进行绑定
此时a相当于组件的实例
相当于通过dom获取到了对应的元素对象
组件基础
定义一个组件
一般包含三块区域,script, template, style
<script setup>
import { ref } from 'vue'
写一些js代码
const count = ref(0)
</script>
<template>
页面标签
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
<style scoped>
页面样式
</style>
导入组件
直接使用import, 组件使用了默认导出
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
传递props(properties)
在子组件上直接写变量:值,子组件通过defineProps接收
<BlogPost title="hi"></BlogPost>
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
监听事件
和
defineProps
类似,defineEmits
仅可用于<script setup>
之中,并且不需要导入,它返回一个等同于$emit
方法的emit
函数。它可以被用于在组件的<script setup>
中抛出事件,因为此处无法直接访问$emit
:
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
给子组件绑定了一个自定义事件enlarge-text,当它被调用时,父组件会执行该内容
- 子组件通过defineEmits接收父组件给的方法
const emits = defineEmits(['hi'])
// 参数是一个数组,数组中的元素是父组件给的方法名
// 返回的是一个emit函数,所有需要一个变量存储
通知父组件
执行对应的函数,子组件传入的参数会传入对应的函数
类型对应的函数别名一样
emits('hi',a)
// 第一个父组件给的方法名,第二个是参数(如果有的话)
通过插槽分配内容
<AlertBox>
Something bad happened.
</AlertBox>
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />父组件在子组件的内容会放在默认插槽这里
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
动态组件
通过绑定is属性,可以切换显示的组件
可接受有
被注册的组件名
导入的组件对象
import a2 from './components/a2.vue'
import a1 from './components/a1.vue'
<component :is="a1"></component>
深入组件
注册
全局注册
我们可以使用 Vue 应用实例的
.component()
方法,让组件在当前 Vue 应用中全局可用。
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现, 完全可以传入一个vue组件
{
/* ... */
}
)
如果使用单文件组件,你可以注册被导入的
.vue
文件:
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
还可以链式注册
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
局部注册
显然在组件中使用import导入的组件就是局部组件
Props
父组件
直接在子组件标签上写上需要传输的键值对
<a1 title="标题" hello="你好"></a1>
子组件
需要使用defineProps接收
// 使用数组 defineProps(['foo']) // 使用对象 defineProps({ title: String, likes: Number }) 对于以对象形式声明的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。
响应式 Props 解构
静态 vs. 动态 Props
静态
<a1 title="aa"></a1> // 像这种直接写死的就是静态的
动态
let aa = ref() <a1 :title="aa"></a1> // 这样使用v-bind绑定属性的,就是动态的
使用一个对象绑定多个 prop
const post = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
Prop 校验
通过使用对象来接收,可进行类型校验
defineProps({
属性名:类型构造器
title:Number,
// 例如number类型的构造器就是Number
// 还可以进行如下配置,更加灵活
propC: {
type: String,
required: true
},
})
事件
组件事件
父级组件
<MyComponent @some-event="callback" /> @自定义事件="函数或表达式" 支持修饰词
事件参数
子组件 <button @click="emit('increaseBy', 1)"> Increase by 1 </button>
声明触发的事件
子组件 <script setup> const a = defineEmits(['inFocus', 'submit']) 返回的是一个emit函数 通过a('事件名'):a('inFocus') </script>
事件校验
接受的参数就是抛出事件时传入
emit
的内容,返回一个布尔值来表明事件是否合法。貌似不能阻止父组件函数调用,只是有个警告<script setup> const emit = defineEmits({ // 没有校验 click: null, 不能留空,不写校验函数的话要写null // 校验 submit 事件,返回值为boolean submit: ({ email, password }) => { if (email && password) { return true } else { console.warn('Invalid submit event payload!') return false } } }) function submitForm(email, password) { emit('submit', { email, password }) } </script>
组件v-model
子父组件双向数据绑定?
透传 Attributes
插槽
<MyCon> hi </MyCon>
// MyCon 内容如下
<div>
<slot></slot>内容就会放在这里
</div>
注:没有给定插槽名称时,默认为defalut,就叫这个名
渲染作用域
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
插槽内容是可以访问到当前的父级数据的 ``
但无法访问子级数据,也就是组件的数据
默认内容
组件aa
<div> 等价于
<slot></slot> ===> <slot name="defalut"></slot>
</div>
// 父组件
<aa> 等价于 <aa>
内容 ====> <template #defalut>
</aa> 内容
</template>
</aa>
具名插槽
显式的写出对应的插槽名称
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
组件内容
<BaseLayout> 简化为
<template v-slot:header> ===> #header
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
条件插槽
不太明白,应该是包裹slot,但无法通过简单的去判断当前的slot是否有值,
所以有对应的$slots.插槽名可以得知是否存在该插槽对应的内容,然后让外
部的元素存在,只有对外部的元素添加指定的属性就行
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div v-if="$slots.default" class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
动态插槽名
<template>
a1组件
<p>默认插槽</p>
<slot>
</slot>
<p>具名插槽</p>
<slot name="a11"></slot>
</template>
父组件:
let a = "default"
<A1>
<template #[a]> // 当a为a11时就会出现在a11的插槽内
哈哈哈哈
</template>
</A1>
作用域插槽
解决父级插槽内无法访问子级的数据
- 默认插槽
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
只使用默认插槽才能在组件上使用v-slot
<MyComponent v-slot="slotProps">
// 通过v-slot指定一个变量去接收一个插槽props对象
直接使用.属性获取对应的值,或解构也行
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
- 具名插槽
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
如果使用了具名插槽,那么默认插槽也要变成具名插槽
依赖注入
解决子组件要使用父级组件,乃至爷爷辈组件等的数据,那么可以使用该方式
Provide
设置需要提供给子级组件的数据
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
响应式也能注入
const a = ref()
provide('a', a)// 子级接收时也是一个ref对象
</script>
第一个是一个类比id一样的东西,类型为字符串或Symbol(能提供唯一值)
第二个就是对应的要传给子级组件的数据
Inject
获取来自父级组件的数据
<script setup>
import { inject } from 'vue'
const message = inject('message')// inject('key')
如果不确定是否有提供者,那么可以设置一个默认值
const aa = inject('key', "默认值")
</script>
- 默认值由工厂函数提供
使用Symbol作为key
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, { /*
要提供的数据
*/ });
// 注入方组件
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
获取数据需要取到对应Symbol变量