Vite+TS+Vue3.0 笔记一
- Vite工具和Vue都可以创建Vu3项目,Vite运行编译更快,几乎瞬间可以完成。
- Vue3.0的.vue文件可以不用根标签包裹。
ref数据响应式和Ref对象
- ref响应需要用到.value赋值,不可直接赋值
<script setup lang="ts">
import { ref,Ref } from "vue";
let msg: Ref<string> = ref('小松')
const changeMsg = () => {
msg.value = '小虎'
console.log(msg);
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
isRef用于判断是否为Ref对象
<script setup lang="ts">
import { ref,Ref,isRef } from "vue";
let msg: Ref<string> = ref('小松')
let notRef: number = 1
const changeMsg = () => {
msg.value = '小虎'
console.log(isRef(msg)); //true
console.log(isRef(notRef)); //false
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
shallowRef包装对象,节省性能,对应对象的属性
<script setup lang="ts">
import { shallowRef } from "vue";
let msg = shallowRef({
name: '小松'
})
const changeMsg = () => {
msg.value = {
name: '大松'
}
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
triggerRef强制更新dom
<script setup lang="ts">
import { shallowRef,triggerRef } from "vue";
let msg = shallowRef({
name: '小松'
})
const changeMsg = () => {
// msg.value = {
// name: '大松'
// }
msg.value.name = '大送'
triggerRef(msg)
}
</script>
自定义customRef
<script setup lang="ts">
import { customRef } from "vue";
function MyRef<T>(value:T) { //T是泛型
return customRef((trank,trigger) => {
return {
get () {
trank()
return value
},
set(newVal: T) {
console.log('SET');
value = newVal
trigger()
}
}
})
}
let msg = MyRef<string>('小曼')
const changeMsg = () => {
msg.value = '大满'
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
shallowRef和triggerRef结合使用
<script setup lang="ts">
import { shallowRef,triggerRef } from "vue";
const msg = shallowRef({
foo:'小送',
bar:'小西'
})
const changeMsg = () => {
msg.value.foo = 'xiaoman'
console.log(msg);
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
想要让视图同步更新,需要用到triggerRef强制更新数据
<script setup lang="ts">
import { shallowRef,triggerRef } from "vue";
const msg = shallowRef({
foo:'小送',
bar:'小西'
})
const changeMsg = () => {
msg.value.foo = 'xiaoman'
triggerRef(msg)
console.log(msg);
}
</script>
<template>
<button @click="changeMsg">change</button>
<div>{{ msg }}</div>
</template>
reactive响应式对象
- 传基础数据类型会报错,字符串等,基础数据类型使用ref
- 可传复杂数据类型,数组和对象等,复杂数据类型使用reactive
- reactive响应对象时不需要用.value赋值,可直接赋值
<script setup lang="ts">
import { reactive } from "vue";
let msg = reactive([])
let obj = reactive({
name: '小西'
})
obj.name = '大西'
</script>
<template>
{{msg}}
{{obj}}
</template>
- 异步赋值数组(请求接口的时候)
<script setup lang="ts">
import { reactive } from "vue";
let msg = reactive<number[]>([])
setTimeout(() => {
let arr = [1,2,3,4]
// msg = arr 直接赋值不会响应
msg.push(...arr) //解构会响应
},1000)
</script>
<template>
{{msg}}
</template>
另外一种数组赋值方式
<script setup lang="ts">
import { reactive } from "vue";
type O = {
list:number[]
}
let msg = reactive<O>({
list:[]
})
setTimeout(() => {
msg.list = [1,2,3,4]
},1000)
</script>
<template>
{{msg}}
</template>
readonly会将proxy对象进行拷贝一份,设置只读模式,不可以操作数据
<script setup lang="ts">
import { reactive,readonly } from "vue";
let person = reactive({
count:1
})
person.count++
let copy = readonly(person)
copy.count++
</script>
shallowReactive只对浅层数据发生响应,对于深层数据只会改变值,但不会更新视图
- 浅层,数据可响应,视图同步更新
- 深层,数据可响应,但视图不更新
<script setup lang="ts">
import { shallowReactive } from "vue";
let msg = shallowReactive({
test: '小满', //浅层,数据可响应,视图同步更新
nav:{ //深层,数据可响应,但视图不更新
bar:{
name: '大满'
}
}
})
const change1 = () => {
msg.test = '小西'
}
const change2 = () => {
msg.nav.bar.name = '大西'
}
</script>
<template>
{{msg}}
<button @click="change1">change1</button>
<button @click="change2">change2</button>
</template>
toRef是引用,修改响应式数据会影响以前的数据,数据发生变化,界面就会自动更新.
-
ref、toRef、toRefs 都可以将某个对象中的属性变成响应式数据
-
ref的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
-
toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
-
toRef 一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
-
toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行
<script setup lang="ts">
import { reactive, toRef } from "vue";
const obj = reactive({
foo: 1,
bar: 1
})
const state = toRef(obj,'bar')
const change = () => {
state.value++
console.log('原始对象>>>',obj);
console.log('引用对象>>>',state);
}
</script>
<template>
<div>{{state}}</div>
<div><button @click="change">change</button></div>
</template>
toRefs接收一个对象,toRef 一次仅能设置一个数据
<script setup lang="ts">
import { reactive, toRefs } from "vue";
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
console.log(foo, bar);
const change = () => {
foo.value++
bar.value++
}
</script>
<template>
<div> >>>{{foo}}</div>
<div> >>>{{bar}}</div>
<div><button @click="change">change</button></div>
</template>
toRaw将响应式代理对象变为普通对象
<script setup lang="ts">
import { reactive, toRaw } from "vue";
const obj = reactive({
foo: 1,
bar: 1
})
const raw = toRaw(obj)
console.log('响应式对象',obj);
console.log('普通对象',raw);
</script>
计算属性 computed的使用
<script setup lang="ts">
import { computed, ref } from "vue";
let firstName = ref('')
let lastName = ref('')
</script>
<template>
<div>
<input v-model="firstName" type="text">
<input v-model="lastName" type="text">
<div>
{{ firstName }} - {{ lastName }}
</div>
</div>
</template>
另外一种书写方式
<script setup lang="ts">
import { computed, ref } from "vue";
let firstName = ref('')
let lastName = ref('')
const name = computed(() => {
return firstName.value + '----' + lastName.value
})
</script>
<template>
<div>
<input v-model="firstName" type="text">
<input v-model="lastName" type="text">
<div>
{{ name }}
</div>
</div>
</template>
另外一种处理方式
<script setup lang="ts">
import { computed, ref } from "vue";
let firstName = ref('')
let lastName = ref('')
const name = computed({
get (){
return firstName.value + '----' + lastName.value
},
set(){
firstName.value + '----' + lastName.value
}
})
</script>
<template>
<div>
<input v-model="firstName" type="text">
<input v-model="lastName" type="text">
<div>
{{ name }}
</div>
</div>
</template>
购物车案例
<script setup lang="ts">
import { reactive,ref } from "vue"
type Shop = {
name: string,
num: number,
price: number
}
const data = reactive<Shop[]>([
{
name:'裤子',
num:1,
price:100
},
{
name:'上衣',
num:1,
price:200
},
{
name:'外套',
num:1,
price:300
}
])
let $total = ref(0)
const addAndSub = (item: Shop,type: boolean):void =>{
if(item.num > 1 && !type){
item.num--
total()
}
if(item.num < 99 && type){
item.num++
total()
}
}
const del = (index: number) => {
data.slice(index,1)
}
/*
reduce 计算总价,
*/
const total = () => {
$total.value = data.reduce((prev, next) => {
return prev + (next.num * next.price)
},0)
}
total()
</script>
<template>
<div>
<table border width="800px">
<thead>
<tr>
<th>名称</th>
<th>数量</th>
<th>单价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(item, index) in data">
<td align="center">{{ item.name }}</td>
<td align="center">
<button @click="addAndSub(item,false)"> - </button>
{{ item.num }}
<button @click="addAndSub(item,true)"> + </button>
</td>
<td align="center">{{ item.price }}</td>
<td align="center">
<button @click="del(index)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<td></td>
<td></td>
<td></td>
<td align="center">总价: {{$total}}¥</td>
</tfoot>
</table>
</div>
</template>
用computed计算属性
<script setup lang="ts">
import { computed, reactive,ref } from "vue"
type Shop = {
name: string,
num: number,
price: number
}
const data = reactive<Shop[]>([
{
name:'裤子',
num:1,
price:100
},
{
name:'上衣',
num:1,
price:200
},
{
name:'外套',
num:1,
price:300
}
])
let $total = ref(0)
const addAndSub = (item: Shop,type: boolean):void =>{
if(item.num > 1 && !type){
item.num--
}
if(item.num < 99 && type){
item.num++
}
}
const del = (index: number) => {
data.slice(index,1)
}
/*
reduce 计算总价,
*/
$total = computed<number>(() => {
return data.reduce((prev, next) => {
return prev + (next.num * next.price)
},0)
})
</script>
<template>
<div>
<table border width="800px">
<thead>
<tr>
<th>名称</th>
<th>数量</th>
<th>单价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(item, index) in data">
<td align="center">{{ item.name }}</td>
<td align="center">
<button @click="addAndSub(item,false)"> - </button>
{{ item.num }}
<button @click="addAndSub(item,true)"> + </button>
</td>
<td align="center">{{ item.price }}</td>
<td align="center">
<button @click="del(index)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<td></td>
<td></td>
<td></td>
<td align="center">总价: {{$total}}¥</td>
</tfoot>
</table>
</div>
</template>
watch侦听器,可侦听数据变化,以及侦听多个数据源
<script setup lang="ts">
import { ref, watch } from 'vue'
let msg1 = ref<string>('')
let msg2 = ref<string>('')
/*
watch:侦听数据变化
参数1:侦听数据源
参数2:回调callback简称cb
cb回调:
参数1:新值
参数2:旧值
*/
watch([msg1,msg2],(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
})
</script>
<template>
<input type="text" v-model="msg1">
<input type="text" v-model="msg2">
</template>
如果需要监听深层对象,则需要开启第三个参数 deep:true
<script setup lang="ts">
import { ref, watch } from 'vue'
let msg = ref({
nav:{
bar:{
name:"东南"
}
}
})
watch(msg,(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
},{deep:true})
</script>
<template>
<input type="text" v-model="msg.nav.bar.name">
</template>
bug:监听到的新值和旧值一样
页面刷新,侦听默认不会执行,只有当变化的时候才会去侦听
但我们可以让页面刷新的时候立马去监听,配置immediate:true属性,立即触发侦听器
<script setup lang="ts">
import { ref, watch } from 'vue'
let msg = ref({
nav:{
bar:{
name:"东南"
}
}
})
watch(msg,(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
},{
deep:true,
immediate:true
})
</script>
- 对于reacative对象使用侦听器,深层对象可以不用配置 deep:true, immediate:true
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
let msg = reactive({
nav:{
bar:{
name:"东南"
}
}
})
watch(msg,(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
})
</script>
<template>
<input type="text" v-model="msg.nav.bar.name">
</template>
监听reactive对象的多个属性
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
let msg = reactive({
name:"东南",
name2:"南北"
})
watch(msg,(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
})
</script>
<template>
<input type="text" v-model="msg.name">
<input type="text" v-model="msg.name2">
</template>
单一监听reactive对象的某个属性,采用函数返回的形式
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
let msg = reactive({
name:"东南",
name2:"南北"
})
watch(() => msg.name,(newVal, oldVal) => {
console.log('新值',newVal);
console.log('旧值',oldVal);
})
</script>
<template>
<input type="text" v-model="msg.name">
<input type="text" v-model="msg.name2">
</template>
认识watchEffect高级监听器
watch是只有数据发生改变的时候才去监听,而且需要立即触发监听的话,需要额外配置immediate,watchEffect则不需要配置immediate则可实现立即监听的效果。
对于watch监听多个属性时,则需要作为参数传进去,watchEffect是高效的,不需要传参。
<script setup lang="ts">
import { watchEffect, ref } from 'vue'
let msg1 = ref<string>('桌子')
let msg2 = ref<string>('板凳')
watchEffect(() => {
console.log('msg1=====>',msg1.value);
console.log('msg2=====>',msg2.value);
})
</script>
<template>
<input type="text" v-model="msg1">
<input type="text" v-model="msg2">
</template>
- watchEffect可接收一个callback作为回调,它可以在侦听之前操作一些东西(比如在侦听之前做一些防抖和节流的操作)
<script setup lang="ts">
import { watchEffect, ref } from 'vue'
let msg1 = ref<string>('桌子')
let msg2 = ref<string>('板凳')
watchEffect((oninvalidate) => {
console.log('msg1=====>',msg1.value);
console.log('msg2=====>',msg2.value);
oninvalidate(() => {
console.log('before');
})
})
</script>
<template>
<input type="text" v-model="msg1">
<input type="text" v-model="msg2">
</template>
- 停止监听,watchEffect可实现停止监听,当停止监听后,数据发生改变,将不会对其进行监听
<script setup lang="ts">
import { watchEffect, ref } from 'vue'
let msg1 = ref<string>('桌子')
let msg2 = ref<string>('板凳')
const stop = watchEffect((oninvalidate) => {
console.log('msg1=====>',msg1.value);
console.log('msg2=====>',msg2.value);
oninvalidate(() => {
console.log('before');
})
})
const stopWatchEffect = () => stop()
</script>
<template>
<input type="text" v-model="msg1">
<input type="text" v-model="msg2">
<button @click="stopWatchEffect">停止监听</button>
</template>
组件使用
对比vue2组件的使用,在vue3中,组件的使用省略了在options中配置components该选项
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld></HelloWorld>
</template>
组件的生命周期
- setup最新被执行
- onBeforeMount 在该生命周期内,不能获取dom相关操作
- onMounted 在该生命周期内,组件已经创建完成,可进行获取dom操作
- onBeforeUpdate 和 onUpdated 数据更新前后使用
- onBeforeUnmount 和 onUnmounted 是组件卸载前后调用
Com.vue子组件
<script setup lang="ts">
import { ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue';
const count = ref(0)
console.log('setup');
onBeforeMount(() => { //获取不到dom
let div = document.querySelector('#msg')
console.log('创建之前===> onBeforeMount',div);
})
onMounted(() => { //可进行dom获取
let div = document.querySelector('#msg')
console.log('创建完成===> onBeforeMount',div);
})
onBeforeUpdate(() => { //数据更新之前
let div = document.querySelector('#msg')
console.log('数据更新之前===> onBeforeUpdate');
})
onUpdated(() => { //数据更新完成
console.log('数据更新完成===> onUpdated');
})
onBeforeUnmount(() => { //组件卸载之前
console.log('组件卸载之前===> onBeforeUnmount');
})
onUnmounted(() => { //数据更新完成
console.log('组件卸载完成===> onUnmounted');
})
</script>
<template>
<div id="msg">Hello!</div>
<div>{{count}}</div>
<button @click="count++">改变count</button>
</template>
app.vue根组件
<script setup lang="ts">
import { ref } from 'vue';
import Com from './components/Com.vue'
let flag = ref(true)
</script>
<template>
<Com v-if="flag"></Com>
<button @click="flag = !flag">改变组件的状态</button>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
scoped组件间的样式隔离
-
实现Layout布局的拼装
-
目录层次规范
App.vue
<script setup lang="ts">
import Layout from './views/Layout/index.vue'
</script>
<template>
<Layout></Layout>
</template>
<style lang="less">
html,body,#app {
height: 100%;
overflow: hidden;
}
*{
margin:0;
padding: 0;
}
</style>
/views/Layout/index.vue
<script setup lang="ts">
import Menu from '../Layout/Menu/index.vue'
import Header from '../Layout/Header/index.vue'
import Content from '../Layout/Content/index.vue'
</script>
<template>
<div id="layout">
<Menu></Menu>
<div id="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<style lang="less" scoped>
#layout{
display: flex;
height: 100%;
overflow: hidden;
&-right{
flex: 1;
display: flex;
flex-direction: column;
}
}
</style>
/views/Layout/Content/index.vue
<script setup lang="ts">
</script>
<template>
<div id="content">
<div id="content-item" :key="item" v-for="item in 100">
{{item}}
</div>
</div>
</template>
<style lang="less" scoped>
#content{
flex: 1;
margin: 20px;
border: 1px solid #ccc;
overflow: auto;
&-item{
padding:20px;
border:1px solid #ccc;
}
}
</style>
/views/Layout/Menu/index.vue
<script setup lang="ts">
</script>
<template>
<div id="menu">菜单区域</div>
</template>
<style lang="less" scoped>
#menu{
width:200px;
border-right: 1px solid #ccc;
}
</style>
<script setup lang="ts">
</script>
<template>
<div id="menu">菜单区域</div>
</template>
<style lang="less" scoped>
#menu{
width:200px;
border-right: 1px solid #ccc;
}
</style>
/views/Layout/Header/index.vue
<script setup lang="ts">
</script>
<template>
<div id="header">头部区域</div>
</template>
<style lang="less" scoped>
#header{
height: 60px;
border-bottom: 1px solid #ccc;
}
</style>
页面展示
父子组件传参
-
子接用defineProps ,子发用defineEmits
-
子组件直接使用defineProps()接收,不需引入,前提是搭配setup和TS语法糖使用
<script setup lang="ts">
</script>
父组件传字符串,非复杂数据类型不需要v-bind指令进行绑定
子组件接收
父组件传数组(复杂数据类型)
<script setup lang="ts">
import Menu from '../Layout/Menu/index.vue'
import Header from '../Layout/Header/index.vue'
import Content from '../Layout/Content/index.vue'
import { reactive } from 'vue';
const list = reactive<number[]>([1,2,3])
</script>
<template>
<div id="layout">
<Menu :data="list" title="春日宴"></Menu>
<div id="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
子组件接收
<script setup lang="ts">
type Props = {
title:string,
data:number[]
}
defineProps<Props>();
</script>
<template>
<div id="menu">
菜单区域
<div>{{title}}</div>
<div>{{data}}</div>
</div>
</template>
效果:
子组件给父组件传值:defineEmits
子组件派发
<script setup lang="ts">
import { reactive } from "vue";
type Props = {
title:string,
data:number[]
}
defineProps<Props>();
//子组件派发
const numList = reactive<number[]>([6,7,8])
const emit = defineEmits(['on-click'])
const Tap = () => {
emit('on-click',numList)
}
</script>
<template>
<div id="menu">
菜单区域
<div>{{title}}</div>
<div>{{data}}</div>
<div><button @click="Tap">发送</button></div>
</div>
</template>
父组件
<script setup lang="ts">
import Menu from '../Layout/Menu/index.vue'
import Header from '../Layout/Header/index.vue'
import Content from '../Layout/Content/index.vue'
import { reactive } from 'vue';
const list = reactive<number[]>([1,2,3])
//父子接收
const getList = (numList:number[]) => {
console.log(numList,'来自子组件的numList');
}
</script>
<template>
<div id="layout">
<Menu @on-click="getList" :data="list" title="春日宴"></Menu>
<div id="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
子组件派发的时候可以携带多个值给父组件
父组件接收
子组件定义多个派发事件
父子组件获取实例,子组件需要defineExpose暴露给父组件
在vue3中父组件通过ref获取子组件的实例是无法获取的
子组件必须同通过defineExpose暴露给父组件
父子组件通讯,子组件设置默认值 withDefaults
父组件
子组件设置默认值
<script setup lang="ts">
type Props = {
title?:string,
data?:number[]
}
withDefaults(defineProps<Props>(),{
title: '标题', //父组件传过来就用父组件的值,没有传过来就用默认值
data:() => [1,2,3]
})
</script>
<template>
<div id="menu">
菜单区域
<div>{{title}}</div>
<div>{{data}}</div>
</div>
</template>