vue3

Vite+TS+Vue3.0 笔记一

2022-05-06  本文已影响0人  似朝朝我心

ref数据响应式和Ref对象

<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响应式对象

<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是引用,修改响应式数据会影响以前的数据,数据发生变化,界面就会自动更新.

<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>

<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>

<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>
<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>

组件的生命周期

<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组件间的样式隔离

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>

页面展示


父子组件传参

<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>
上一篇下一篇

猜你喜欢

热点阅读