vu3入门
vue3
注意:从P41开始记笔记
1.p41 父传子prop沟通的重要性
三种传递方式:
<!-- 1.写法1 -->
<Navbar :title="title" left="返回" right="首页"></Navbar>
<button @:click="handleClick()">改变标题的名称</button>
<!-- 2.写法2 -->
<!-- <Navbar v-bind="{
'my-title':'电影',
left: '返回',
right: '首页'
}"></Navbar> -->
<!-- 3.写法3:将写法2抽象为对象进行绑定 -->
<!-- <Navbar v-bind="propObj"></Navbar>
<button @:click="handleClick()">改变标题的名称</button> -->
<!-- <Navbar title="影院"></Navbar>
<Navbar title="我的"></Navbar> -->
2.p42属性验证与默认属性
在Vue中,当组件的prop验证失败时,Vue不会自动阻止这个prop的绑定,而是会在控制台输出一条警告信息。这意味着即使leftshow的验证失败,Vue仍然会使用这个prop的值,并且这个值将会影响模板中的v-show指令。
在你的代码中,leftshow是一个布尔类型的prop,它有一个验证器,这个验证器检查传入的值是否是'true'或'false'。但是,当你传递给leftshow的值不是'true'或'false'时,验证器会返回false,Vue会在控制台输出一条警告,说明leftshow的值无效。
然而,由于v-show指令是基于JavaScript的布尔值来工作的,它期望的是一个布尔值(true或false),如果传入的值不是有效的布尔值,Vue会尝试将其转换为布尔值。例如,如果leftshow的值是空字符串、null、undefined、0、NaN或'',Vue会将其转换为false;如果值是非空字符串、非0的数字、对象、数组等,Vue会将其转换为true
3.P43属性的透传
单向数据绑定:
4.P44 子传父:自定义事件
once:
5.P45-$refs_父组件的强权
6.p46 $parent和$root
7.p47跨级通信_provide和inject
8.P48发布订阅模式
9.p49组件的动态生成
10.p50异步组件加载
使用好处:懒加载,页面需要用到那个组件时才进行加载
11.插槽
11-1.p53
11-2 具名插槽p54
11-3 作用域插槽
说明:可以通过子组件暴露数据给父组件,父组件操作数据,然后再插槽到子组件中
下载axios:
npm install axios --save
12 生命周期
12-1 生命周期创建
12-2生命周期更新
补充:调用方法执行点击事件,然后需要更新DOM树,但是更新DOM树是一个异步过程
代码参考:
<template>
<div >
<button @click="handleClick()">改变echarts宽度</button>
<div id="main" :style="{width: mywith, height: '400px'}"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default{
data() {
return{
title:"标题",
mywith: '600px',
optinon:{}
}
},
// 1.没啥用,也无法获取组件的属性
beforeCreate() {
console.log(this.title)
},
// 2.可以用于对组件属性的初始化
created() {
this.title="22222",
this.optinon = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
}
},
// 3.使用少,生成Dom节点之前一刻,此时无法得到Dom节点
beforeMount() {
},
// 4.Dom节点挂载完成,可以访问Dom节点
mounted() {
// 订阅发布
// ajax
// setInterval
// 访问dom
// 获取节点,送入echarts初始化,生成图表
// 基于准备好的dom,初始化echarts实例
// 将myChart挂载到当前对象中,提供method使用
this.myCharts = echarts.init(document.getElementById('main'));
// 绘制图表
this.myCharts.setOption(this.optinon);
},
methods: {
handleClick() {
this.mywith = '800px'
// with还是600px
console.log(document.getElementById('main').style.width)
// 生命周期update()更新完成之后,执行一个回调函数,可以只更新某一个节点,
//不像update对所以属性都进行更新
this.$nextTick(()=>{
console.log("nextTick")
this.myCharts.resize()
})
}
},
beforeUpdate() {
console.log("beforeUpdate()方法")
// with还是600px
console.log(document.getElementById('main').style.width)
},
updated() {
console.log("updated()方法")
console.log(document.getElementById('main').style.width)
// 重新改变echarts大小,我将改变大小放到$nextTick()函数中
// this.myCharts.resize()
}
}
</script>
12-3 生命周期销毁
销毁组件中调用了windows对应的函数不会随着组件销毁而销毁,需要手动在unmounted中销毁
13.swapper
14.自定义组件
15 过度效果(内置组件)
15-1 vue内置组件
15-2 使用css库实现过度效果
官网链接:Animate.css | A cross-browser library of CSS animations.
第一步:下载库
npm install animate.css --save
第二步:引入库
import 'animate.css';
第三步:使用
源码参考:
<template>
<div>
<Transition enter-active-class="animate__animated animate__bounceIn"
leave-active-class="animate__animated animate__bounceOut">
<div v-show="isShow">11111</div>
</Transition>
<button @click="handleClick()">显示or隐藏</button>
</div>
</template>
<script>
import 'animate.css';
export default{
data() {
return{
isShow: true
}
},
methods: {
handleClick() {
this.isShow = !this.isShow
}
}
}
</script>
<style>
</style>
15-3 列表过度
16 .VCA(Vue Composition API)入门
17.p68ref函数使用
<template>
app
<div>
<!-- 1.如果使用ref封装基本类型值,模板解析时会自动执行myname.value,所以只需要写一个myname就可以了 -->
{{ myname }}
<input type="text" ref="myinput">
<button @click="handleClick()">改变名字</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
// reactive只能封装对象,ref功能更丰富,可以封装基本类型
const myname = ref("xurong") // 底层:new Proxy({value: "xurong"})
const myinput = ref(null)
const handleClick = () => {
// 2.使用ref封装基本类型值,事件处理的时候,需要调用value进行处理,使用ref外层都封装了value
myname.value = "wang"
// 3.可以拿到输入框的值
// myinput.value拿到代理对象,myinput.value.value:通过代理对象获取输入框中的值
console.log(myinput.value.value)
}
return{
myname,
handleClick,
myinput
}
}
}
</script>
18.toRef和toRefs使用
reactive转ref:
ref转reactive:
19.VCL中computed中的计算属性
逻辑复用-函数封装:
第一步:封装
第二步:直接调用即可
19.p72 VAL中的watch和watchEffect
<template>
<div>
<input type="text" v-model="mytext">
<select name="" id="" v-model="opt">
<option value="aa">aa</option>
<option value="bb">bb</option>
<option value="cc">cc</option>
</select>
{{reactiveValueTest.testText}}
</div>
</template>
<script>
import {ref, watch,reactive} from 'vue'
export default {
setup() {
const mytext = ref("")
// 1.写法1
// watch(mytext,(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue)
// })
// 2.写法2
// watch(()=>mytext.value,(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue)
// })
// 3.写法3:可以监听多组数据源
// const opt = ref("aa");
// watch([mytext,opt],(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue)
// })
// 4.写法4:第一次数据渲染时也打印监听的数据
const opt = ref("aa");
watch([mytext,opt],(newValue,oldValue)=>{
console.log("同步/异步","ajax",newValue)
},{immediate:true})
// 5.推荐使用watch使用ref,以下是对reactive实现监听
const reactiveValueTest = reactive({
testText: "reactive test111"
})
// 对reactive对象中的某个值进行监听
watch(()=>reactiveValueTest.testText,(newValue,oldValue)=>{
console.log("同步/异步","ajax",newValue)
},{immediate:true})
return {
mytext,
opt,
reactiveValueTest
}
}
}
</script>
watchEffect用法:
20.props和emit
父传子:
子传父:
特别要注意父组件监听的参数不要加括号
21.provide和inject
22.生命周期
<template>
app--{{ v}}
<button @click="handleClick">更新value</button>
</template>
<script>
import { onBeforeMount,onMounted,onBeforeUpdate,onUpdated,ref,nextTick} from 'vue';
export default {
setup() {
const v = ref("11111")
onBeforeMount(()=>{
console.log("DOM创建之前调用111")
})
onMounted(()=>{
console.log("订阅,ajax,DOM创建后,swiper,echart初始化222")
})
onBeforeUpdate(()=>{
console.log("DOM树更新之前调用")
})
onUpdated(()=>{
console.log("DOM树更新之后调用")
})
const handleClick = () => {
v.value = "22222"
// DOM树更新完成之后调用
nextTick(()=>{
console.log("nextTick")
})
}
return {
v,
handleClick
}
}
}
</script>
销毁组件时,终止异步函数setInterval
<template>
<div>
clear
</div>
</template>
<script>
import {onBeforeUnmount,onMounted,onUnmounted} from 'vue'
export default{
setup() {
let clearId
onMounted(()=>{
clearId = setInterval(()=>{
console.log(11111)
},1000)
})
onBeforeUnmount(()=>{
})
onUnmounted(()=>{
clearInterval(clearId)
})
}
}
</script>
23.setup语法糖,见代码
<template>
app
<div>
{{ msg }}
<button @click="handleClick">button</button>
<div>
{{ myname }}--{{age }}
</div>
<div>
{{computedName}}
</div>
<Child title="电影" @right="handleRightClick"></Child>
</div>
</template>
<!-- 省略了setup,return -->
<script setup>
import {ref,reactive,toRefs,computed} from 'vue'
import Child from './Child.vue'
const msg = ref("hello,world")
const obj = reactive({
myname: "xu",
age:23
})
// 将obj属性值解构出去
const {myname,age} = {...toRefs(obj)}
const computedName = computed(()=>myname.value.substring(0,1).toUpperCase()+myname.value.substring(1))
const handleClick= () => {
msg.value = "xurong"
}
const handleRightClick = (value) => {
console.log("接收到的子组件值为:",value)
}
</script>
<template>
<div>
<button>返回</button>
{{ title }}
<button @click="handHome">首页</button>
</div>
</template>
<script setup>
import {defineProps,defineEmits} from 'vue'
const props = defineProps({
title: {
type: String,
default: "000000"
}
})
// 向父组件传值
const emit = defineEmits(["right"])
const handHome = () => {
emit("right","来自子组件的值")
}
</script>
24.Vue Router
作用:一个html单页面开发
24-1 使用
说明:router-view被全局注册了,无需进行引入注册操作
24-2 重定向
方式1:
方式2:
错误路径处理和给路径起别名:
24-3 css嵌套语法:
scss:
24-4 p80 路由跳转
结果:
默认会生成a链接
自定义为li元素,也可以为div
24-5 嵌套路由
效果:
自动加载子组件:
24-6 p82 编程导航
1.声明式导航-跳转路径拼接id
2.编程式导航
24-7 p83 动态路由
1.params传参:方式1:路径拼接参数 方式2:对象传参
注意:对象传参需要给上述路径起一个名字
得到携带的参数:
2.query传参:在路径上拼接上?然后拼接上参数进行传参
得到传递的参数:
效果图:
3.返回上一个页面
4.猜你喜欢电影页面跳转(同级别)
结果:
24-8 p84 路由模式
结果:
24-9 p85全局登录拦截
1.只放行登录页面,对其他页面都需要进行登录拦截
// 全局拦截-后台系统,除了登录页面,其他页面都必须授权才能放行访问
router.beforeEach((to,from,next) => {
let isAuthenticated = localStorage.getItem("token");
if(to.name !== 'Login' && !isAuthenticated) next({
name: 'Login'
})
else next()
})
2.对特定页面进行登录拦截
对Centers页面进行拦截:设置标记meta表明访问该路径时需要登录
{
path:"/center",
component:Centers,
meta: {
requiredAuth: true
}
}
逻辑实现:
// 对center页面进行登录拦截
router.beforeEach((to,from,next) => {
let isAuthenticated = localStorage.getItem("token");
console.log(to.fullPath)
if(to.name !== 'Login' && !isAuthenticated && to.meta.requiredAuth) next({
name: 'Login'
})
else next()
})
3.路径拦截通过之后,可以对点击量进行统计
router.afterEach((to,from) => {
// 统计对该电影喜爱数据(可以在路径中获取对应的电影id,然后可以统计该用户对该id统计的次数)
let filmId = to.params.myid;
console.log("提交后端用户行为信息",filmId);
})
24-10 组件内的守卫
1.单个组件的拦截--beforeRouteEnter
2.路由更新之前获取请求参数-- beforeRouteUpdate
4.beforeRouteLeave
24-11 路由懒加载--按需导入对应的组件
结果:
24-12 VCL与路由
1.组件注册
2.路由跳转--引入useRouter
3.改造1
4.onMounted,route,router,onBeforeRouteUpdate--setup模式
5.beforeRouteEnter在组合式模式下没有对应的函数
25 vuex
25- 1 简介
npm install vuex@next --save
1.页面中有多个需要共享的状态,引入vuex,便于维护(非父子通信)
2.缓存部分异步数据,减少后端服务的访问,增加用户体验
使用:
注册:
使用vuex创建属性值:决定是否显示Tabbar组件
实践:
结果:
上述为错误示例:isTabbarShow属性值不可跟踪
25-2 mutations
演示:
改进:
开发中常用写法:
25-3 action--缓存数据
如果你希望在 Vuex 中缓存数据,通常的做法是:
在 action 中进行异步请求(例如,从 API 获取数据)。
在异步操作完成后,将数据提交到 mutation 中,mutation 会更新 store 的状态。
一旦数据被提交到 store,它就会被保存在 Vuex 的状态树中,直到你显式地修改它。
状态树中的数据在默认情况下是响应式的,这意味着当状态发生变化时,任何依赖于该状态的组件都会自动更新。如果你想要缓存数据,你可以选择将数据保存在状态树中,这样它就会在组件之间共享,并且可以在多个组件之间保持同步。
25-4 getter
25-5 辅助函数--mapState,mapAction,mapGetters 鸡肋
25-6 vux--module开发
注意:涉及到辅助函数,命名空间,可以结合上一节重复看,我直接没有使用辅助函数
每一类数据对应一个js模块,然后合并到index.js中
index.js:将下面的切分不同module,然后合并:
/**
* 1.页面中有多个需要共享的状态,引入vuex,便于维护(非父子通信)
2.缓存部分异步数据,减少后端服务的访问,增加用户体验
*/
import {createStore} from 'vuex'
import {CHANGE_TABBAR,CHANGE_CINEMALIST} from './type.js'
import axios from 'axios'
const store = createStore({
state() {
return {
isTabbarShow: true,
cinemaList: []
}
},
// 唯一修改状态的位置--同步
mutations: {
// showTabbar(state) {
// state.isTabbarShow = true;
// },
// hiddenTabbar(state) {
// state.isTabbarShow = false;
// }
[CHANGE_TABBAR](state,playload) {
state.isTabbarShow = playload
},
[CHANGE_CINEMALIST](state,playload) {
state.cinemaList = playload
}
},
// 同步+异步
actions: {
async getCinemaList(store) {
const res = await axios({
url: "https://m.maizuo.com/gateway?cityId=440300&ticketFlag=1&k=1628079",
headers: {
'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777"}',
'x-host':'mall.film-ticket.cinema.list'
}
});
console.log(res.data.data.cinemas);
// 提交mutation
store.commit(CHANGE_CINEMALIST,res.data.data.cinemas);
}
},
// getter store的计算属性
getters: {
filterCinemaList(state) {
return (playload) => {
return state.cinemaList.filter(item =>
item.eTicketFlag === playload)
}
}
}
})
export default store
改造:
/**
* 1.页面中有多个需要共享的状态,引入vuex,便于维护(非父子通信)
2.缓存部分异步数据,减少后端服务的访问,增加用户体验
*/
import { CHANGE_CINEMALIST } from '../type.js'
import axios from 'axios'
const CinemaModule = {
state() {
return {
cinemaList: []
}
},
// 唯一修改状态的位置--同步
mutations: {
// showTabbar(state) {
// state.isTabbarShow = true;
// },
// hiddenTabbar(state) {
// state.isTabbarShow = false;
// }
[CHANGE_CINEMALIST](state, playload) {
state.cinemaList = playload
}
},
// 同步+异步
actions: {
async getCinemaList(store) {
const res = await axios({
url: "https://m.maizuo.com/gateway?cityId=440300&ticketFlag=1&k=1628079",
headers: {
'x-client-info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777"}',
'x-host': 'mall.film-ticket.cinema.list'
}
});
console.log(res.data.data.cinemas);
// 提交mutation
store.commit(CHANGE_CINEMALIST, res.data.data.cinemas);
}
},
// getter store的计算属性
getters: {
filterCinemaList(state) {
return (playload) => {
return state.cinemaList.filter(item =>
item.eTicketFlag === playload)
}
}
}
}
export default CinemaModule
/**
* 1.页面中有多个需要共享的状态,引入vuex,便于维护(非父子通信)
2.缓存部分异步数据,减少后端服务的访问,增加用户体验
*/
import { CHANGE_TABBAR } from '../type.js'
const TabbarModule = {
state() {
return {
isTabbarShow: true
}
},
// 唯一修改状态的位置--同步
mutations: {
// showTabbar(state) {
// state.isTabbarShow = true;
// },
// hiddenTabbar(state) {
// state.isTabbarShow = false;
// }
[CHANGE_TABBAR](state, playload) {
state.isTabbarShow = playload
}
}
}
export default TabbarModule
/**
* 1.页面中有多个需要共享的状态,引入vuex,便于维护(非父子通信)
2.缓存部分异步数据,减少后端服务的访问,增加用户体验
*/
import {createStore} from 'vuex'
import CinemaModule from './module/CinemaModule'
import TabbarModule from './module/TabbarModule'
const store = createStore({
modules: {
CinemaModule,
TabbarModule
}
})
export default store
代码变动:
老师使用了辅助函数,命名空间,我是直接调用方法和计算属性,我觉得这和普通的方法和计算属性可以区别开来
25-7 vca与vuex
vca模式下,不支持 (辅助函数--mapState,mapAction,mapGetters,底层基于this)所有改造的项目中没有使用辅助函数
25-7 vuex持久化插件
将保存在内存中的数据保存到localStore中
npm install --save vuex-persistedstate
结果;
26.pinia
26-1 简介
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态.
由于组合式API中vuex不能够很好使用,所以出现了pinia
npm install pinia
26-2 optionStore
1.aciton
2.getters
26-3 setupStore
tabbarStore.js:
import { defineStore } from 'pinia'
import { ref } from 'vue';
// 第一个参数是唯一的storeId
export const useTabbarStore = defineStore("tabbar", () => {
// state: () => ({
// isTabbarShow: true
// }),
const isTabbarShow = ref(true);
// actions: {
// change(value) {
// // this可以直接获取state对象
// this.isTabbarShow = value;
// }
// },
const change = (value) => {
isTabbarShow.value = value;
}
return {
isTabbarShow,
change
}
})
cinemaStore.js:
import { defineStore } from "pinia";
import axios from 'axios'
import { computed, ref } from "vue";
export const useCinemaStore = defineStore("cinema",() => {
// state: () => ({
// cinemaList: []
// }),
const cinemaList = ref([]);
// actions: {
// // 方法
// async getCinemaList() {
// const res = await axios({
// url: "https://m.maizuo.com/gateway?cityId=440300&ticketFlag=1&k=1628079",
// headers: {
// 'x-client-info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777"}',
// 'x-host': 'mall.film-ticket.cinema.list'
// }
// });
// console.log(res.data.data.cinemas);
// this.cinemaList = res.data.data.cinemas;
// }
// },
const getCinemaList = async() => {
const res = await axios({
url: "https://m.maizuo.com/gateway?cityId=440300&ticketFlag=1&k=1628079",
headers: {
'x-client-info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777"}',
'x-host': 'mall.film-ticket.cinema.list'
}
});
console.log(res.data.data.cinemas);
cinemaList.value = res.data.data.cinemas;
}
// getters: {
// // 根据选择的App订票和前台兑换过滤cinemaList,注意该计算属性返回值是一个参数为value的函数
// filterCinema(state) {
// return (value) => {
// return state.cinemaList.filter(item =>
// item.eTicketFlag === value)
// }
// }
// }
// 老师写法:
// const filterCinema = computed(() =>
// // 一个参数为value的函数
// // 注意cinemaList是ref包装的
// (value) => {return cinemaList.value.filter(item =>
// item.eTicketFlag === value)
// }
// )
// gpt写法
// ue 3 中,computed 函数应该返回一个值或者一个函数,而不是一个函数的声明
const filterCinema = computed(() => {
// 注意cinemaList是ref包装的
return (value) => {
return cinemaList.value.filter(item => item.eTicketFlag === value);
}
});
return {
cinemaList,
getCinemaList,
filterCinema
}
})
27.vant
快速上手 - Vant 4 (vant-ui.github.io)
移动组件库:
npm i vant
27.1 导入vant中的组件作为全局组件和局部组件
1.全局组件:
2.局部组件:
27-2 axios结合Toast组件使用
实现效果:
27-3 List组件
<template>
<div>
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了"
@load="onLoad" offset="10" :immediate-check="false">
<van-cell v-for="item in datalist" :key="item.filmId" @click="handleClick(item.filmId)">
<img :src="item.poster" alt="电影图片展示" style="width: 100px;float: left;">
<div>
{{ item.name }}
</div>
</van-cell>
</van-list>
<!-- <ul>
<li v-for="(item, index) in datalist" :key="item.filmId" @click="handleClick(item.filmId)">
{{ item.name }}
</li>
</ul> -->
</div>
</template>
<script setup>
import axios from 'axios';
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { List as vanList, Cell as vanCell } from 'vant';
const datalist = ref([]);
const router = useRouter();
const loading = ref(false);
const finished = ref(false);
const pageNumber = ref(1);
const totalSize = ref(0);
const handleClick = (id) => {
router.push(`/detail/${id}`);
}
onMounted(async () => {
const res = await axios({
url: "https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=7994369",
headers: {
'x-client-info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777","bc":"110100"}',
'x-host':
'mall.film-ticket.film.list'
}
});
console.log(res.data)
datalist.value = res.data.data.films;
console.log(datalist.value[1]);
totalSize.value = res.data.data.total;
console.log(totalSize.value)
})
const onLoad = async () => {
console.log(datalist.value.length)
if(totalSize.value === datalist.value.length) {
finished.value = true;
return
}
console.log("到底了");
pageNumber.value++;
const res = await axios({
url: `https://m.maizuo.com/gateway?cityId=110100&pageNum=${pageNumber.value}&pageSize=10&type=1&k=7994369`,
headers: {
'x-client-info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"172475767310819585259339777","bc":"110100"}',
'x-host':
'mall.film-ticket.film.list'
}
});
console.log(res.data.data.films)
datalist.value = [...datalist.value,...res.data.data.films];
loading.value = false;
}
</script>
<style scoped lang="scss">
// 外部组件:van-cell为van-list的子节点,无法直接使用类名进行样式绑定,以下为解决方案
// van-cell__value可以借助浏览器的devtool找到
// 深度选择器:表示只要是父节点下有class名为van-cell__value节点,文本都居中显示
:deep(.van-cell__value) {
text-align: left;
}
ul {
li {
padding: 10px;
}
}
</style>
注意点1:样式渲染
// 外部组件:van-cell为van-list的子节点,无法直接使用类名进行样式绑定,以下为解决方案
// van-cell__value可以借助浏览器的devtool找到
// 深度选择器:表示只要是父节点下有class名为van-cell__value节点,文本都居中显示
:deep(.van-cell__value) {
text-align: left;
}
注意点2:
28.element-plus
见代码:
29.TypeScript
29-1:组合式API
1.reactive定义自定义类型的对象
import {reactive,ref} from 'vue'
interface IState {
name:string,
age: number
}
const state:IState = reactive({
name: "xiao3",
age:23
})
2.ref定义变量
法1:
import {ref} from 'vue'
import type {Ref} from 'vue'
const state:Ref<IState> = ref({
name: "aa",
age:23
})
法2:
import {ref} from 'vue'
const state = ref<IState>({
name:"xiao1",
age: 22
})
3.计算属性使用
onMounted(()=>{
console.log(mydiv.value?.textContent+"abc")
})
// 指明该计算属性返回为string类型
const computedName = computed<string>(() => {
return state.value.name.substring(0,1).toUpperCase()+state.value.name.substring(1)
})
4.父传子,子传父
父传子:
子传父: