Vue3
Vite 创建项目
使用 Vite 创建项目需要 node 的版本 >= 12.0.0
npm 创建命令:
npm init vite vue-demo
接下来会让你选择一个需要创建的框架,这里我们选择 vue
? Select a framework: » - Use arrow-keys. Return to submit.
vanilla
> vue
react
preact
lit
svelte
接下来是需要选择是否使用 ts,这里我不是使用就选择 vue,使用 ts 就选择 vue-ts
√ Select a framework: » vue
? Select a variant: » - Use arrow-keys. Return to submit.
> vue
vue-ts
回车选择完成之后就是要 Vite 创建了一个 Vue3 的项目
接下来进入项目目录,安装依赖后,使用 npm run dev
来启动项目
vite.config.js 配置
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
base: './', // 公共路径
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, './src') // 使用 @ 访问 src 目录
}
},
server: {
host: '127.0.0.1',
port: '2001', // 端口号
open: true // 自动打开
}
})
setup
setup
是在单文件组件中使用 Composition API
的编译时的语法糖,相比普通的 script 语法,它有更多的优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 Typescript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
定义变量
在 setup
中可以定义函数或者变量,但是必须将这些函数或变量 return 出去
,才可以正常在模板中使用。
<template>
<h1>{{ text }}</h1>
<button @click="onclick">点击</button>
</template>
<script>
export default {
setup() {
const text = 'hello vue3'
function onclick() {
console.log('被点击了')
}
return {
text,
onclick
}
}
}
</script>
响应式变量 ref
如果想要更改某个变量的时候,那么像下面这种直接赋值的方式修改,是不能实现的:
<template>
<h1>{{ text }}</h1>
<button @click="change">修改内容</button>
</template>
<script>
export default {
setup() {
let text = 'hello vue3'
function change() {
text = '你好啊'
}
return {
text,
change
}
}
}
</script>
如果想要使变量变成响应式,那么就需要创建响应式的变量,可以通过在 vue 来解构出一个函数来创建响应式变量
<template>
<h1>{{ text }}</h1>
<button @click="change">修改内容</button>
</template>
<script>
// 通过在 vue 中解构出 ref 函数
import { ref } from 'vue'
export default {
setup() {
// 使变量变成响应式变量
let text = ref('hello vue3')
function change() {
// 通过变量名.value 来改变变量
text.value = '你好啊'
}
return {
text,
change
}
}
}
</script>
响应式对象
使用解构出的 reactive
可以将对象变成响应式的对象:
<script>
import { reactive } from 'vue'
export default {
setup() {
const student = reactive({
name: '小明',
ahe: 12
})
return {
student
}
}
}
</script>
注:只有将对象或者变量变成响应式的才可以随意修改其值
script setup
在上面,我介绍了 vue3 的全新目录结构,整理使用组合 api,直接暴露出来一个 setup 函数,代码是这样的:
export default {
setup() {
const name = '张三'
return {
name
}
}
}
变量
通过在 setup 中写入变量或者函数,在 return 出去提供给模板使用。
但是 setup 是有个语法糖的写法,就是将 setup 当作属性直接作用到 script 标签上,如下:
<template>
{{ name }}
</template>
<script setup>
const name = '张三'
</script>
将 script
标签添加 setup
属性之后,这样定义的变量 name 变量,**可以在模板中直接使用,并不需要 return 出去。**这样的代码段自然而然又变得简洁了很多。其实 script setup 就相当于在编译运行是把代码放到了 setup 函数中运行,然后把导出的变量定义到上下文中,并包含在返回的对象中。
导入组件
对于导入组件,导入之后可以直接在模板上使用,并不需要注册,也可以正常工作。
<template>
<my-button />
</template>
<script setup>
import MyButton from './components/MyButton.vue'
</script>
获取 props
在 <script setup>
中必须使用 defineProps
api 来声明 props
:
父组件
<template>
<my-button title1="这是标题1" title2="这是标题2" />
</template>
<script setup>
import MyButton from './components/MyButton.vue'
</script>
子组件
<template>
<h1>这是我的组件-{{ title1 }}-{{ title2 }}</h1>
</template>
<script setup>
const props = defineProps({
title1: String,
title2: String
})
</script>
子组件发送 emits
在 <script setup>
中必须使用 defineEmits
api 来声明 emits
:
父组件
<template>
<h1>{{ title }}</h1>
<my-button @changeTitle="change" />
</template>
<script setup>
import MyButton from './components/MyButton.vue'
import { ref } from 'vue'
const title = ref('这是父组件的内容')
function change(val) {
title.value = val
}
</script>
子组件
<template>
<h2>这是我的子组件</h2>
<button @click="onchangeTitle">点击</button>
</template>
<script setup>
// 使用 defineEmits 来声明 emit,里面是一个数组
// 数组的值是发送自定义事件的事件名
const emit = defineEmits(['changeTitle'])
function onchangeTitle() {
emit('changeTitle', '嘿嘿嘿')
}
</script>
缺失地方
当然,这种语法糖的写法,也是会有缺失的地方,有时候我们需要更改组件选项,比如添加 name 属性,这时候就需要再引入一个 script,在上方写入对应的export
即可
<script>
export default {
name: 'app'
}
</script>
<script setup>
const name = '小明'
</script>
生命周期
下面是 vue2 -> vue3 的生命周期钩子函数
beforeCreate -> setup() // 创建之前
created -> setup() // 创建完成
beforeMount -> onBeforeMount // 挂载前
mounted -> onMounted // 挂载完成
beforeUpdate -> onBeforeUpdate // 更新前
updated -> onUpdated // 更新完成
beforeDestroy -> onBeforeUnmount // 销毁前
destroyed -> onUnmounted // 销毁完成
errorCaptured -> onErrorCaptured
renderTracked -> onRenderTracked
renderTriggered -> onRenderTriggered
activated -> onActivated
deactivated -> onDeactivated
在 vue3 中,移除了 beforeCreate
和 created
,现在使用 setup
就可以直接优先加载了,其余钩子函数都需要通过解构引入才能进行使用。
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('onMounted')
})
console.log('setup')
}
}
// 输出结果:
// setup
// onMounted
那么想要通过接口获取数据就可以使用下面方式:
import { onMounted } from 'vue'
import axios from 'axios'
export default {
setup() {
onMounted(() => {
loadData()
})
function loadData() {
axios({
method: 'GET',
url: 'http://api.wod.xyz/Ip/outGetIpInfo?ip=57.23.66.35'
}).then((res) => {
console.log(res)
})
}
}
}
计算属性
计算属性 computed 也是需要在 vue 中结构来引入的:
<template>
<h1>{{ num }}</h1>
</template>
<script setup>
// 引入 computed
import { computed } from 'vue'
// computed 内部传入一个回调函数再赋值给变量 num
const num = computed(() => {
// 计算属性必须有返回值
return 10 + 20
})
</script>
组合 api 的优势
在之前选项 api 中,有很多钩子函数,可能在 data 中定义的很多的数据,然后 methods 中,一堆的方法,create 一堆的方法,还有各种函数中都存在很多的方法,这样就会显得非常的凌乱,不清楚那些数据和那些函数是有关系的。
但是组合 api 可以将所有的变量数据函数全部都放在 setup 一个函数中,这样其实我们可以将固定的模块抽离出一个单独的文件进行处理,然后再引入传参解构进行调用,如果逻辑很多的情况下,使用这种组合拆分的方式,你的组件里的代码就会越来越少了,每个模块单独管理方便维护。
组件绑定事件
在 Vue2 中,想要给组件绑定事件需要在子组件向父组件发送自定义事件才可以,但是在 Vue3 中,可以对组件直接进行绑定事件
子组件
<template>
<button>按钮</button>
</template>
父组件
<template>
<Btn @click="add" />
</template>
<script setup>
import Btn from './components/demo/Btn.vue'
function add() {
alert('点击了')
}
</script>
插槽
Vue3 相比 Vue2 插槽也有了一定的变化,尤其是具名插槽
普通插槽没有变化,下面来举出具名插槽的例子:
子组件
<template>
<!-- 子组件相比以往是没有变化的,具名插槽使用 name 定义插槽名称 -->
<h1>
<slot name="title"></slot>
</h1>
<p>
<slot name="text"></slot>
</p>
</template>
父组件
<template>
<MyCom>
<template v-slot:title>这是标题</template>
<template v-slot:text>这是一个段落</template>
</MyCom>
</template>
<script setup>
import MyCom from './components/demo/My-com.vue'
</script>
在父组件中,插槽必须统一使用 template 容器来进行存放,内部可以有其他标签,但是容器标签不能改变,在容易上使用 v-slot:插槽名 方式来绑定对应的插槽名称插入对应的内容。
注:v-slot 指令只能在 template 标签上使用!!!
获取 DOM
获取单个 DOM
<template>
<div ref="myRef">获取单个DOM元素</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const myRef = ref(null)
onMounted(() => {
console.log(myRef)
})
</script>
获取多个 DOM
<template>
<div :ref="setRef">
<div>获取多个DOM元素</div>
<input type="text" />
<button>按钮</button>
<p>这是一段文字</p>
</div>
</template>
<script setup>
const setRef = (el) => {
console.log(el)
}
</script>
nextTick 函数
<template>
<div class="text">获取单个DOM元素</div>
</template>
<script setup>
import { nextTick } from 'vue'
nextTick(() => {
console.log(document.querySelector('.text'))
})
</script>
组件上绑定 v-model
父组件
<template>
<!-- 组件上绑定 v-mode 对应下面一个响应式的变量 -->
<MyInput v-model="text" />
</template>
<script setup>
import MyInput from './components/MyInput.vue'
import { ref } from 'vue'
const text = ref('')
</script>
子组件
<template>
<!--
type 是文本框的内容 绑定传递的参数 modelValue
默认监听 input 事件,这里我们来调用自己定义的 myInput 函数
-->
<input :type="modelValue" @input="myInput" />
</template>
<script setup>
// 在父组件上绑定了 v-model 后,相当于传递了一个 modelValue 的参数
// modelValue 的名字的固定的,不能改变
// 并且 prop 还会抛出 update:modelValue 事件,事件名称也是不能改变的
const props = defineProps({
modelValue: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
// 通过调用函数 向父组件发送文本框的内容
function myInput(evt) {
emit('update:modelValue', evt.target.value)
}
</script>
<style scoped>
input {
width: 200px;
height: 35px;
border-radius: 5px;
color: #3f536e;
border: 1px solid #c5d9e8;
outline: 0;
padding: 0 10px;
transition: border 0.35s;
background: #fff;
box-sizing: border-box;
}
</style>
provide / inject
这是一个组合 api,必须两个同时使用才可以生效
provide
和 inject
启用依赖注入。这两者只能在使用当前活动实例的 setup()
期间被调用
无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者,provide
只能通过父组件来提供给子组件,不能子组件给父组件提供,子组件通过inject
注入。
父组件
<template>
<h1>这是父组件</h1>
<my-component></my-component>
</template>
<script setup>
import MyComponent from './components/MyComponent.vue'
import { provide } from 'vue'
const say = '这是父组件提供的数据' // 需要提供的数据
// 使用 provide 函数,第一个参数为提供数据的名称,可以自定义,第二个是需要提供的数据
provide('AppSay', say)
</script>
子组件
<template>
<h1>这是子组件</h1>
<h2>父组件提供的数据是:{{ res }}</h2>
</template>
<script setup>
import { inject } from 'vue'
// 通过 inject 函数接收父组件提供的数据,可以通过名称 AppSay 获取
const res = inject('AppSay')
</script>
component / is
通过 component
标签配合 is
属性,可以绑定一个动态的组件进行渲染
<template>
<h1>这是父组件</h1>
<!-- 通过计算 is 来绑定一个计算属性来渲染组件 -->
<component :is="componentsName"></component>
<!-- 点击按钮渲染不同的组件 -->
<button @click="change">改变组件</button>
</template>
<script setup>
// 引入两个组件
import MyInput from './components/MyInput.vue'
import MyButton from './components/MyButton.vue'
import { computed, ref } from 'vue'
// 通过变量控制渲染的组件
const text = ref(true)
// 点击按钮调用函数,改变变量控制渲染的组件
function change() {
text.value = !text.value
}
// 计算属性根据 text 的变量来返回不同的组件名称进行渲染
const componentsName = computed(() => {
return text.value ? 'MyInput' : 'MyButton'
})
</script>
在组件上使用 keep-alive
我们之前曾经在一个多标签的界面中使用 is
来切换不同的组件
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,比如下面引入的子组件中有一个文本框的组件 MyInput
,但是当我点击按钮写换渲染的组件之后再切换回来的时候,发现之前在文本框中输入的内容没有了,但是我想在输入之后切换组件回来的时候文本框中的内容依然存在,那么就需要 keep-alive
元素将其动态组件包裹起来,那么这样的话失活的组件将会被缓存,当我切换回 MyInput
组件的时候,里面内容依然存在
<keep-alive>
<component :is="componentsName"></component>
</keep-alive>
事件修饰符
.stop
下方代码中,当点击红色盒子的时候,因为父级也有一个事件,所以会导致事件冒泡,会同时触发 change1 和 change2 两个函数,那么可以通过事件对象 event.stopPropagation()
来阻止向上的冒泡:
<template>
<div @click="change1" style="background: green">
<div @click="change2" style="background: red; width: 200px">123</div>
</div>
</template>
<script setup>
function change1() {
console.log('change1')
}
function change2(event) {
event.stopPropagation()
console.log('change2')
}
</script>
但是在 vue 中又加入了 dom 操作,显然是不太友好的,那么就可以通过事件修饰符 .stop 来阻止向上的事件冒泡:
<template>
<div @click="change1" style="background: green">
<div @click.stop="change2" style="background: red; width: 200px">123</div>
</div>
</template>
<script setup>
function change1() {
console.log('change1')
}
function change2() {
console.log('change2')
}
</script>
.capture
还有一种情况是,想要在捕获阶段就执行函数,因为 change1 在外面,我希望先执行 change1,然后再执行 change2,通过事件修饰符 .capture 可以实现效果:
<template>
<div @click.capture="change1" style="background: green">
<div @click.capture="change2" style="background: red; width: 200px">
123
</div>
</div>
</template>
.self
还是上面例子,当我们点击内部红色盒子的时候,其实并不是真正点击的外面绿色盒子,change1 执行是由于冒泡导致的,我希望是真正点击的时候它才会执行,通过事件修饰符 .self 可以实现效果:
<template>
<div @click.self="change1" style="background: green">
<div @click="change2" style="background: red; width: 200px">123</div>
</div>
</template>
.once
有时候,我希望有些事件只执行一次,之后就再也不执行,可以通过修饰符 .once
来完成效果,但这是一个小众的修饰符:
<template>
<div @click="change1" style="background: green">
<div @click.once="change2" style="background: red; width: 200px">123</div>
</div>
</template
.prevent
用 .prevent 可以阻止一些元素的默认行为:
<template>
<a href="https://baidu.com" @click.prevent="link">百度</a>
</template>
.passive
因为有些元素会自带默认行为,那么有默认行为的事件,js 都会首先检测一下是否有阻止 默认行为,比如 <a href="''></a>
,其实像这种的还好,因为我们不会经常的去使用,那么如果有类似滚动的元素,那么每滚动一下,js 都要进行判断是否有阻止默认行为,这样就会有一定的性能损伤,所以页面可能会有一定的性能降低,尤其在移动端,可能不会显得那么的顺滑,如果所有的默认行为都不阻止,js 也有不用判断了,那么就可以用到了 .passive 修饰符
passive 修饰符的含义就是:默认行为在 js 内部不需要判断了,我不阻止你。所以可以省去这一段的判断环节,例如下方滚动案例,每当滚动的时候,js 都需要检测一下是否阻止了默认行为,所以需要加入事件修饰符:
<template>
<div id="box" @scroll.passive="onScroll">
<h1 v-for="(s, i) in 30" :key="i">{{ s }}</h1>
</div>
</template>
<script setup>
function onScroll() {
console.log('11')
}
</script>
<style scoped>
#box {
height: 300px;
overflow-y: auto;
border: 5px solid black;
}
</style>
表单修饰符
.lazy
在以下场景中,数据进行双向绑定,在文本框中输入内容之后,会同步到 p
标签中,但是有些时候逻辑过于多的时候,或者请求后端,这样同步更新数据会有一定的性能损耗,所以给双向绑定的 v-model
属性添加 .lazy
修饰符可以在输入途中不会同步,当文本框失去焦点之后再进行同步数据:
<template>
<input type="text" v-model.lazy="text" />
<p>{{ text }}</p>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
.number
下方例子中,双向绑定的值希望 +100 作为结果显示,但是正常状态下,会将文本框内输入的变成字符串来解析,那么就变成了字符串拼接:
<input type="text" v-model="text" />
<p>{{ text + 100 }}</p>
但是如果希望变成的是加和的状态,就需要修饰符 .number
来解决,这样就可以将文本框内输入的以数字形式解析出来并加和
<input type="text" v-model.number="text" />
<p>{{ text + 100 }}</p>
如果你在文本框内输入了汉字或者英文字母之后,就算你加入了 .number 修饰符,系统也不会正常工作,要确保你输入的是纯数字,才可以正常工作!
.trim
在输入标题的时候,可能会将标题等发送至后端存入数据库中,但是并不希望开头和结尾插入空格,因为当用户每输入一个空格,虽然是看不到的,但是仍然会占用数据库的存储空间,这时就可以使用 .trim
来清除文本开头和结尾的空格,无论输入多少空格,都默认会被清除
<input type="text" v-model.trim="title" />
<p>{{ title }}</p>
注意:仅仅可以清楚段落开头和结尾的空格,作用在中间部分的空格会被保留!
键盘鼠标事件
当我们按下键盘的时候就会触发事件:
<template>
<input type="text" @keyup="key" />
</template>
<script setup>
function key(event) {
console.log(event)
}
</script>
那么在接受到的事件对象中我们可以看到里面有一个key
的属性,就是我们当前按下的键位
也可以通过事件修饰符来指定按下哪个键位触发函数,比如下方按下 b
触发函数:
<template>
<input type="text" @keyup.b="key" />
</template>
<script setup>
function key(event) {
console.log(event.key)
}
</script>
也可以结合使用快捷键,例如下方 ctrl + b
触发事件:
<template>
<input type="text" @keyup.ctrl.b="key" />
</template>
鼠标左中右事件定义方式:
<template>
<button @click.left="change1">按钮1</button>
<button @click.middle="change2">按钮2</button>
<button @click.right="change3">按钮3</button>
</template>
<script setup>
const change1 = () => console.log('change1')
const change2 = () => console.log('change2')
const change3 = () => console.log('change3')
</script>
在 style 中使用 v-bind
vue3 中可以在 style
中使用 v-bind
来绑定 js 中的变量,比如下面例子中,一个累加器来渲染不同的颜色
<template>
<h1 @click="add">切换颜色</h1>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('1')
const color = ref('red')
const add = () => {
text.value++
text.value % 2 === 0 ? (color.value = 'red') : (color.value = 'blue')
}
</script>
<style scoped>
h1 {
color: v-bind(color);
}
</style>
watch
累加器每次数值进行改变的时候执行 watch
函数,下面是 vue3 中 watch 的写法:
<template>
<button @click="add">{{ text }}</button>
</template>
<script setup>
import { ref, watch } from 'vue'
const text = ref(1)
const add = () => text.value++
watch(
() => text.value,
(newText, oldText) => {
// newText 是新值,oldText 是老值
console.log(newText, oldText)
}
)
</script>
vue3 的特性
- RFC
在 Github 接受整个社区的需求和请求来 讨论整个 Vue 的发展方向
- 新的相应式系统
新的相应式系统是基于 ES6 的 proxy 来设计的
- Composition API 和
<script setup>
script setup 可以更快的帮我们编写 Composition
- TypeScript 重构
调试阅读源码更加方法,每个数据都有数据类型,每个函数知道参数和返回对象是什么
- Vite
速度非常快,的工程化工具
Src 引入
如果你倾向于将 *.vue
组件拆分为多个文件,可以使用 src
attribute 来引入外部的文件作为语言块
下面列举出三个文件,分别是 index.html index.js index.css
:
html
<h1>{{ text }}</h1>
<button @click="change">点击</button>
<ul>
<li v-for="(item, index) in arr" :key="index">{{ item }}</li>
</ul>
js
import { ref } from 'vue'
export default {
setup() {
const text = ref('这是首页app组件')
const arr = [1, 2, 3, 4, 5]
function change() {
alert('点击了')
}
return {
change,
text,
arr
}
}
}
css
h1 {
color: red;
}
这样分别写好之后,然后引入到组件中,那么这时候我们的组件的样子就是:
<template src="./app/index.html"></template>
<script src="./app/index.js"></script>
<style scoped src="./app/index.css"></style>
可以试一下效果依然可以实现