vue3 学习记录

### ref 和 reactive 的区别

        ref:用来定义 : 基本类型数据 、 对象类型数据

        reactive 用来定义 : 对象类型数据

    区别:

        1、ref 创建的变量必须使用.value (可以使用volar插件自动添加.value)

        2、reactive 重新分配一个新的对象 , 会失去响应式 (可以使用Object.assign去整体替换)

    使用原则

        1、如果需要一个基本类型的响应式数据,必须使用ref

        2、如果需要一个响应式对象,层级不深 ref reactive都可以

        3、如果需要一个响应式对象,并且层次不深,推荐使用reactive

## 接口回调更新reactive

```

ler car = reactive({name:'奔驰',price:100})

function handlerChangeCar(){

    // Object.assign(obj1,obj2,obj3) 把obj2 和 obj3 里面的东西都加到obj1里面去 这么写 页面可以更新

    Object.assign(car,{name:'奥迪',price:1})

}

```

reactive 具有局限性 -> reactive定义的对象不能整体修改

```

<script>

    import { reactive } from 'vue'

    let person = reactive({

        name:'zhangsan',

        age:18

    })

    // 修改name

    function handlerChangeName(){

        person.name = 'lisi'

    }

    // 修改age

    function handlerChangeAge(){

        person.age = 20

    }

    function handlerChangeAll(){

        <!-- 整体修改 是不可以的 不能整体修改-->

        person = {name:'lisia',age:92}

        <!-- 但是可以用Object.assign() 整体修改reactive定义的对象 但是对象的地址值没有发生改变 只是字面量覆盖 -->

        Object.assign(person,{name:'lisia',age:92})

    }

</script>

```

如果是用ref的话 直接改就可以了

```

let car = ref({name:'奔驰',price:12})

car.value = {name:'奥迪',price:10}

```

### toRefs 用于解构赋值 把reactive相应是对象变成ref响应式对象

```

<script setuo lang='ts'>

    import { reactive , toRefs } from 'vue'

    let person = reactive({

        name:'张三',

        age:'19'

    })

    // 解构赋值

    let { name , age } = toRefs(person) // toRefs 将reactive对象结构拿出来变成 ref的相应是对象 toRef只能解构一个对象

    function handlerChangeName(){

        name.value = '李四'

    }

    function handlerChangeAge(){

        age.value = 112

    }

</script>

```


 

从响应式对象的身上直接解构属性 获得的属性必定失去响应式 所以就需要toRefs()

<!-- 获取路由的query参数 需要引入 useRoute 从 vue-router里面 -->

```

    <script lang='ts' setup>

        import { useRoute } from "vue-router"

        let route = useRoute()

        let { query } = toRefs(route)

        <!-- route是一个响应式对象  直接解构相应是对象的话数据会丢失响应式 所以需要用toRefs()包裹 -->

    </script>

```

## 计算属性 computed

```

<script setup lang='ts'>

    import { ref , computed } from "vue"

   

    let firstName = 'zhang'

    let lastName = 'san'

    // 计算属性 只读 不可修改 需要写成箭头函数的形式

    let fullName = computed(()=>{

        return firstName + lastName

    })

    //可以修改

    let fullName = computed({

        get(){

            return firstName + lastName

        },

        set(val){

            let [ str1 , str2 ] = val

            firstName.value = str1

            lastName.value = str1

        }

    })

    <!-- btn点击修改名字 -->

    function handlerChangeName(){

        fullName.value = 'li-si'

    }

    // 点击按钮把值传给 fullName 然后计算属性的set会接受 并且把值赋值给定义的响应式数据 实现数据修改

</script>

```

## watch

作用:监视数据的变化(和vue2中的watch作用是一样的)

特点:Vue3中的watch只能监视以下四种数据:

    1、ref定义的数据

    2、reactive定义的数据

    3、函数返回一个值(getter函数)

    4、一个包含上述内容的数组

### 监视情况1:监视ref定义的基本类型数据

```

<script>

    import { ref , watch } from 'vue'

    let sum = ref(0)

    function handlerChangeSum(){

        sum.value ++

    }

    <!-- 监视 watch(谁? , 回到函数()) -->

    watch(sum,(newValue,oldValue)=>{

        console.log('sum的值发生了变化',newValue,oldValue)

    })

    // 结束监视 自己回调自己 结束监视

    let stopWatch = watch(sum,(newValue,oldValue)=>{

        console.log('newValue',newValue);

        if(newValue > 10){

            stopWatch()

        }

    })

</script>

```

### 监视情况2 监视ref定义的[对象类型]数据:直接写数据名,坚实的是对象的[地址值],如果想要监视对象内部的数据,需要手动开启深度监视

```

<script>

    import { ref , watch } from "vue"

    let person = ref({

        name:'张三',

        age:18

    })

    function handlerChangeNmae(){

        person.value.name += '~~'

    }

    function handlerChangeAge(){

        person.value.age += 1

    }

    // 监视person对象里面的属性值 需要开启深度监视

    // 如果不开启深度监视 则监视不到对象里属性的变化 除非修改了整个ref对象

    watch(person,(newValue,oldValue)=>{

        console.log("@@@newValue",newValue)

        console.log("@@@oldValue",oldValue)

    },{deep:true})

   

</script>

```

注意:

    * 如果修改的是ref定义的对象中的属性,newValue和oldValue监测到的都是新值,因为他们都是同一个对象

    * 如果修改的是整个ref定义的对象,newValue是新值,oldValue是旧值,因为不是同一个对象了

### 监视3 reactive 默认开启深度监视 监视[对象类型]的数据

```

<template>

    <buttom @click='handlerChangeBtn'>点击修改</buttom>

</template>

<script>

    import { reactive,watch } from 'vue'

    let person = reactive({

        a:{

            b:{

                c:600

            }

        }

    })

    function handlerChangeBtn(){

        person.a.b.c = 800

    }

    watch(person,(newValue)=>{

        console.log(newValue)

    })

</script>

```

### 情况四: 监视ref或者是reactive定义的[对象类型]数据中的某个属性 -> 注意以下几点

监视 ref或者是reactive里面的某一个值

* 如果该属性不是[对象类型]的数据,需要写成函数形式

* 如果该属性依然是[对象类型],可直接写,也可以写成函数,不过建议写成函数

![Alt text](image.png)

需要监听一个getter函数

![Alt text](image-1.png)

```

<script>

    import { reactive,watch } import 'vue'

    let person = reactive({

        name:'zhangsan',

        age:18,

        car:{

            car1:'宝马',

            car2:'奔驰'

        }

    })

    <!-- 监视响应式对象中的某个属性 ,并且属性是基本类型的,要写成函数形式 -->

    <!-- 监视相应是对象当中的某个值 -->

    watch(()=>{return person.name},(newValue,oldValue)=>{

        console.log

    })

    <!-- 简写 不用写 return 和 { } 这个时候就只会监视person里面的name属性-->

    watcch(()=> person.name ,(newValue,oldValue)=>{

        console.log(newValue)

    })

    <!-- 此时person.name属性不是对象属性 是一个字符串 所以需要写成函数形式 ()=>{} -->

</script>

```

```

<script>

    let person = reactive({

        name:'zhangsan',

        age:19,

        car:{

            car1:'奔驰',

            car2:'奥迪'

        }

    })

    function changeC1(){

        person.car.car1 = '大众'

    }

    function changeC2(){

        person.car.car1 = '雅迪'

    }

    function changeCar(){

        person.car = { car1:'1122' , car2:'3332' }

    }

    watch(person.car,(newValue,oldValue)=>{

        console.log("@@@newValue",newValue)

    })

    <!-- 监视属性值car  应为car是一个对象类型 所以监视car的时候可以不用写成函数的形式 ,但是这种情况只能监视car1和car2的变化 不能检测到整体的变化 因为car1 和 car2 变化的时候 person.car的地址值没有发生改变 所以能检测到 但是如果是整体发生了改变 即对象的地址值发生了改变 person.car就监测不到了 所以建议写成函数形式 () => person.car -->

    <!-- 写成函数 watch监视的就是地址值 能检测整体的变化 但是修改car1和car2监测不到了 所以要写成以下形式-->

    watch(() => person.car , (newValue) => {

        console.log('@@@res',res)

    },{deep:true})

    <!-- 写成函数式能检测整体的变化 写深度监视 也能检测到car1和car2某个属性的改变 -->

</script>

```

### 监视5:监视多个数据

<!-- 用数组 , 并且开启深度监视-->

```

    let person = {

        name:'zhangsan',

        age:19,

        car:{

            car1:'奔驰',

            car2:'宝马'

        }

    }

    watch([()=>person.name,()=>person.car.car1],(newValue)=>{

        console.log('newValue')

    },{deep:true})

```

## watchEffect() 自动监视所有属性

watchEffect是一个立即执行函数,并且是一个响应式的追踪依赖,并在依赖更改时重新执行函数

* watch和watchEffect对比

    1.都能监视响应式数据的变化,不同的是监听数据变化的方式不同

    2.watch需要指明索要监视的对象

    3.watchEffect不用指明需要监视的数据(函数中用到哪些属性,就监视哪些属性)

* 代码示例

```

<template>

    <!-- 温度超过60度 或者是水位高度超过80就请求接口 -->

    <h1>当前温度{{ temp }}</h1>

    <h1>当前水位高度{{ height }}</h1>

    <button @click='handlerAddTemp'>点击温度+10</button>

    <button @click='handlerAddHeight'>点击水位+10</button>

</template>

<script>

    import { ref , watchEffect } from 'vue'

    let temp = ref(10)

    let height = ref(0)

    function handlerAddTemp(){

        temp.value += 10

    }

    function handlerAddHeight(){

        height.value += 10

    }

    <!--  通过watch监视 temp 和 height 的变化 监听多个参数需要数组-->

    watch([temp,height],(newValue)=>{

        console.log("newValue",newValue)

    })

    <!-- 用watchEffect 不需要写参数 自动监听变化的属性-->

    watchEffect(()=>{

        if(temp.value > 60 || height.value >80){

            console.log('@@@)

        }

    })

</script>

```

## ref 写在标签属性里面 用于存储ref标记的内容

```

<template>

    <div ref='title1'>中国</div>

    <button @click='handlerClickRef'>点我</button>

</template>

<script>

    import { ref } from "vue"

    let title1 = ref()

    function handlerClickRef(){

        console.log(title1.value)

    }

</script>

```

如果ref 放在组件标签上 打印出来的东西是组件的实例化对象

person.vue -> ```

    <template>

       

    </template>

    <script setup>

        import { ref , defineExport } from "vue"

        let a = ref(1)

        let b = ref(2)

        let c = ref(3)

        defineExport({ a , b , c })

    <script>

```

App.vue -> ```

    <template>

        <person ref='title1'/>

        <botton @click='clickRef'>点我</botton>

    </template>

    <script setup>

        import { ref } from "vue"

        let title1 = ref()

        function clickRef(){

            console.log(title1.value)

        }

    </script>

```

按理来说 能打印出以下结果

![Alt text](image-2.png)

2024/1/19 控制台提示信息

defineExpose() is a compiler-hint helper that is only usable inside <script setup> of a single file component. Its arguments should be compiled away and passing it at runtime has no effect.

## 泛型

    泛型 用于规范js的数据类型

创建 types->index.ts 用于存储变量的类型

```

<!-- interface // 定义一个接口,用于限制person对象的具体属性 定义完之后把他暴漏出去 -->

export interface Persons {

    id:string,

    name:string,

    age:number

}

<!-- 规范数组的时候也可以用以下两种写法 -->

export type personList = Array<Persons>

export type personList2 = Persons[]

```

person.vue

```

<script lang='ts' setup>

    <!-- 需要写type -->

    import { type Persons , type personList } from "@/types"

   

    <!-- 限制创建变量的类型 -->

    let person:Person = { id:'asddsww',name:'zhangsan',age:18 }

    <!-- 如果要规范一个数组 -->

    <!-- 下面的意思是 创建一个personList数组并且数组当中的每一个值都符合Person规范 -->

    let personList:Array<Person> = [

        { id:'asddsww',name:'zhangsan',age:18 },

        { id:'ffsxxww',name:'lisi',age:32 },

        { id:'ttgscc',name:'wangwu',age:8 }

    ]

     let personList2:personList = [

        { id:'asddsww',name:'zhangsan',age:18 },

        { id:'ffsxxww',name:'lisi',age:32 },

        { id:'ttgscc',name:'wangwu',age:8 }

    ]

   

</script>

```


 

## Props父子传值

App.vue ->

```

<template>

    <Person a='哈哈' :list='personList' />

</template>

<script lang='ts' setup>

    let personList = [

        {id:'xxsas',name:'zhangsan',age:39},

        {id:'vvaec',name:'lisi',age:2},

        {id:'44gsc',name:'wangwu',age:12},

    ]

</script>

```

传递到子组件 person.vue ->

```

<template>

    <div>{{ a }}</div>

    <div>

        <ul>

            <li v-for='item in list'>{{ item.name }} -- {{ item.age }}</li>

        </ul>

    </div>

</template>

<script lang='ts' setup>

    import { type Persons } from "@/types"

    import { withDefault } from "vue"

   

    <!-- 第一种接收方式 只是接收 不能打印 -->

    defineProps(['a','list'])

    <!-- 第二种接收方式 defineProps有返回值 用变量接收可以打印 -->

    let x = defineProps(['a'])

    console.log('@@@@x',x)

    <!-- 第三种接收方式 接收并且限制类型 -->

    defineProps<{list:Persons}>()

    defineProps<{list?:Persons}>() // 父组件可以传递也可以不传递

    <!-- 接收list + 限制类型 + 限制必要性 + 指定默认值(默认值用withDefaults) -->

    <!-- 指定默认值用withDefault(xxx,{默认值}) -->

    withDefault(defineProps<{list?:Persons}>(),{

        list:()=>[{id:'scjjsa',name:'zhangskkkd',age:83}]

    })

    {

        list:[{name:'zhangsan',age:32}] // 这种写法不行 需要写成函数形式 返回一个函数

    }

    // vue3中 defineXXX 是宏函数 不需要引入 直接用就行

</script>

```

## Vue2生命周期

    创建(创建前 beforeCreate 创建完毕created)

   

    挂载(挂载前 beforeMount 挂载完毕mounted) 把组件放到页面里面

   

    更新(更新前 更新完毕)

   

    销毁(销毁前 销毁完毕)

    不包括路由的生命周期

## vue3生命周期

    vue3生命周期中没有创建的钩子 setup替代了vue2的创建生命周期 不再区分创建前还是创建后

   

    创建 setup

    生命周期使用时需要引入

    vue3生命周期相比于vue2来说前面需要加-on 并且调用的是生命周期指定的回调函数

    挂载前vue3给你调用onBeforeMount指定的那个回调函数

    onBeforeMount(()=>{

       

    })

    生命周期都需要传入指定的回调函数

    销毁不用destory 用 onUnmounted 叫卸载

    <!-- 父子组件生命周期挂载的顺序是子组件先挂载 父组件再挂载 符合深度优先遍历 App.vue是最后挂载完的 -->

## 用 await 请求接口时候如何处理异常 用try catch

    v3示例 ->

        ```

        async function handlerTestRequest(){

            try{

                let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')

                console.log('@@@@',result)

            }catch(error){

                alert(error)

            }

        }

        <!-- 一般异常处理都是写在拦截器里面 -->

        ```

## 自定义hooks 让一个功能的数据和方法都放在同一个容器里面 类似于v2的mixin 实现组合式api 封装 + 引用(解构赋值)

引入的是一个函数

    需要创建一个hooks文件夹 如果是跟学生相关的文件就写 usePerson.js/.ts 如果是跟狗相关的就写useDog.js/.ts

    ---hooks

        ----useDog.ts

        ----useSum.ts

    ---components

        ----person.vue

    ---App.vue

useDog.ts->

```

    import { reactive } from 'vue'

    import axios from 'axios'

   

    export default function(){  // 写成一个函数 然后抛出 不需要写函数名

        let dogList = reactive([])

        async function addDogPage(){

            try{

                let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')

                dogList.push(result.data.message)

            }catch(err){

                alert(err)

            }

        }

        return { dogList , addDogPage } // 需要有返回值

    }

```

person.vue ->

```

<template>

    <button @click='addDogPage'>点击增加图片</button>

    <div>

        <img v-for= "(item,index) in dogList" :src='item'></img>

    </div>

</template>

<script lang='ts' setup>

    import useDog from "@/hooks/useDog"

    let { dogList,addDogPage } = useDog()

</script>

```

## vue3路由

引入路由 npm install vue-router

router->index.ts

```

    <!-- 第一步 引入createRouter -->

    import { createRouter } from "vue-router"

    <!-- 引入要呈现的组件 -->

    import Home from "@/components/Home.vue"

    <!-- 第二步 创建路由 -->

    const router = createRouter({  // 这样创建路由会报错 vue3需要规定路由的工作模式 通过引入createWebHistory/createWebHashHistory 分别对应history和hash两种工作模式

        routes:[

            {

                path:'/Home',

                components:'Home'

            }

        ]

    })

    <!-- 正确写法 -->

    import { createRouter , createWebHistory,createWebHashHistory } from "vue-router"

    const router = createRouter({  // 引入路由工作模式

        history:createWebHistory()

        routes:[ // 一个一个路由规则

            {

                path:'/Home',

                component:'Home'   // 这个地方是component 没有s  不要加s

            }

        ]

    })

    export default router // 暴露出去

```

main.js中需要引入

```

    import router = from "./router"

   

    <!-- 挂载到app当中 -->

    const app = createApp(App)

    app.use(router)

    app.mount('#app')

```

用的时候需要引入 RouterView 和 RouterLine  // 现在不需要引入了 直接写RouterView 和 RouterLink就行了

App.vue ->

```

    <template>

        <!-- 跳转区 -->

        <RouterLink to='/home'>我的</RouterLink>

        <RouterLink to='/news'>新闻</RouterLink>

        <RouterLink to='/xxx'>xxx</RouterLink>

        <!-- 展示区 -->

        <RouterView></RouterView>

    </template>

    <script lang='ts' setup>

        import { RouterLink,RouterView } from 'vue-router'

    </script>

```

## 两种路由模式 hash(默认)和history模式

    history模式

        优点:url更加美观 不带# 更接近传统的网站url

        缺点:后期项目上线,需要服务端配合处理路径问题,否则会有404报错

   

    hash模式(默认形式)

        优点:兼容性好,不需要服务端处理路径

        缺点:URL带有 # 不太美观,并且再SEO优化方面相对较差

    ** Vue2处理路由两种模式 **

    ```

    export default new  VueRouter({

        mode:'history' / 'hash'(默认不用写)

        routers:[

            {

                xxz

            }

        ]

    })

    ```

    ** Vue3处理路由两种模式 **

    ```

    import { createRouter,createWebHistory,createWebHashHistory }

    const router = createRouter({

        history:createWebHistory() / createWebHashHistory()

        routes:[

            {

                xxx

            }

        ]

    })

    ```

    ** vue3 必须要配置路由模式 vue2不需要(默认是hash) **

    一般后台用hash模式 不需要考虑美观 前台项目例如商城等项目用history模式比较多

### router注意点

    ** 路由组件通常放在pages或者是views文件夹下 一般组件通常放在components文件夹 **

    ** 通过点击导航,视觉效果上消失了路由组件 默认是被销毁掉了 需要的时候再去挂载 **

    但是vue2中可以用 keep-alive 缓存路由组件 保持挂载不被销毁 vue3暂时不知道

## routet的query传参以及嵌套路由

Vue3需要用到路由的话 需要引入useRoute 跟vue2直接打印this.$route是有区别的

Vue3中 import { uesRoute } from "vue-router"

useRoute是一个hooks函数

detail.vue ->

```

    <template>

        <ul>

            <li>编号:{{ query.id }}</li>

            <li>标题:{{ query.title }}</li>

            <li>内容:{{ query.content }}</li>

        </ul>

    </template>

    <script>

        import { toRefs } from "vue"

        import { useRoute } from "vue-router"

        let { useRoute } from "vue-router"

        let route = useRoute()

        let { query } = toRefs(route)

    </script>

```

news.vue ->

```

<!--

 * @FilePath: News.vue

 * @Author: 是十九呐

 * @Date: 2024-01-25 16:20:31

 * @LastEditTime: 2024-01-29 17:07:43

-->

    <template>

        <div class="container">

            新闻

            <div class="content-box">

            <div>

                <ul>

                <li v-for="item of newList" :key="item.id">

                    <RouterLink :to="{path:'/news/detail',query:{

                    id:item.id,

                    title:item.title,

                    content:item.content

                    }}"> {{ item.title }} </RouterLink>

                </li>

                </ul>

            </div>

            <div>

                <RouterView />

            </div>

            </div>

        </div>

    </template>

    <script lang="ts" setup>

        import { reactive } from "vue";

        let newList = reactive([

        { id: 'asdcc22', content: '新闻内容1112111', title: '标题1' },

        { id: '34sffe', content: '测试呐aaa', title: '标题2' },

        { id: 'kkxmj2', content: '69', title: '69' },

        ])

    </script>

    <style>

        .content-box{

            display: flex;

        }

    </style>

```

* !!!Vue3接收route里面的参数时候 必须要 import { useRoute } from 'vue-router' *

## query参数和params参数的区别 + params参数传值

    1、query参数在拼接字符串传递时 可以->  to='/news/detail?id=xxsff2&title=测试标题&content=测试内容'

    1、params参数在拼接字符串传递时 不需要key-value的形式  to='/news/detail/cajjw23/测试标题/测试内容' 但是需要在配置路由的时候占位

    count router = createRouter({

        routes:[

            {

                path:'/home',

                component:() => import('@/components/Home.vue') // 路由懒加载

            },{

                path:'/news',

                component:() => import('@/components/News.vue') // 路由懒加载

                children:[

                    {

                        path:'detail/:id/:title/:content', // params参数传递时需要占位 !!!!

                        component: () => import('@/component/detail.vue')

                    }

                ]

            },{

                path:'/about',

                component:() => import('@/components/About.vue') // 路由懒加载

            }

        ]

    })

* params两个注意点 *

1、params传参时在router.js的路径上需要占位

2、params参数在对象传值中不能用path,只能用name

```

    <RouterLink :to='{ name:'xiang',params:{id:'2332ddds',title:'测试标题',content:'测试内容'} }'></RouterLink>

    <!-- 这个地方只能用name 不能用 path 否则就会报警告 忽视命名式路由 -->

```

![Alt text](image-4.png)

![Alt text](image-5.png)

![Alt text](image-6.png)

用params传值方式如果遇到可传可不传的参数在后面写一个?即可

    children:[

        {

            path:'detail/:id/:title/:content?',

            component:() => import('@/components/detail.vue')

        }

    ]

![Alt text](image-7.png)

## 路由规则的props配置 不建议用 因为代码不好追溯

![Alt text](image-8.png)

在router.js里面配置props

    children:[

        {

            path:'detail/:id/:title?/:content?',

            component:()=>import('xxx')

            <!-- 第一种写法 将路由收到的所有params参数作为props传递给路由组件 -->

            props:true // 👈 只能用于params传值

        },

        {

            name:'xiang',

            component:()=>import('xxx')

            <!-- 第二种写法 可以自己决定将什么作为props给路由组件 需要写成函数的形式并且有返回值,query和Params传值方式都可以用 -->

            props(route){

                return route.query/params // 👈 params和query传值方式都可以用

            }

        }

    ]

    接收参数时 都是下面的方式用defineProps 👇👇👇

    在相应的组件中用defineProps接收就跟Vue3的父子传值一样

    defineProps(['id','title','content'])

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值