系列文章目录
Day 01
目录
- 系列文章目录
- 前言
- Day01
- 1.项目使用相关技术栈
- 2. 项目规模和亮点
- 3. Vue2和Vue3实现一个小案例
- 4. vue3的优势
- 5. create-vue脚手架工具
- 6. 熟悉我们的项目目录和文件
- 7. 组合式API-setup选项
- 8. 组合式API-reactive和ref函数
- 9. 组合式API-computed计算属性
- 10. 组合式API-watch监听属性
- 11. 组合式API-生命周期函数
- 12. 组合式API-父子通信
- 13. 组合式API-子传父
- 14.组合式API-模板引用
- 15.组合式API-provide和inject
- 16.综合小案例
- 渲染
- 删除
- 编辑
- Day01小结
前言
vue3现在使用很广泛,必学!
vue3项目,本文主要是学习项目功能实现并进行总结。
配套资料的话去B站下方自己拿。
持续更新…
Day01
1.项目使用相关技术栈
create-vue、Vue3+Setup、VueRouter、VueUse、Pinia
知识点插入:
C端:C是单词Consumer
的缩写,即个人用户。C端产品指的是面向个人用户的产品,如微信、淘宝。
B端:B是单词Business
的缩写,即企业用户。B端产品指的是面向企业用户的产品,如ERP系统、CRM系统。
2. 项目规模和亮点
项目规模:涉及到9大业务模块,27+业务组件、20+业务接口、10+业务解决方案
项目亮点:长页面吸顶交互实现、图片懒加载指令封装、画板插槽组件等业务通用组件封装、SKU电商组件封装、通用逻辑函数封装、路由缓存问题处理。
3. Vue2和Vue3实现一个小案例
需求描述:点击按钮,数字+1
vue2实现:
//vue2 选项式API
<template>
<button @click="addCounter">{{ counter }}</button>
</template>
<script>
export default {
name: 'Counter',
data() {
return {
counter: 0
}
},
methods: {
addCounter() {
this.counter++;
}
}
}
</script>
vue3实现:
//vue3 组合式API
<template>
<button @click="addCounter">{{ counter }}</button>
</template>
<script setup>
//有这个setup就可以写组合式api了
import { ref } from 'vue' //ref 生成响应数据
const counter = ref(0)
const addCounter = () => { counter.value++ }
</script>
vue3的代码量变少了,分散式维护编程了集中式维护了
4. vue3的优势
- 组合式
API
,能更好的支持TS
,容易维护 - 速度提高,
diff
算法重写,模板编译优化,组件初始化更高效了 - 按需引入,良好的
treeShaking
,体积更小 - 数据响应式更优了,
proxy
5. create-vue脚手架工具
我们之前在vue2
学过使用过vue-cli
脚手架
vue3
使用的是create-vue
,底层是vite
,我们使用它来创建项目。
创建环境条件:node.js
版本16.0
以上
创建vue3
应用,安装并执行create-vue
:npm init vue@latest
注:目前配置项我们都是否,做项目的时候按需选择。
这样我们就安装成功了,按照它的绿色代码执行(进入文件夹,安装依赖,运行项目),将我们的代码运行起来
运行起来地址,我们打开:
如果是这个页面就是成功的,这个页面也是一个很好的学习工具,感兴趣可以点击了解一下:
6. 熟悉我们的项目目录和文件
package.json
:本次项目执行的命令和相关的依赖。
vite.config.js
:项目配置文件,在vue2
中是vue.config.js
。
业务文件夹src
下的main.js
入口文件
//入口文件
//vue2中式new Vue()创建一个应用实例对象
//vue3中使用createApp函数创建应用实例
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
//1.以App为参数,生成一个应用实例对象
//2.挂载到id为app的节点上
createApp(App).mount('#app')
这个节点在哪呢,和vue2一样在index.html
中
src中的App.vue
我删了一些代码,结构看起来更清晰
<script setup>
<!--setup:开关,容许在script中书写组合式API-->
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template>
<header>
<img />
<div class="wrapper"></div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
...
</style>
相比于vue2
,script
和template
的顺序发生了改变,template
里不再要求唯一根元素,省去了一些无用的元素。
7. 组合式API-setup选项
我们先看一些组合式api和选项式api是什么
-
组合式API是一种基于函数的API,它允许开发者将组件的逻辑代码拆分成多个独立的函数,每个函数负责处理特定的功能或逻辑。
-
选项式API是基于对象的,我们将一个Vue实例的各个部分拆分成不同的选项,如
data、methods、computed、watch
等,并在创建Vue实例时将它们作为选项传入。
setup的写法:
<script>
export default {
setup() {
...
}
}
</script>
这个函数会在生命周期beforeCreate
之前执行,验证一下:
<!--App.vue-->
<script>
export default {
setup() {
console.log('setup')
},
beforeCreate() {
console.log('beforeCreate')
}
}
</script>
<template>
</template>
<style></style>
setup的用法:
- 在setup定义数据和函数
- setup函数中 return 数据和函数
- 在template中使用数据和方法
做个小练习:
<script>
export default {
setup() {
//1.声明变量和函数
const message = '我是一个小蘑菇'
const logMessage = () => {
console.log(message)
}
return {
//2.返回变量和函数
message,
logMessage
}
},
}
</script>
<template>
//3.使用变量和函数
{{ message }}
<button @click="logMessage">点击我</button>
</template>
<style></style>
保存,看效果:
这里不再推荐使用this,this也获取不到组件实例对象了
这里vue3提供了一个语法糖<script setup>
,可以简化代码的写法,将export default、setup()、return
都简化掉了
刚才那个栗子使用语法糖的写法:
<script setup>
const message = '我是一个小蘑菇'
const logMessage = () => {
console.log(message)
}
</script>
<template>
{{ message }}
<button @click="logMessage">点击我</button>
</template>
8. 组合式API-reactive和ref函数
作用:生成响应数据
响应式数据:数据变而页面变
reactive
:传入对象类型数据,返回一个响应式对象,注意是对象类型
reactive
用法:
- 导入
reactive
- 执行函数(传参,接收)
<script setup>
//引入
import { reactive } from 'vue';
//对象类型变量
const person = {
name: 'ruru',
age: 18
}
//使用函数
const res = reactive(person);
</script>
<template>
<!-- 使用变量 -->
我是{{ res.name }},我今年{{ res.age }}岁了
<button @click="res.age++">年龄+1</button>
</template>
ref
:传入简单或对象类型数据,返回一个响应式对象,简单类型和对象类型都支持
ref
用法:
- 导入
ref
- 执行函数(传参,接收)
注意:脚本区域修改ref产生的响应式对象数据(如 a),需要通过value属性。
<script setup>
//引入
import { ref } from 'vue';
//对象类型变量
const name = 'ruru'
const age = 18
const add = () => {
a.value++
}
//使用函数
const n = ref(name);
const a = ref(age)
</script>
<template>
<!-- 使用变量 -->
我是{{ n }},我今年{{ a }}岁了
<button @click="add">点击a+1</button>
</template>
ref函数内部实现依赖于reactive,实际工作中推荐使用ref函数,比较灵活
9. 组合式API-computed计算属性
思想和vue2
保持一致,只是写法不同
用法:
- 导入
- 执行函数,
return
计算之后的值,变量接收
小栗子实现:[1,2,3,4,5,6,7,8] =》 [3,4,5,6,7,8]
<script setup>
import { computed, ref } from 'vue';
const arr = ref([1, 2, 3, 4, 5, 6, 7, 8]);
const arr2 = computed(() => {
//ref产生的数据要通过value修改,使用filter函数过滤
return arr.value.filter(item => item > 2)
})
</script>
<template>
<div>
原始响应式数组:{{ arr }}
<br />
过滤后的数组:{{ arr2 }}
</div>
</template>
计算属性中不应该有副作用,避免直接修改计算属性的值,计算属性应该是只读的
10. 组合式API-watch监听属性
侦听一个或者多个数据的变化,数据变化时执行监听函数
额外参数:immediate
(立即执行)、deep
(深度监听)
使用:
- 导入函数
- 执行watch函数,参数传入响应式数据和回调函数
举个 单个数据 的栗子
<script setup>
import { watch, ref } from 'vue';
const count = ref(0)
const setCount = () => {
count.value++
}
//监听属性watch
watch(count, (newValue, oldValue) => {
//当count发生改变时打印
console.log('count改变了',newValue, oldValue)
})
</script>
<template>
<div>
当前count:{{ count }}
<button @click="setCount">count+1</button>
</div>
</template>
监听 多个数据 的话,只需要改动一下参数,参考代码⬇
多个数据中,只要有一个数据改变了,就会调用回调函数。
watch(
[count,name],
([newCount,newName],[oldCount.oldName]) =>{
...
}
)
immediate
:侦听器创建时立即触发回调,响应式数据变化继续执行回调
用法示例:
const count = ref(0)
watch(count,()=>{
console.log('count发生了变化')
},{
immediate : true
})
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
选项
deep
:深层监听
const state = ref({count:0})
watch(count,()=>{
//count发生变化也不会打印
console.log('count发生了变化')
})
const state = ref({count:0})
watch(count,()=>{
//会打印
console.log('count发生了变化')
},{
deep:true
})
精确监听
假如目前有两个响应式数据age
和name
,我只想在监听到age数据变化的时候执行回调函数,该怎么办呢?
我们可以这样做:watch参数写两个回调,一个写数据值,另一个写逻辑,大致代码如下:
const person = ref({name:'ruru',age:18})
watch(
()=>person.age.value,
()=> console.log('age改变了')
)
deep
有性能损耗,尽量不开启deep
,使用 精确监听
11. 组合式API-生命周期函数
vue2和vue3生命周期API(选项式 vs 组合式)
选项式API | 组合式API | |
---|---|---|
beforeCreate/created | setup | vue3无onCreated钩子,直接写在setup中即可。 |
beforeMount | onBeforeMount | 挂载之前 |
mounted | onMounted | 挂载完毕 |
beforeUpdate | onBoforeUpdate | 更新之前 |
Updated | onUpdated | 更新完毕 |
beforeUnmount | onBeforeUnmount | 销毁之前 |
unmounted | onUnmounted | 销毁完毕 |
基本使用:
- 导入
- 执行函数,传入回调
我们以onMounted
作栗子来演示生命钩子的使用。
<script setup>
import { onMounted, ref } from 'vue';
onMounted(() => {
console.log('组件挂载完毕')
})
</script>
<template>
</template>
生命周期函数可执行多次,按顺序执行
一种适用场景是:当你拿到一个写好的项目,不敢改别人的代码怕给整错了,就可以创建一个钩子写入自己的代码,不会影响别人的代码。
<script setup>
import { onMounted, ref } from 'vue';
onMounted(() => {
console.log('组件挂载完毕1')
})
onMounted(() => {
console.log('组件挂载完毕3')
})
onMounted(() => {
console.log('组件挂载完毕2')
})
</script>
<template>
<div>
</div>
</template>
12. 组合式API-父子通信
最常用,最简单
基本思想:
- 父组件给子组件绑定属性
- 子组件通过
defineProps
编译器宏,接收子组件传递过来的数据
现在我们准备了父App.vue和子son.vue组件。
子组件导入父组件之后,不需要注册直接使用,这是setup语法糖的一个效果。
<!-- App.vue -->
<script setup>
import { onMounted, ref } from 'vue';
import Son from '@/components/son.vue'
</script>
<template>
<div>
<h2>父组件App</h2>
<Son />
</div>
</template>
<!-- son.vue -->
<script setup>
</script>
<template>
<div class="son">
<h3>子组件son</h3>
</div>
</template>
效果:
现在我们要实现数据的传递:
<!-- App.vue -->
<script setup>
import { onMounted, ref } from 'vue';
import Son from '@/components/son.vue'
</script>
<template>
<div>
<h2>父组件App</h2>
<!-- 1.属性绑定 -->
<Son message='我是父组件的数据' />
</div>
</template>
<!-- son.vue -->
<script setup>
const props = defineProps({
// 2.接收
message: String
})
</script>
<template>
<div class="son">
<h3>子组件son</h3>
<!-- 3.使用 -->
{{ message }}
</div>
</template>
上述代码使用了props
变量接收了数据,我们可以props.message
获取到数据。
补充:
传入响应式数据,传递多个数据,相关代码演示:
// App.vue
const count = ref(0)
<Son :count="count" message = '我是父组件的数据' />
//son.vue
const props = defineProps({
message:String,
count:Number
})
13. 组合式API-子传父
基本思想:
-
父组件给子组件标签通过@绑定事件
-
子组件通过
defineEmits
编译器生成emit方法 -
触发自定义事件,传递参数
通过栗子演示
<!-- App.vue -->
<script setup>
import { onMounted, ref } from 'vue';
import Son from '@/components/son.vue'
const getMessage = (msg) => {
console.log(msg)
}
</script>
<template>
<div>
<h2>父组件App</h2>
<!--1.绑定事件 -->
<Son @get-message="getMessage" />
</div>
</template>
<!-- son.vue -->
<script setup>
//2.生成emit方法
const emit = defineEmits(['get-message'])
//3.触发自定义事件,传参
const sendMsg = () => emit('get-message', '我是来自子组件的信息,收到请回答!')
</script>
<template>
<div class="son">
<h3>子组件son</h3>
<!-- 4.使用 -->
<button @click="sendMsg">给父组件发信息</button>
</div>
</template>
14.组合式API-模板引用
学过vue2的应该很熟悉
通过ref获取真实的dom对象或者组件示例对象。
获取dom使用方法(组件同理):
- 调用 ref 函数生成一个 ref 对象
- 通过 ref 标识绑定 ref 对象到标签
- 通过
value
获取标签
<script setup>
import {ref} from 'vue'
//1.调用ref函数
const h1ref = ref(null)
//组件挂载完毕才能获取到dom对象
onMounted(()=>{
console.log(h1ref.value) //<h1>我是dom标签h1</h1>
})
</script>
<template>
<!--2.通过ref标识绑定ref对象-->
<h1 ref="h1ref">我是dom标签h1</h1>
</template>
defineExpose
:
默认情况下,在
<script setup>
语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose
编译宏指定哪些属性可以访问。
<script setup>
import {ref} from 'vue'
const msg = ref('一段消息')
defineExpose({
msg
})
</script>
15.组合式API-provide和inject
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信。
实现办法:
-
从vue中引入这俩函数
-
顶层数据通过
provide
函数提供数据/响应式数据
provide('key',顶层数据/ref对象)
- 底层组件通过
inject
函数获取数据
const message = inject('key')
除了可以传数据,还可以传方法
-
从vue中引入这俩函数
-
顶层数据通过
provide
函数提供函数
const setCount = ()=>{
count.value++
}
provide('setCount-key',setCount)
- 底层组件通过
inject
函数获取函数
const setCount = inject('setCount-key')
- 就可以在底层组件中使用了
<button @click="setCount">修改顶层组件的count</button>
底层函数想要通知顶层函数组件做修改的话,传递一个方法,底层函数调用方法·
16.综合小案例
找个文件夹,克隆一下,阅读README.md
安装依赖,启动项目:
npm install
npm run dev
页面就是这样的啦:
我们是要实现:渲染、编辑、删除 三个小功能
渲染
思路:声明响应式列表list - 调接口获取数据赋值给list - 绑定给table
const list = ref([])
const getList = async () => {
const res = await axios.get('/list')
list.value = res.data //赋值
}
<el-table :data=list>
...
</el-table>
删除
思路:获取当前行的id - 调用接口 - 更新数据
<!--利用插槽-->
<template #default="{ row }">
<el-button type="primary" link>编辑</el-button>
<el-button type="danger" link @click="delList(row.id)">删除</el-button>
</template>
const delList = async (id) => {
await axios.delete(`/del/${id}`)
getList()
}
编辑
思路:打开弹框 - 回填数据 - 更新数据
点击 编辑 按钮,打开弹框
<el-button type="primary" link @click="onEdit">编辑</el-button>
父组件获取子组件的数据dialogVisible
,使用ref获取子组件的示例对象,调用其方法或者属性
const editRef = ref(null)
<Edit ref="editRef" />
const open = ()=>{
dialogVisible.value = true
}
defineExpose({
open
})
const onEdit = ()=>{
editRef.value.open()
}
这时弹框可以正常打开了。
我们在点击按钮调用onEdit,将数据给Edit组件传下去
<el-button type="primary" link @click="onEdit(row)">编辑</el-button>
const onEdit = (row)=>{
editRef.value.open(row)
}
const open = (row)=>{
dialogVisible.value = true
console.log(row)
}
这样子组件就拿到了数据
准备一个Edit组件的响应式数据form,用来绑定数据
这个属性名称要参考下面的姓名和籍贯书写
const form = ref({
name:'',
place:''
})
const open = (row)=>{
dialogVisible.value = true
//console.log(row)
form.value.name = row.name
form.value.place = row.place
}
双向绑定:
<el-form label-width="50px">
<el-form-item label="姓名">
<el-input placeholder="请输入姓名" v-model="form.name"/>
</el-form-item>
<el-form-item label="籍贯">
<el-input placeholder="请输入籍贯" v-model="form.place"/>
</el-form-item>
</el-form>
更新数据,先调接口,关闭弹窗,通知父组件更新列表
<el-button type="primary" @click="onUpdate">确认</el-button>
import axios from 'axios';
const onUpdate = async ()=>{
await axios.patch(`/edit/${id}`, {
name: form.value.name,
place: form.value.place,
})
}
要传入id,我们补一个id 上面的 ${id} => ${form.value.id}
,姓名和籍贯都改改,async,await也写上。
const form = ref({
name:'',
place:'',
id:''
})
const open = (row)=>{
dialogVisible.value = true
//console.log(row)
form.value.name = row.name
form.value.place = row.place
form.value.id = row.id
}
关闭弹窗
dialogVisible.value = false
通知父组件做列表更新,是子传父,我们使用自定义事件。
<!--绑定事件,将 获取列表数据 的函数传过去-->
<Edit ref="editRef" @on-update="getList"/>
const emit = defineEmits(['on-update'])
emit('on-update')
这样就完成了
项目相关全部代码:
<!-- App.vue -->
<script setup>
import Edit from './components/Edit.vue'
import { onMounted, ref } from 'vue';
import axios from 'axios'
// TODO: 列表渲染
const list = ref([])
const getList = async () => {
const res = await axios.get('/list')
list.value = res.data //赋值
}
onMounted(() => getList())
// TODO: 删除功能
const delList = async (id) => {
await axios.delete(`/del/${id}`)
getList()
}
// TODO: 编辑功能
//思路:打开弹框 - 回填数据 - 更新数据
const editRef = ref(null)
const onEdit = (row) => {
editRef.value.open(row)
}
</script>
<template>
<div class="app">
<el-table :data=list>
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column label="姓名" prop="name" width="150"></el-table-column>
<el-table-column label="籍贯" prop="place"></el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button type="primary" link @click="onEdit(row)">编辑</el-button>
<el-button type="danger" link @click="delList(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<Edit ref="editRef" @on-update="getList" />
</template>
<style scoped>
.app {
width: 980px;
margin: 100px auto 0;
}
</style>
<!-- Edit.vue -->
<script setup>
// TODO: 编辑
import { ref } from 'vue'
import axios from 'axios';
// 弹框开关
const dialogVisible = ref(false)
const emit = defineEmits(['on-update'])
const form = ref({
name: '',
place: '',
id: ''
})
const onUpdate = async () => {
await axios.patch(`/edit/${form.value.id}`, {
name: form.value.name,
place: form.value.place,
})
dialogVisible.value = false //关闭弹窗
emit('on-update')
}
const open = (row) => {
dialogVisible.value = true
form.value.name = row.name
form.value.place = row.place
form.value.id = row.id
}
defineExpose({
open
})
</script>
<template>
<el-dialog v-model="dialogVisible" title="编辑" width="400px">
<el-form label-width="50px">
<el-form-item label="姓名">
<el-input placeholder="请输入姓名" v-model="form.name" />
</el-form-item>
<el-form-item label="籍贯">
<el-input placeholder="请输入籍贯" v-model="form.place" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onUpdate">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<style scoped>
.el-input {
width: 290px;
}
</style>
Day01小结
第一天,讲的是vue3的一些基础知识,做了一个小练习
love and peace
没学vue2的慎入