vue相关配置
1、scss配置
// 安装命令
npm install sass-loader sass -D
// vite.config.js文件
css: {
// css预处理器
preprocessorOptions: {
scss: {
// 引入 mixin.scss 这样就可以在全局中使用 mixin.scss中预定义的变量了
// 给导入的路径最后加上 ;
additionalData: '@import "@/assets/style/mixin.scss";'
}
}
}
1 、Vue3安装
1.1. 【基于 vue-cli 创建】
点击查看官方文档
备注:目前
vue-cli
已处于维护模式,官方推荐基于Vite
创建项目。
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 执行创建命令
vue create vue_test
## 随后选择3.x
## Choose a version of Vue.js that you want to start the project with (Use arrow keys)
## > 3.x
## 2.x
## 启动
cd vue_test
npm run serve
1.2 【基于 vite 创建】(推荐)
vite
是新一代前端构建工具,官网地址:https://vitejs.cn,vite
的优势如下:
-
轻量快速的热重载(
HMR
),能实现极速的服务启动。 -
对
TypeScript
、JSX
、CSS
等支持开箱即用。 -
真正的按需编译,不再等待整个应用编译完成。
-
webpack
构建 与vite
构建对比图如下:具体操作如下(点击查看官方文档)
## 1.创建命令
npm create vue@latest
## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No
1.3 代码片段
{
// Place your 全局 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Print to console": {
"prefix": "vue3",
"body": [
"<template>",
" <div class=\"$TM_FILENAME\">",
" <p> 这是$TM_FILENAME页面$0 </p>",
" </div>",
"</template>",
"",
"<script setup name=\"$TM_FILENAME\">",
"import { ref,reactive,onMounted } from 'vue';",
"",
"",
"",
"",
"</script>",
"",
"<style lang='scss' scoped>",
".$TM_FILENAME{}",
"</style>",
""
],
"description": "Log output to console"
}
}
2、Vue3核心语法
2.1、vue3模板
1 . 基础模板
<template>
<div class="app">
<h1>你好啊!</h1>
<div @click="changeName">{{name}}</div>
</div>
</template>
<script lang="ts">
export default {
name:'App', //组件名
setup(){
let name = '张三'
let age = 18
let tel = '13888888888'
function changeName(){
name = 'zhang-san' //注意:此时这么修改name页面是不变化的
console.log(name)
}
return {name,age,tel,changeName}
}
}
</script>
<style>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
2. 语法糖
2.1 . 不带name
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changName">修改名字</button>
</div>
</template>
<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
let name = '张三'
let age = 18
let tel = '13888888888'
// 方法
function changName(){
name = '李四'//注意:此时这么修改name页面是不变化的
}
</script>
2.2 带name
// 1、安装插件
npm i vite-plugin-vue-setup-extend -D
// 2、注册插件
vite.config.ts页面
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [
vue(),
VueSetupExtend() //这里注册
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
2.2、创建响应式
1. 【ref 与 reactive】
1.1 ref 创建
<script setup lang="ts" name="Person">
// ref可以创建基本数据也可以创建复杂数据
import { ref } from 'vue' //引入
let name = ref('张三') //创建
name.value = '李四' //修改必须通过value
let obj = ref({name:"张三",age:18})
obj.value.name = '李四'
// vscode插件 Vue-Official 配置中 Vue › Auto Insert: Dot Value 可以自动补充value
</script>
1.2 reactive 创建
<script setup lang="ts" name="Person">
// reactive 只可以创建复杂数据,不可重新创建
import { reactive } from 'vue' //引入
let obj = reactive({name:"张三",age:18})
obj.name = '李四' //修改不用通过value
//不可重新创建
obj = reactive({name:"张三",age:18}) //不生效
Object.assign(obj,{name:"李四"}) //可以用 `Object.assign` 修改
</script>
1.3 使用规则
宏观角度看:
ref
用来定义:基本类型数据、对象类型数据;
reactive
用来定义:对象类型数据。
- 区别:
ref
创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。
reactive
重新分配一个新对象,会失去响应式(可以使用Object.assign
去整体替换)。
- 使用原则:
- 若需要一个基本类型的响应式数据,必须使用
ref
。- 若需要一个响应式对象,层级不深,
ref
、reactive
都可以。- 若需要一个响应式对象,且层级较深,推荐使用
reactive
。
2. 【toRefs 与 toRef】
- 结构赋值不是响应式,需要通过
toRefs
设置响应式
let { name, age } = toRefs(person)
<script setup lang="ts" name="Person">
import {ref,reactive,toRefs,toRef} from 'vue'
let person = reactive({name:'张三', age:18, gender:'男'})
let { name, age } = toRefs(person)
// 把person的属性变成响应式,toRefs把所有对象的属性变成响应式
name.value = '李四' //这样修改数据person.name也会修改变成响应式
let ceshi = toRef(person,"name") // 结构出person的name变成响应式
//指定某个属性变成响应式
ceshi.value = '李四'
//这样修改数据person.name也会修改变成响应式
</script>
2.3、 计算属性( computed )
1、计算属性
<template>
<div class="person">
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
全名:<span>{{fullName}}</span> <br>
<button @click="changeFullName">全名改为:li-si</button>
</div>
</template>
<script setup lang="ts" name="App">
import {ref,computed} from 'vue'
let firstName = ref('zhang')
let lastName = ref('san')
// 计算属性——只读取,不修改
let fullName = computed(()=>{
return firstName.value + '-' + lastName.value
})
</script>
2、修改计算属性
<template>
<div class="person">
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
全名:<span>{{fullName}}</span> <br>
<button @click="changeFullName">全名改为:li-si</button>
</div>
</template>
<script setup lang="ts" name="App">
import {ref,computed} from 'vue'
let firstName = ref('zhang')
let lastName = ref('san')
// 计算属性——既读取又修改
let fullName = computed({
// 读取
get(){
return firstName.value + '-' + lastName.value
},
// 修改
set(val){
console.log('有人修改了fullName',val)
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})
function changeFullName(){
fullName.value = 'li-si'
}
</script>
2.4、监听 ( watach )
1、watach
监听
基础设置
<script lang="ts" setup name="Person">
import {watch} from 'vue'
watch(监听值,(newValue,oldValue)=>{
// newValue改变后的值,oldValue改变前的值
},{
deep:true, //深度监听
immediate:true //立即监听,在侦听器创建时立即触发回调
})
</script>
1. 监听ref
【基本类型】
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
let sum = ref(0)
function changeSum(){
sum.value += 1
}
// 监视,情况一:监视【ref】定义的【基本类型】数据
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch() //停止监听
}
})
//可以同时监听多个值
let name1 = ref("测试1")
let name2 = ref("测试2")
watch([name1,name2],(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
})
</script>
2. 监听ref
对象
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
let sum = ref({name:"张三"})
function changeSum(){
sum.value.name = "李四"
}
// 监视,深度监听ref与reactive对象
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{deep:true})
</script>
3. 监听 reactive
对象
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
let sum = reactive({name:"张三"})
function changeSum(){
sum.name = '李四'
}
// 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
})
</script>
4. 监听对象指定属性
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
function changeName(){
person.name += '~'
}
// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
})
// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
</script>
5. 监听多个数据
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
function changeName(){
person.name += '~'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'}
}
// 监视,情况五:监视上述的多个数据
watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
watch([()=>person.name,()=>person.car.c1],(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
</script>
2、watchEffect
监听
-
官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
-
watch
对比watchEffect
-
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
-
watch
:要明确指出监视的数据 -
watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
-
<script lang="ts" setup name="Person">
import {ref,watch,watchEffect} from 'vue'
// 用watchEffect实现,不用
const stopWtach = watchEffect(()=>{
if(temp.value >= 50 || height.value >= 20){
// 自动监听使用的属性
}
if(temp.value === 100 || height.value === 50){
stopWtach()
// 清除监听
}
})
</script>
2.5、标签的 ref
1、普通的 dom
标签上
<template>
<div class="person">
<h1 ref="title1">尚硅谷</h1>
<button @click="showLog">点我打印内容</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref } from 'vue'
let title1 = ref() //定义的名字必须和标签上的ref一样
function showLog(){
console.log(title1.value)
}
</script>
2、用在组件标签上
<!-- 父组件App.vue -->
<template>
<Person ref="ren"/>
<button @click="test">测试</button>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import { ref } from 'vue'
let ren = ref()
function test(){
console.log(ren.value.name)
console.log(ren.value.age)
}
</script>
<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">
import {ref,defineExpose} from 'vue'
// 数据
let name = ref('张三')
let age = ref(18)
/****************************/
// 父组件想要访问子组件的数据,需要通过defineExpose暴漏出去
// 使用defineExpose将组件中的数据交给外部
defineExpose({name,age})
</script>
2.6、生命周期函数
<script lang="ts" setup name="Person">
import {onBeforeMount,onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
console.log('setup',"创建")
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前,直接执行')
})
onMounted(()=>{
console.log('挂载完毕,直接执行')
})
onBeforeUpdate(()=>{
console.log('更新之前,数据更新前,不会直接执行')
})
onUpdated(()=>{
console.log('更新完毕,数据更新后,不会直接执行')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
2.7、自定义hook
- 什么是
hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。 - 自定义
hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
父类
<template>
<div class="person">
<h1 ref="title1">{{comds}}</h1>
<button @click="handleClick">++</button>
</div>
</template>
<script lang="js" setup name="Person">
import { ref } from "vue";
import useSum from "./js/index"; //引入
let { comds, handleClick } = useSum();
</script>
./js/index
import { ref, onMounted } from 'vue'
export default () => {
let comds = ref(0)
const handleClick = () => {
comds.value += 1
}
//向外部暴露数据
return { comds, handleClick }
}
3、Vue-router 路由
3.1、安装命令
## 安装命令
npm install vue-router@4
3.2、创建vue-router
- 创建router文件
import {createRouter,createWebHistory} from 'vue-router' const router = createRouter({ history:createWebHistory(), routes:[ { name:'preson', path:'/preson', component: () => import('@/views/preson.vue'), }, // 重定向 { path:'/', redirect:'/preson' } ] }) export default router
- 注册
router
- 在main.js里面
import { createApp } from 'vue' import App from './App.vue' import router from '@/router/index' const app = createApp(App) app.use(router) app.mount('#app')
- App.vue
<template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="navigate"> <RouterLink to="/home" active-class="active">首页</RouterLink> <RouterLink to="/news" active-class="active">新闻</RouterLink> <RouterLink to="/about" active-class="active">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView></RouterView> </div> </div> </template> <script lang="ts" setup name="App"> import {RouterLink,RouterView} from 'vue-router' </script>
3.3、路由模式
-
history
模式读法:黑斯睿
优点:
URL
更加美观,不带有#
,更接近传统的网站URL
。缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有
404
错误。import { createRouter,createWebHistory } from 'vue-router' const router = createRouter({ history:createWebHistory(), //history模式 /******/ })
-
hash
模式读法:哈希
优点:兼容性更好,因为不需要服务器端处理路径。
缺点:
URL
带有#
不太美观,且在SEO
优化方面相对较差。import { createRouter,createWebHashHistory } from 'vue-router' const router = createRouter({ history:createWebHashHistory(), //hash模式 /******/ })
3.4、嵌套路由
-
编写
News
的子路由:Detail.vue
-
配置路由规则,使用
children
配置项: -
嵌套路由的子路由不需要加
/
const router = createRouter({ history:createWebHistory(), routes:[ { name:'zhuye', path:'/home', component: () => import('@/views/home.vue'), }, { name:'xinwen', path:'/news', component:component: () => import('@/views/news.vue'),, children:[ { name:'xiang', path:'detail', component: () => import('@/views/detail.vue'), } ] }, ] }) export default router
3.5、路由跳转
1. 标签内跳转
- 跳转方式
$router.push
$router.push({path: '/preson',query: {a:1}}) $router.push({name: 'preson',query: {a:1}})
<a @click="$router.push({name: 'preson',query: {a:1},})">首页</a>
-
接受参数
import { useRoute } from 'vue-router' const route = useRoute() route.query.a //接受参数
2. 函数跳转
-
跳转方式
useRouter
import { useRouter, useRoute } from 'vue-router' let router = useRouter() // 跳转 // 方式一 router.push({ name: 'preson',query: {a:'11'}}) // 方式二 router.push({ path: '/preson',query: {a:'11'}})
-
接受参数
import { useRoute } from 'vue-router' const route = useRoute() route.query.a //接受参数
3.6、replace
属性
1. 作用:控制路由跳转时操作浏览器历史记录的模式。
2. 浏览器的历史记录有两种写入方式:分别为```push```和```replace```:
- ```push```是追加历史记录(默认值)。
- `replace`是替换当前记录。
3. 开启`replace`模式:
import { useRouter } from 'vue-router'
let router = useRouter() // 跳转
router.push({
name: 'preson',
replace:true, //关闭记录
query: {
a:'11'
},
})
4、pinia
数据管理
4.1、搭建 pinia 环境
- 读法:皮尼耶
-
第一步
## 安装命令 npm install pinia
-
第二步:操作
src/main.ts
import { createApp } from 'vue' import App from './App.vue' /* 引入createPinia,用于创建pinia */ import { createPinia } from 'pinia' /* 创建pinia */ const pinia = createPinia() const app = createApp(App) /* 使用插件 */ app.use(pinia) app.mount('#app')
4.2 、存储+读取数据
1、存储数据
Store
是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它。- 它有三个概念:
state
、getter
、action
,相当于组件中的:data
、computed
和methods
。 - 命名规范:usexxxxStore
- 具体编码:
src/store/count.ts
// 引入defineStore用于创建store
import { defineStore } from 'pinia'
// 定义并暴露一个store
export const useCountStore = defineStore('count',{
// 数据
state(){
return {
sum:6
}
},
// 函数
actions:{},
// 计算
getters:{}
})
2、读取
组件中使用state
中的数据
1) 读取方式一
<template>
<h2>当前求和为:{{ sumStore.sum }}</h2>
</template>
<script setup lang="ts" name="Count">
// 引入对应的useXxxxxStore
import { useCountStore } from '@/store/count'
// 调用useXxxxxStore得到对应的store
const sumStore = useCountStore()
</script>
2) 读取方式二
<template>
<div class="count">
<h2>当前求和为:{{sum}}</h2>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
/* 引入storeToRefs */
import { storeToRefs } from 'pinia'
//`storeToRefs`
/* 得到countStore */
const countStore = useCountStore()
/* 使用storeToRefs转换countStore,随后解构 */
const {sum} = storeToRefs(countStore)
</script>
4.3、【修改数据】(三种方式)
-
第一种修改方式,直接修改
<template> <h2>当前求和为:{{ sumStore.sum }}</h2> </template> <script setup lang="ts" name="Count"> import { useCountStore } from '@/store/count' const sumStore = useCountStore() // 拿到数据可以直接修改数据, sumStore.sum++ </script>
-
第二种修改方式:批量修改
<template> <h2>当前求和为:{{ sumStore.sum }}</h2> </template> <script setup lang="ts" name="Count"> import { useCountStore } from '@/store/count' const sumStore = useCountStore() // 可以设置多个值同时修改 countStore.$patch({ sum:999, school:'atguigu' }) </script>
-
第三种修改方式:借助
action
修改(action
中可以编写一些业务逻辑)-
src/store/count.ts
import { defineStore } from 'pinia' export const useCountStore = defineStore('count', { // 函数 actions: { increment(value) { // 这里this可以直接访问state定义的数据 this.sum += value }, }, // 数据 state(){ return { sum:6 } }, })
-
组件中调用
action
即可<template> <h2>当前求和为:{{ sumStore.sum }}</h2> </template> <script setup lang="ts" name="Count"> import { useCountStore } from '@/store/count' const sumStore = useCountStore() sumStore.increment(1) </script>
-
4.4、计算属性
// 引入defineStore用于创建store
import {defineStore} from 'pinia'
// 定义并暴露一个store
export const useCountStore = defineStore('count',{
// 状态
state(){
return {
sum:1,
school:'atguigu'
}
},
// 动作
actions:{},
// 计算
getters:{
bigSum:(state):number => state.sum *10,
upperSchool():string{
return this. school.toUpperCase()
}
}
})
4.5 、监听值的修改
通过 store 的 $subscribe()
方法侦听 state
及其变化
<template>
<div class="count">
<h2>当前求和为:{{sum}}</h2>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
/* 引入storeToRefs */
import { storeToRefs } from 'pinia'
/* 得到countStore */
const countStore = useCountStore()
/* 使用storeToRefs转换countStore,随后解构 */
const {sum} = storeToRefs(countStore)
countStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
})
</script>
4.6、【store组合式写法】
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{
// talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
// getATalk函数相当于action
async function getATalk(){
// 发请求,下面这行的写法是:连续解构赋值+重命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把请求回来的字符串,包装成一个对象
let obj = {id:nanoid(),title}
// 放到数组中
talkList.unshift(obj)
}
return {talkList,getATalk}
})
5、组件通信
5.0、常见搭配形式:
传参方式 | 组件关系 |
---|---|
父传子 |
1. props 2. v-model 3. $refs <br / >4. 默认插槽、具名插槽 |
子传父 |
1. props 2. 自定义事件 3. v-model 4. $parent <br / >4. 作用域插槽 |
祖传孙、孙传祖 | 1. $attrs 2. provide 、inject |
兄弟间、任意组件之间 | 1. mitt 2. pinia |
5.1、常见属性
1.1 $event
$event
<div @click="changNan($event)"></div>
1.2、defineProps
// 1.接受父组件传的参数
defineProps(['car','getToy'])
let { name } = defineProps(["name"])
1.3、defineEmits
// 1. 声明事件,向父组件发送事件
defineEmits(['send-toy'])
const emit = defineEmits(['send-toy'])
emit('send-toy',"传参") //向父组件传参
//父组件接受事件
<Child @send-toy="saveToy"/>
function saveToy(value){
console.log("接受子组件传的参数",value)
}
1.4、defineExpose
- 不管是父组件要访问子组件还是子组件要访问父组件,都需要
defineExpose
向外暴露出去
<script setup lang="ts" name="Child2">
import { ref,defineExpose } from "vue";
// 数据
let computer = ref('联想')
let book = ref(6)
// 把数据交给外部
defineExpose({computer,book})
</script>
5.2、【props】
概述:props
是使用频率最高的一种通信方式,常用与 :父 ↔ 子。
- 若 父传子:属性值是非函数。
- 若 子传父:属性值是函数。
父组件
<template>
<div class="father">
<h3>父组件,</h3>
<h4>我的车:{{ car }}</h4>
<h4>儿子给的玩具:{{ toy }}</h4>
<Child :car="car" :getToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
const car = ref('奔驰') // 向父组件传的参数
const toy = ref()
// 方法 子组件给父组件传的参数
function getToy(value){
toy.value = value
}
</script>
子组件
<template>
<div class="child">
<h3>子组件</h3>
<h4>我的玩具:{{ toy }}</h4>
<h4>父给我的车:{{ car }}</h4>
<button @click="getToy(toy)">向父组件传参</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from "vue";
const toy = ref('奥特曼')
// 接受父组件传的参数
defineProps(['car','getToy'])
</script>
5.3、【自定义事件】
- 概述:自定义事件常用于:子 => 父。
父组件
<!--在父组件中,给子组件绑定自定义事件:-->
<Child @send-toy="sendtoy"/>
<script setup >
function sendtoy(value){
console.log("接受子组件的传参",value)
}
</script>
子组件
<script setup name="ceshi1">
let emit = defineEmits(["canshuju"])
emit("canshuju","向父组件传递数据")
</script>
5.4、 【mitt】
概述:可以实现任意组件传参,与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
【第一步】:安装mitt
npm i mitt
【第二步】:新建文件:src\utils\emitter.ts
// 引入mitt
import mitt from "mitt";
// 创建emitter
const emitter = mitt()
// 创建并暴露mitt
export default emitter
emitter.on("绑定事件",()=>{})
【第三步】:接收数据的组件中:绑定事件、同时在销毁前解绑事件:
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 绑定事件
emitter.on('send-toy',(value)=>{
console.log('接受参数',value)
})
onUnmounted(()=>{
// 解绑事件
emitter.off('send-toy')
})
【第四步】:提供数据的组件,在合适的时候触发事件
import emitter from "@/utils/emitter";
function sendToy(){
// 触发事件
emitter.emit('send-toy',"传参")
}
5.5、【v-model】
-
概述:实现 父↔子 之间相互通信。
-
前序知识 ——
v-model
的本质<!-- 使用v-model指令 --> <input type="text" v-model="userName"> <!-- v-model的本质是下面这行代码 --> <input type="text" :value="userName" @input="userName =(<HTMLInputElement>$event.target).value" >
-
组件标签上的
v-model
的本质::moldeValue
+update:modelValue
事件。<!-- 组件标签上使用v-model指令 --> <AtguiguInput v-model="userName"/> <!-- 组件标签上v-model的本质 --> <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
AtguiguInput
组件中:<template> <div class="box"> <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 --> <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件--> <input type="text" :value="modelValue" @input="emit('update:model-value',$event.target.value)" > </div> </template> <script setup lang="ts" name="AtguiguInput"> // 接收props defineProps(['modelValue']) // 声明事件 const emit = defineEmits(['update:model-value']) </script>
-
也可以更换
value
,例如改成abc
<!-- 也可以更换value,例如改成abc--> <AtguiguInput v-model:abc="userName"/> <!-- 上面代码的本质如下 --> <AtguiguInput :abc="userName" @update:abc="userName = $event"/>
AtguiguInput
组件中:<template> <div class="box"> <input type="text" :value="abc" @input="emit('update:abc',$event.target.value)" > </div> </template> <script setup lang="ts" name="AtguiguInput"> // 接收props defineProps(['abc']) // 声明事件 const emit = defineEmits(['update:abc']) </script>
-
如果
value
可以更换,那么就可以在组件标签上多次使用v-model
<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>
5.6、【$attrs 】
-
概述:
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。 -
具体说明:
$attrs
是一个对象,包含所有父组件传入的标签属性。注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value){
a.value = value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">点我更新A</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>
5.7、【$refs、$parent】
-
概述:
$refs
用于 :父→子。$parent
用于:子→父。
-
原理如下:
属性 说明 $refs
值为对象,包含所有被 ref
属性标识的DOM
元素或组件实例。$parent
值为对象,当前组件的父组件实例对象。
- 不管是父组件要访问子组件还是子组件要访问父组件,都需要
defineExpose
向外暴露出去
$refs
访问子组件
<template>
<div class="index">
<button @click="SubComponents($refs)">打印所有子组件实例</button>
<ceshi1 ref="c1"></ceshi1>
<ceshi2 ref="c2"></ceshi2>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref,reactive,onMounted ,defineExpose} from 'vue';
import ceshi1 from './components/ceshi1.vue';
import ceshi2 from './components/ceshi2.vue';
// 1、访问所有子组件:通过标签事件可以访问所有子组件
const SubComponents = (refs)=>{
console.log(refs)
}
// 2、 单独访问某个子组件: 命名与要和标签上的ref一致
let c1 = ref()
let c2 = ref()
// 需要在生命周期函数访问
onMounted(()=>{
console.log(c1,c2,"需要在生命周期函数访问")
})
</script>
$parent
访问父组件
<template>
<div class="ceshi1">
<p> {{name}} </p>
<button @click="ParentComponent($parent)"> </button>
<!-- 需要在标签内传入 -->
</div>
</template>
<script setup name="ceshi1.vue">
const ParentComponent = (parent)=>{
console.log(parent)
//接收访问父组件的实例
}
</script>
5.8、【provide、inject】
-
概述:实现祖孙组件直接通信
-
具体使用:
-
在祖先组件中通过
provide
配置向后代组件提供数据 -
import { provide } from "vue";
-
在后代组件中通过
inject
配置来声明接收数据 -
import { inject } from 'vue';
-
【第一步】父组件中,使用provide
提供数据
<template>
<div class="father">
<h4>资产:{{ money }}</h4>
<Child/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref,reactive,provide } from "vue";
// 数据
let money = ref(100)
// 向后代提供数据
provide('money',money)
provide('自定义名称',money)
// 向后代提供函数,用与接受子组件的传参
const updateMoney = (value)=>{
money.value+= value
}
provide("updateMoney",updateMoney)
//同时可以传多个参数
provide('money',{money,updateMoney})
</script>
【第二步】孙组件中使用inject
配置项接受数据。
<template>
<div class="grand-child">
<h4>资产:{{ money }}</h4>
<button @click="changMoney(6)">点我</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from 'vue';
// 注入数据
// inject("接受父组件定义的名称",设置默认值)
let money = inject('money',0)
let { money,updateMoney } = inject('updateMoney',{money,updateMoney)
// 调用父组件的函数并传值
const changMoney = ()=>{
updateMoney(6)
}
</script>
5.9、【pinia】
参考之前pinia
部分的讲解
5.10、插槽
1、默认插槽 slot
-
父组件
<Category title="今日热门游戏"> <span>我是插槽</span> </Category>
-
子组件
<template> <div class="item"> <slot></slot> </div> </template>
2、具名插槽
-
父组件
<Category title="今日热门游戏"> <span v-slot:c1>我是插槽c1</span> <span v-slot:c2>我是插槽c2</span> </Category>
-
子组件
<template> <div class="item"> <slot name="c1"></slot> <slot name="c2"></slot> </div> </template>
3、
- 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在
News
组件中,但使用数据所遍历出来的结构由App
组件决定)
-
父组件
<Game v-slot="params"> <!-- <Game v-slot:default="params"> --> <!-- <Game #default="params"> --> <ul> <li v-for="g in params.games" :key="g.id">{{ g.name }}</li> </ul> </Game>
-
子组件
<template> <div class="category"> <h2>今日游戏榜单</h2> <slot :games="games" a="哈哈"></slot> </div> </template> <script setup lang="ts" name="Category"> import {reactive} from 'vue' let games = reactive([ {id:'asgdytsa01',name:'英雄联盟'}, {id:'asgdytsa02',name:'王者荣耀'}, {id:'asgdytsa03',name:'红色警戒'}, {id:'asgdytsa04',name:'斗罗大陆'} ]) </script>
6、其它 API
6.1、【shallowRef 与 shallowReactive 】
-
作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
-
用法:
let myVar = shallowRef('00');
-
特点:只跟踪引用值的变化,不关心值内部的属性变化。
shallowReactive
-
作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的
-
用法:
const myObj = shallowReactive({ ... });
-
特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。
总结
通过使用
shallowRef()
和shallowReactive()
来绕开深度响应。浅层式API
创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。
7、Vue3新组件
7.1. 【Teleport】
- 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to='body' >
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
<p>我是弹窗中的一些内容</p>
<button @click="isShow = false">关闭弹窗</button>
</div>
</teleport>
标签:vue,name,记录,let,vue3,组件,import,ref
From: https://www.cnblogs.com/yuluochengxu/p/18369727