vue3学习笔记

原文:尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程_哔哩哔哩_bilibili

1.环境安装

nodejs 验证命令node,有node 才有npm这个命令

npm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具。通过 npm 可以安装、共享、分发代码,管理项目依赖关系。

npm 由三个独立的部分组成:

>npm create vue@latest

只是使用typescript

通过vscode打开目录

注意起重一定要有package.json文件,如果没有就另建项目重新npm create vue@latest

工程介绍

1).vscode-->extensions.json

.vscode存放从商店中拿到的插件

比如

Vue - Official(代替了volar)

2)env.d.ts

从node modules引入

如果没有则飘红

npm i

报错npm WARN saveError ENOENT: no such file or directory, open 'D:\src\ebook\package.json'

原因是没有package.json,需要重新运行项目或者考过来pacakge.json

之后会出现node_modules文件夹

vite网址Vite中文网

3)index.html

入口文件

入口文件不是main.js也不是main.ts

vite.config.ts:工程配置文件

3)main.ts

import './assets/main.css' //引入css文件

import { createApp } from 'vue' //类似于养花的花盆,创建应用

import App from './App.vue' //类似于养花的花根

createApp(App).mount('#app'):

createApp(App).把花插在花盆里 创建应用,每个应用都有个根组件

mount: 成果摆在#app里面

4)删掉src

vue文件三个标签

template script style

推荐使用

<script lang="ts">

</script>

2.setup

新配置项,里面不能用this,this是undefined,vue3弱化this

setup比beforecreate还在执行前面

返回对象

复制代码
setup(){
        // console.log("@@@@@@@@@@@@@@"+this)   this是undefined
        // 数据,直接用let不是响应式的,也就是变化不会引发页面自动更新
        let name="杜甫";
        let age=21;
        let tel='123455'
        // 方法
        function changeName(){
            console.log(1)
           name='李白'
           console.log(name)
        }

        function changeAge(){
            console.log(2)
            age+=1;
            console.log(age)
        }
         
        function showTel(){
            console.log(3)
              alert(tel)
        }
     return{
        name,age,changeAge,changeName,showTel
 
     }
    }

上述返回的是对象

返回函数

复制代码
  return function(){
        return 'haha'
    }

页面上直接出现

因为绝不会有this,改造成箭头函数

复制代码
  return ()=>{
      return 'haha2'
    }

箭头函数在语法上比普通函数简洁多。箭头函数就是采用箭头=>来定义函数,省去关键字function。

函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中

①如果箭头函数没有参数,写空括号

②如果箭头函数有一个参数,也可以省去包裹参数的括号

③如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中。

箭头函数的函数体

①如果箭头函数的函数体只有一句执行代码,简单返回某个变量或者返回一个简单的js表达式,可以省去函数体花括号{ }及return

箭头函数没有原型prototype,因此箭头函数没有this指向

箭头函数没有自己的this指向,它会捕获自己定义 所处的外层执行环境,并且继承这个this值。箭头函数的this指向在被定义的时候就确定了,之后永远都不会改变。(!永远)

箭头函数进一步

return ()=>"haha3"

语法糖

可以不写setup 及里面的return

复制代码
<script setup>
let a='666'

</script>

<script lang="ts" setup>
let a='666'

</script>

一般vue3都有两个setup

一个用来定义名字,不然就等同文件名

另外一个写数据和方法

配置插件

为了支持把以上两个setup写在一起:

复制代码
<script lang="ts" setup name="Person2">
let a='666'

</script>

npm i vite-plugin-vue-setup-extend -D

更改vite.config.ts

之后重启就使用了<script lang="ts" setup name="Person2">

从输出来看 右侧数据被单独放在一块,其余放在一块

响应式数据

不同于vue2用data(){return{}} 里面的数据自动是响应式

ref:基本类型数据

引入ref,在需要变成响应式的前面加ref

复制代码
<script lang="ts" setup name="Person">
import {ref} from 'vue'
let name=ref("libai");
let a='666';
console.log(name);
console.log(a);
function change(){

}
</script>

从输出可以看到

打开,带下划线的都是系统自身用的

可见name是一个对象,使用时应该用name.value获取其值,但是

注意如下2个位置不同:template中不用写.value,自动添加

reactive:只能对象类型数据

let car=reactive({brand:'Benz',price:100})

没包裹叫原对象

包裹之后变为响应式对象

ref2:对象类型数据

复制代码
<template>
<div>
{{ car.brand }}{{ car.city }}

<ul>
  <li v-for="item in games" :key="item.id">
  {{item.id  }}  {{item.name  }}
  </li>
</ul>
<button @click="changeBrand">更改品牌</button>
<button @click="changeName">更改游戏编号</button>
</div>
</template>
<script lang='ts' setup name="Person">
import {ref} from 'vue'
let car=ref({brand:'aodi',city:'shanghai'})
let games=ref([
  {id:'a1',name:'王者'},
  {id:'a2',name:'神探'},
  {id:'a3',name:'日落'}])

  function changeBrand(){
    car.value.brand='宝马'
  }


  function changeName(){
    games.value[0].id='a11'

  }


</script>
<style scoped>


</style>

reactive和Ref

RefImpl都是由ref定义得来

Proxy都是由reactive得来

Ref遇到对象时用Reactive

避免ref后面.value每次都要写的设置

左下角齿轮-settings

选中 AutoInsertDotvalue

reactive所指元素被重新分配对象

失去响应式

以下function不能更改页面

let car=reactive({brand:'aodi',city:'shanghai'})

function changeCar(){

car={brand:'保时捷',city:'tianjin'}

}

但是以下是可以的

function changeCar(){

car.brand='a'

car.csity='gz'

}

解决方法1

to refs结构对象

左边相当于右面

修改为 let {name,age}=toRefs(person)

把reactive对象所定义的每一组key value变成ref对象

并且name就是person.name ,改变name同时也改变了person.name

toRef:只改变某个元素

let nl=toRef(person,'age')

console.log("nl"+nl.value)

3.计算属性

<input type="text"

单向属性 :value= 其实是v-bind:value

双向属性 v-model= 其 实是v-model:value

复制代码
<template>
    <div class="person">
      姓:<input type="text" v-model="fName"> <br>
      名:<input type="text" v-model="lName"> <br>
      <button @click="changeFullName">将全名修改为li-four</button>
    全名  <span>{{ fullName }}</span>
    <!-- 全名  <span>{{ fullName2() }}</span>
    全名  <span>{{ fullName2() }}</span> -->
    </div>
</template>
<script lang="ts" setup name="Person">
import {ref,computed} from 'vue'
let fName=ref('zhang')
let lName=ref("big")
// 下面定义的fullName只读,不可修改
// let fullName=computed(()=>{
//     // slice(0,1)截取从0开始到1的1个数据  ,slice(1)  截取从1到最后的所有数据 
//     return (fName.value.slice(0,1).toUpperCase()+fName.value.slice(1)+lName.value)
// })
// 下面定义的fullName  可读写
let fullName=computed(
    {
       get(){ return (fName.value.slice(0,1).toUpperCase()+fName.value.slice(1)+"-"+lName.value)},
       set(val){
        const  [str1,str2]=val.split('-')
        fName.value=str1
        lName.value=str2
       
       } 
    }
)
console.log(fullName)
function fullName2(){
    console.log("function2")
    return (fName.value.slice(0,1).toUpperCase()+fName.value.slice(1)+lName.value)
}


function changeFullName(){
  fullName.value="li-si"
}
</script>
<style scoped>
.person{
    background-color: cadetblue;
}


</style>

计算属性是有缓存的,发现其构成没变,则即使其它地方使用也不会再计算

方法则没有缓存 {{方法名()}}

如上定义计算属性是只读的

要想该则需要设置get set

set(val) 中的val就是fullname="li-si"中被赋予的值

复制代码
let fullName=computed(
    {
       get(){ return (fName.value.slice(0,1).toUpperCase()+fName.value.slice(1)+"-"+lName.value)},
       set(val){
        const  [str1,str2]=val.split('-')
        fName.value=str1
        lName.value=str2
       
       } 
    }
)

4.watch

watch在vue3是一个函数

watch(监视谁,回调函数)

监视基本类型

注意:监视sum不需要写.value

复制代码
let sum=ref(0);
watch(sum,(newVal,oldVal)=>{
console.log("sum变化了"+newVal+"旧值"+oldVal)
})

结束监视

监视函数的返回值就是结束监视函数

复制代码
import {ref,computed,watch} from 'vue'
let sum=ref(0);
function addOne(){
    sum.value++
}
const stopWatch=watch(sum,(newVal,oldVal)=>{
console.log("sum变化了"+newVal+"旧值"+oldVal)
if(newVal>10){
    stopWatch();
}
})
console.log(stopWatch)

stopWatch就是

监视Ref定义的对象类型数据

监视对象地址值

若需监视对象值,需开启深度监视,使用监视函数第三个值

复制代码
watch(person,(newVal,oldVal)=>{
   console.log("Person被修改了"+newVal+"旧值"+oldVal)
},{deep:true})

还可以加个immediate参数,一上来先执行,因为原来肯定是undefined

复制代码
watch(person,(newVal,oldVal)=>{
   console.log("Person被修改了"+newVal+"旧值"+oldVal)
},{deep:true,immediate:true})

另外(newVal,oldVal)如果里面只写一个参数代表的是新值

监视Reatctive定义对象

默认开启深度监视,不能关闭

ref替换对象才是真正的替换,地址也变了,并且一直保持响应式

如果用reactive person={},会失去响应式;object.assign:是修改人的属性而已

监视对象类型的某个属性

对象属性是基本类型

复制代码
<template>
    <div>
    姓名<h2>{{ person.name }}</h2><br>
    年龄<h2>{{ person.age }}</h2><br>
    车<h2>{{ person.car.c1 }},{{person.car.c2  }}</h2><br>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeFirstCar">修改第一台车</button>
    <button @click="changeSecondCar">修改第二台车</button>
    <button @click="changeCar">修改车</button>
    </div>
</template>
<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 changeAge(){
    person.age++
}

function changeFirstCar(){
    person.car.c1="保时捷"
}

function changeSecondCar(){
    person.car.c2="法拉利"
}

function changeCar(){
//    Object.assign(person.car,{c1:'蓝宇',c2:'比亚迪'})
person.car={c1:'蓝宇',c2:'比亚迪'}
}

watch(()=>{
    return person.name
},(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
})

</script>
<style scoped>

</style>

上文中watch第一个值用了一个getter函数,简写为箭头函数,能返回一个值

以实现只监视一个属性

进一步简写为

复制代码
watch(()=>person.name,(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
})

对象属性是对象类型

复制代码
watch(person.car,(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
})

上面的有缺陷:就是整个car变了没有被监视到

建议写全,监视的是地址值,属性变化监视不到

复制代码
watch(()=>person.car,(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
})

需要开启deep

复制代码
watch(()=>person.car,(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
},{deep:true})

监视多种值

复制代码
watch([()=>person.name,()=>person.car.c1],(newVal,oldVal)=>{
   console.log("发生变化了",newVal,oldVal)
},{deep:true})

watch Effect

复制代码
<template>
    <div>
   <h2>当前水温:{{ temp }}C</h2>
   <h2>当前水位:{{ height }}M</h2>
   <button @click="changeTemp">点我水温+1</button>
   <button @click="changeHeight">点我水位+1</button>
    </div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
let temp=ref(20)
let height=ref(3)

function changeTemp(){
    temp.value++
}
function changeHeight(){
    height.value++
}

watch([temp,height],(newVal,oldVal)=>{
    // 从newVal中获取新的温度和水位
    let [newTemp,newHeight]=newVal
    if(newTemp>=30||newHeight>=8){
        console.log("alarm",newVal,oldVal)
    }
})


</script>
<style scoped>

</style>

改用watch effect,有immediate效果

因为被监视值就是定义的属性,

复制代码
watchEffect(()=>{
   if(temp.value>=30||height.value>=10){
    console.log("hello")
   }
})

标签REF

用Ref的原因,是因为如果使用id属性,那么不同vue文件可能重复使用

<h2 id="title2">深圳</h2>

console.log(document.getElementById('title2'))

输出为

对应改为ref2

复制代码
<template>
    <div>
      <h1>中国</h1>
      <!-- 把深圳放在名称为title2的容器里 -->
      <h2 ref="title2">深圳</h2>
      <h3>龙华</h3>
      <button @click="showLog">点我输出h2</button>
    </div>
</template>
<script lang="ts" setup name="Person">
 import{ref} from 'vue'
//  创建一个title2,用于存储ref2标记的内容
 let title2=ref()


function showLog(){
    // console.log(document.getElementById('title2'))
    console.log(title2.value)
}

</script>
<style scoped>

</style>

有的时候会输出,标签中会有 data-v-4cadc14e,这是局部样式导致的,去掉scoped就没有了

复制代码
<template>
    <div class="person">
      <h1>中国</h1>
      <!-- 把深圳放在名称为title2的容器里 -->
      <h2 >深圳</h2>
      <h3 ref="title2">龙华</h3>
      <button @click="showLog">点我输出h2</button>
    </div>
</template>
<script lang="ts" setup name="Person">
 import{ref} from 'vue'
//  创建一个title2,用于存储ref2标记的内容
 let title2=ref()


function showLog(){
    // console.log(document.getElementById('title2'))
    console.log(title2.value)
}

</script>
<style scoped>
.person{
    background-color: cadetblue;
}
</style>

REF在组件

Ref都是加在普通的html标签上而不是组件标签

如果给组件加,比如

复制代码
<template>
    <h2 ref="title2">ok</h2>
    <button @click="outPrint">点我输出</button>
   <Person ref="ren"/>
</template>
<script lang="ts" setup name="App">
import  Person from './components/Person.vue'
import {ref} from 'vue'
let title2=ref()
let ren=ref()

function outPrint(){
    // console.log(title2.value)
    console.log(ren.value)
}

// export default{
//     name:'App',
//     components:{Person}
// }

</script>

输出ref是组件的实例对象,但是什么具体值也看不到,原因是被本vue文件作为父级保护起来,这与Vue2不同,在Vue2中父级是可以看到所有子级的

如果要显示,则需要去组件中添加

import{ref,defineExpose} from 'vue

defineExpose({a,b,c})

复制代码
<template>
    <div class="person">
      <h1>中国</h1>
      <!-- 把深圳放在名称为title2的容器里 -->
      <h2 >深圳</h2>
      <h3 ref="title2">龙华</h3>
      <button @click="showLog">点我输出h2</button>
    </div>
</template>
<script lang="ts" setup name="Person">
 import{ref,defineExpose} from 'vue'
//  创建一个title2,用于存储ref2标记的内容
 let title2=ref()
let a=ref(0)
let b=ref(1)
let c=ref(2)








function showLog(){
    // console.log(document.getElementById('title2'))
    console.log(title2.value)
}

defineExpose({a,b,c})
</script>
<style scoped>
.person{
    background-color: cadetblue;
}
</style>

总结

ref可以定义在html普通标签,拿到的是元素

也可以定义在组件上,拿到的是组件实例,能看到哪些元素取决于组件本身的expose

TS

约束定义

1)在src下新建types文件夹,下面新建index.ts

复制代码
// 定义一个接口对象,用于限制person对象的具体数值
export interface PersonInter{
    id:string,
    name:string,
    age:number
}

// export type Persons=Array<PersonInter>
export type Persons=PersonInter[]

上面定义的是对象

下面定义了数组,引用了对象,有两种定义方法,一种用泛型,另外一种用[]

约束使用

先引入

然后用冒号

复制代码
<template>
    <div class="person">
     ???
    </div>
</template>
<script lang="ts" setup name="Person">
// import的是个约束,不是具体值,所以无法打印
import {type PersonInter,type Persons} from '@/types'
//下文的意思是person要符合接口规范,包括变量名称
let person:PersonInter={id:'001',name:'李白',age:22}
//下文的意思是person数组泛型要符合接口规范,包括变量名称
// let persons:Array<PersonInter>=[
    let persons:Persons=[
{id:'001',name:'李白',age:22},
{id:'002',name:'杜甫',age:32}
{id:'003',name:'白居易',age:36}
]
</script>
<style scoped>
.person{
    background-color: cadetblue;
}

</style>

Props

1. 属性中冒号

<h2 a="1+1" :b="1+1" c="x" :d="x" ></h2> let x=99

有了冒号代表取变量或者本身就是表达式,结果如下

2.给子组件传值

<Person a="haha" b="ss" :list="personList"/>

子组件接收:注意子组件里面的let x把所有的defineProps里面的值都保存了起来

复制代码
<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
// 接收a
// defineProps(['a','b'])
//接收a并且保存起来
let x=defineProps(['a','b','list'])

console.log(x.a)
console.log(x)
</script>

v-for

<Person a="haha" b="ss" :list="personList"/>如果写为 <Person a="haha" b="ss" :list="5"/>

:list="5" 子组件显示会展示5次

v-for的key主要是为了更新,作为索引;如果不指定key,则用数据下标0,1,2等为索引,容易混乱

<li v-for="item in list" :key="item.id"> persons也可以用具体数字

如果v-for对应list为空,则直接不显示

可有可无的属性

参见x后的?

复制代码
export interface PersonInterface{
    id:string,
    name:string,
    age:number,
    x?: number
}

export type Persons=PersonInterface[]

优雅泛型

参加reactive后面的<Persons>取代了PersonList:Persons

复制代码
let PersonList=reactive<Persons> ([
    {id:'001',name:'李白',age:22},
    {id:'002',name:'杜甫',age:23},
    {id:'003',name:'白居易',age:24},
    {id:'004',name:'苏轼',age:25},
])
</script>

接收并限制类型

子组件接收时检查类型

复制代码
<template>
    <div>
       <ul>
        <li v-for="item in list" :key="item.id">
        {{item.id}}---{{item.name}}---{{ item.age }}
        
        </li>
        </ul>

    </div>
</template>
<script lang="ts" setup name="Person">
   import{defineProps,withDefaults} from 'vue'
   import {type Persons} from '../types'
//    定义需要手动父组件传递过来的的属性名称,同时必须类型符合
// defineProps<{list:Persons}>()

// 接收list+限制类型 +限制必要性+默认值
withDefaults(defineProps<{list?:Persons}>(),{
    // 
list:()=>[{id:'008',name:'王麻子',age:99}]
}
)


</script>

<style scoped>

</style>

宏函数

define定义的函数比如defineProps等,vue3默认引入,不会报错

生命周期

也叫生命周期函数,钩子

组件:创建 挂载 更新 销毁

created mounted

vue2声明周期

全局安装

复制代码
npm install -g @vue/cli

查看vue版本 vue -V

创建 vue create vue2test

创建 前 before 后

挂载

更新

销毁

复制代码
<template>
    <div>
        <h2>当前求和为{{sum}}</h2>
        <button @click="addSum">点我sum+1</button>
    </div>
</template>
<script>
   export default{
    /* eslint-disable */ 
    name:'Person',
    data(){
   return{
        sum : 1,
   }
    },

    methods:{
   
        addSum(){
            this.sum++;
        }
    },
//   顺序不重要,何时调vue决定
// 创建:比如人怀孕
   beforeCreate() {
    console.log("创建前");
},
   created(){
    console.log("创建完毕")
  },
  // 挂载:出生
 
beforeMount() {
    console.log("没有挂载")
    // debugger  //停在这里
},
mounted(){
    console.log("挂载完毕")
},
//更新,多次
beforeUpdate(){
    console.log("更新前")
},
updated() {
    console.log("更新完毕")
},
//销毁前
beforeDestroy(){
    console.log("销毁前")
},
destroyed(){
    console.log("销毁完毕")
}


}


</script>

vue3声明周期

1.子先挂载

1.子先挂载,App这个组件最后挂载

2.vue2与vue3生命周期对比

3.vue3

复制代码
<template>
    <div>
         <h2>{{ sum }}</h2>
         <button @click="addSum">点我加1</button>
    </div>
</template>
<script lang="ts" setup  name="Person">
import { ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue';
   let sum=ref(0)
   function addSum(){
         sum.value++
   }


// setup 相当于befeforeCreate和created
  console.log("创建完了")
  
  
//挂载需要引入onBeforeMounted
onBeforeMount(()=>{
    console.log("挂载前")
  
})

//挂载完引入onMounted
onMounted(()=>{
    console.log("子---挂载完毕")
  
})
//更新前
onUpdated(()=>{
    console.log("更新前")
  
})

//更新后
onUpdated(()=>{
    console.log("更新后")
  
})
//卸载前
onBeforeUnmount(()=>{
    console.log("卸载前")
  
})

//卸载后
onUnmounted(()=>{
    console.log("卸载后")
  
})



</script>
<style scoped>




</style>

axios

npm i axios

获取狗的图片

https://dog.ceo/api/breed/pembroke/images/random

后端服务器返回的一定是对象,里面有很多key value

{data: {...}, status: 200, statusText: '', headers: AxiosHeaders, config: {...}, ...}

找到需要要的key

hooks

复制代码
<template>
    <div>
        <h2>{{ sum }}</h2>
        <button @click="addSum">点我加1</button>
        <hr>
        <img v-for="(dog,index) in dogList" :src="dog" :key="index"   />
        <button @click="moreDog">再来一只狗</button>
    </div>
</template>
<script lang="ts" setup name="App">
import { ref,reactive } from 'vue';
import axios from 'axios'
let sum=ref(0);




function addSum(){
    sum.value++;
}
let dogList=reactive([
    "https://images.dog.ceo/breeds/pembroke/n02113023_3927.jpg",
    "https://images.dog.ceo/breeds/pembroke/n02113023_1659.jpg"

])
async function moreDog(){

    try{let result= await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
    dogList.push(result.data.message)}catch(error){
      console.log(error)
    }

}
</script>
<style scoped>
  img{
    height: 200px;
    margin-right: 10px;
  }
</style>

:把相关的数据和方法写在一起,有点像vue2 mixin

step1:

src下新建hooks文件夹

本质就是xx.ts或xx.js

命名规范 useXXX

step2:

定义bing暴露

useDog.ts

复制代码
import { reactive } from 'vue';
import axios from 'axios'
//export default后面直接跟值比如export default '1',所以也可以跟一个匿名函数,如果没有default则必须命名
export default function(){
    
let dogList=reactive([
    "https://images.dog.ceo/breeds/pembroke/n02113023_3927.jpg",
    "https://images.dog.ceo/breeds/pembroke/n02113023_1659.jpg"

])
async function getDog(){

    try{let result= await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
    dogList.push(result.data.message)}catch(error){
      console.log(error)
    }

}
// 向外部提供东西,可以提供对象
return {dogList,getDog}
}

useSum.ts

复制代码
import { ref ,onMounted,computed} from 'vue';
export default function(){
    let sum=ref(0);
    let bigSum=computed(()=>{
        return sum.value*10
    })
    function addSum(){
        sum.value++;
    }

    onMounted(()=>{
      addSum()
    })
    return {sum,addSum,bigSum}
}

stpe3

引入 接收 使用

复制代码
<template>
    <div>
        <h2>{{ sum }}  放大10倍后{{ bigSum }}</h2>  
        <button @click="addSum">点我加1</button>
        <hr>
        <img v-for="(dog,index) in dogList" :src="dog" :key="index"   />
        <button @click="getDog">再来一只狗</button>
    </div>
</template>
<script lang="ts" setup name="App">
import useDog from '@/hooks/useDog'
import useSum from '@/hooks/useSum'
const {dogList,getDog}=useDog()
const {sum,addSum,bigSum}=useSum()
</script>
<style scoped>
  img{
    height: 200px;
    margin-right: 10px;
  }
</style>

前端路由

因为是单页面应用所以需要路由

当点击左侧导航,触发路径变化

路径变化被路由器捕获到,加载对应组件

点击另外一个目录时,卸载原来的组件,挂载新的组件

路由设置

1)确定页面导航区,展示区

2)请来路由器

安装路由器 npm i vue-router

src下建立router文件夹 建立 index.ts

3)制定路由的具体规则(什么路径,对应什么组件)

复制代码
// 创建一个路由器bing暴露出去
// 1.1引入createRouter
import { createRouter ,createWebHistory} from "vue-router";
// 1.2引入要呈现的组件  ,一开始飘红需要关掉vscode重新打开,主要是不认vue3
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
// 2.创建路由器
const router=createRouter({
    history:createWebHistory(),//确定工作模式
    routes:[ //一个一个的路由规则
        {
            path:'/home',component:Home
        },
        {
            path:'/news',component:News
        },
        {
            path:'/about',component:About
        }
    ]
})
// 3.暴露
export default router

//4.要去main.ts引入路由器

4)main.ts中引入路由

复制代码
import { createApp } from "vue"
import App from './App.vue'
// 路由4:引入路由器
import router from "./router"
// 创建一个应用
const app=createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用
app.mount('#app')

当有个app.use(router)之后,就可以看到控制台多了Routes

这时候在地址栏加比如/news 就会被router监控到,但是还不知道展示在哪里

5)展示位置

import { RouterView } from 'vue-router';

<RouterView></RouterView>

6)增加切换支持

复制代码
 import { RouterView,RouterLink} from 'vue-router';

<div class="navigate">

           <RouterLink to="/home" class="active">首页</RouterLink>

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

           <RouterLink to="/about">关于</RouterLink>

       </div >

7)选中高亮

复制代码
<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 { RouterView,RouterLink} from 'vue-router';



</script>
<style scoped>
/* app */
.title{
    text-align: center;
    word-spacing: 5px;
    margin: 30px 0;
    height: 70px;
    line-height: 70px;
    background-image: linear-gradient(45deg,gray,white);
    border-radius: 10px;
    box-shadow: 0 0 2px;
    font-size: 30px;
}
.navigate{
    display: flex;
    justify-content: space-around;
    margin: 0 100px;
}
.navigate a{
    display: block;
    text-align: center;
    width: 90px;
    height: 40px;
    line-height: 40px;
    border-radius: 10px;
    background-color: gray;
    text-decoration: none;
    color: white;
    font-size: 18px;
    letter-spacing: 5px;
}

.navigate a.active{
    background-color: #64967E;
    color: #ffc268;
    font-weight: 900;
    text-shadow: 0 0 1px black;
    font-family: 微软雅黑;
}

.main-content{
    margin: 0 auto;
    margin-top: 30px;
    border-radius: 10px;
    width: 90%;
    height: 400px;
    border: 1px solid;
}
</style>

注意

路由组件:就是通过路由引入进来的

一般组件:通过import进来的,页面上要写<组件名/>

视觉小时了组件是被卸载了

路由工作模式

history

vue2 mode:'history'

vue3: history:createWebHistory()

hash

路由to的两种写法

1.字符串写法

<RouterLink to="/news/detail?a=哈哈&b=呃呃">{{item.title}}</RouterLink>

2.对象写法1

<RouterLink :to="{path:'/about'}" active-class="active">关于</RouterLink>

3.对象写法2

<RouterLink :to="{name:'xinwen'}" active-class="active">新闻</RouterLink>

参见命名路由

命名路由

增加了name

复制代码
 routes:[ //一个一个的路由规则
        {
            name:'zhueye',
            path:'/home',
            component:Home
        },
        {
            name:'xinwen',
            path:'/news',component:News
        },
        {   name:'guanyu',
            path:'/about',component:About
        }
    ]

嵌套路由

Vue Router warn\]: No match found for location with path "/" 没有/的路由[http://localhost:5173/](http://localhost:5173/ "http://localhost:5173/") 先引入 routes:[ //一个一个的路由规则 { name:'zhueye', path:'/home', component:Home }, { name:'xinwen', path:'/news',component:News, children:[ { path:'detail', component:Details } ] }, { name:'guanyu', path:'/about',component:About } ] }) ### 路由参数 #### query 父给子 \{{item.title}}\ a=哈哈相当于键值对 \&连接多个 子组件接收: 引入route

  • 编号:{{route.query.id}}
  • 标题:{{route.query.title}}
  • 内容:{{route.query.content}}
import {useRoute} from 'vue-router' let route=useRoute() console.log(route) 传递的参数在route对象的Target里面的query参数 ![](https://file.jishuzhan.net/article/1795666631448334338/99bcb9b89f12efcdcf72d0e691d519e2.webp) 第一种写法 \{{item.title}}\ 第二种写法 简化子处使用参数 注意解构赋值会丢失响应式 新的details.vue #### params 三个注意 1)在路由注册了参数,才能使用,并且不能缺 除非在路由参数加? path:'detail/:id/:title/:content?', 2)router link对象写法中必须使用路由name 3)router link对象写法中参数值不能是数组,对象 在路由后面不写?直接继续写/,也不用键值对 下面红色是路由,绿色是参数 ![](https://file.jishuzhan.net/article/1795666631448334338/af6980d503a0263891cd12ca4aa20ca6.webp) {{item.title}} 没占位前会报 Vue Router warn\]: No match found for location with path "/news/detail/哈哈/呃呃" 2)router/index.ts占位 path:'detail/:id/:title/:content', 有三个占位,则要求调用时必须是3个否则无法识别 {{item.title}} ![](https://file.jishuzhan.net/article/1795666631448334338/81eb3e4f17511044ae734bb07d03af3f.webp) 父组件 如下写法必须使用name ### 路由的props配置 为了简化页面引用的层级,可以在路由参数中配置: props:true #### 第一种写法 将路由收到所有params参数作为props传给路由组件 routes:[ //一个一个的路由规则 { name:'zhueye', path:'/home', component:Home }, { name:'xinwen', path:'/news',component:News, children:[ { name:'xiangqing', path:'detail/:id/:title/:content?', component:Details, props:true } ] }, { name:'guanyu', path:'/about',component:About } ] 加了props相当于在使用Detail时顺便传三个参数 ![](https://file.jishuzhan.net/article/1795666631448334338/c8c73a77650a9f802df98e27e7cde382.webp) #### 第二种写法 可以用于query 自己决定将什么作为路由组件传给props {{item.title}} children:[ { name:'xiangqing', // path:'detail/:id/:title/:content?', path:'detail', component:Details, // 第一种写法将路由收到所有params参数作为props传给路由组件 // props:true //第二种写法 自己决定将什么作为路由组件传给props props(qwe){ console.log(qwe) return{ x:100, y:200, z:300 } } } ] }, 可以看到qwe就是route ![](https://file.jishuzhan.net/article/1795666631448334338/5020e4c8f3e4515f04a5f47e8fb21923.webp) router文件修改为 children:[ { name:'xiangqing', // path:'detail/:id/:title/:content?', path:'detail', component:Details, // 第一种写法将路由收到所有params参数作为props传给路由组件 // props:true //第二种写法 自己决定将什么作为路由组件传给props // props收到的参数本质就是route,并且因为在query查询时query也是对象,所以可以返回 props(route){ return route.query } } ] #### 第三种写法 对象写法,但是是写死了,很少用 ![](https://file.jishuzhan.net/article/1795666631448334338/396cfcd2bd39627a7b103436bc3978d4.webp) ### replace 路由跳转时,会操作浏览器历史记录,默认是push push: 相当于浏览器历史记录是个栈,有个指针 ![](https://file.jishuzhan.net/article/1795666631448334338/bfd2f323b2e7f1882807346a90d537f0.webp) replace 替换 在导航区routelink上加replace 首页 新闻 关于 ### 编程式导航 脱离\实现跳转 1.设置自动跳转 import { useRouter } from 'vue-router'; 2.设置点击跳转 ![](https://file.jishuzhan.net/article/1795666631448334338/491636d07ab03efccd0bb8b081821d59.webp) ### 重定向 主要是解决刚进入网站初始页面路由报错的问题 、让指定的路径重新定位到另一个路径 ![](https://file.jishuzhan.net/article/1795666631448334338/f0bfe1ae21b55044be0899986f90bedf.webp) // 创建一个路由器bing暴露出去 // 1.1引入createRouter import { createRouter ,createWebHistory} from "vue-router"; // 1.2引入要呈现的组件 ,一开始飘红需要关掉vscode重新打开,主要是不认vue3 import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' import Details from "@/pages/Details.vue"; // 2.创建路由器,自己路由不用写斜杠 const router=createRouter({ history:createWebHistory(),//确定工作模式 routes:[ //一个一个的路由规则 { name:'zhueye', path:'/home', component:Home }, { name:'xinwen', path:'/news',component:News, children:[ { name:'xiangqing', // path:'detail/:id/:title/:content?', path:'detail', component:Details, // 第一种写法将路由收到所有params参数作为props传给路由组件 // props:true //第二种写法 自己决定将什么作为路由组件传给props // props收到的参数本质就是route,并且因为在query查询时query也是对象,所以可以返回 // props(route){ // return route.query // } // 第三种写法 props:{ a:100, b:200, c:300 } } ] }, { name:'guanyu', path:'/about',component:About }, { path:'/', redirect:'/home' } ] }) // 3.暴露 export default router //4.要去main.ts引入路由器 ## pinia:共用数据的处理 [Pinia \| The intuitive store for Vue.js](https://pinia.vuejs.org/ "Pinia | The intuitive store for Vue.js") Pinia 是 Vue 的存储库,它允许跨组件/页面共享状态。实际上,pinia就是[Vuex](https://so.csdn.net/so/search?q=Vuex&spm=1001.2101.3001.7020 "Vuex")的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家可以直接将pinia比作为Vue3的Vuex。 vue2用的是vueX vue3用的是pinia 集中式状态(也就是数据)管理 把各个组件需要共享的数据交给pinia [https://api.uomg.com/api/rand.qinghua?format=json](https://api.uomg.com/api/rand.qinghua?format=json "https://api.uomg.com/api/rand.qinghua?format=json") 生成id npm i nanoid npm i uuid ### 准备 1.sum.app 注意把下值转换为数字的两种方法 v-model.number='n' 这样更优雅 或者:value= 2.Talk.vue 注意axios获取接口数据后的两次结构,一次重命名 还有取随机数 ### 安装 npm i pinia main.ts引入 创建 安装三步 import { createApp } from "vue" import App from './App.vue' // pinia1 :引入pinia import { createPinia } from "pinia" // 路由4:引入路由器 // import router from "./router" // 创建一个应用 const app=createApp(App) // 使用路由器 // app.use(router) // pinia2 app创建完后创建pinia const pinia=createPinia() // pinia3 :安装pinia app.use(pinia) // 挂载整个应用 app.mount('#app') 之后浏览器会出现 ![](https://file.jishuzhan.net/article/1795666631448334338/099863e45892a4f8a4f5cc4334632333.webp) ### 建立仓库 pinia强调分类 src下应该建立store 在其下建立比如count.ts import { defineStore } from "pinia"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useCountStore=defineStore( // 第一个参数一般建议与文件名一致 'count', { // 真正存储数据的地方是state state(){ return{ sum:6 }} } ) 这样就有了 ![](https://file.jishuzhan.net/article/1795666631448334338/6f10378e44ba3ce4ea1ca257a0d37541.webp) 把cout.ts想象成一个仓库,只要跟统计相关的都放入 ### 组件使用 // pinar1:引入 import {useCountStore} from '@/store/count' // const countStore=useCountStore(); console.log("@@@@@",countStore) countStore实际上是reactive定义的响应式对象 ![](https://file.jishuzhan.net/article/1795666631448334338/9cb29b8055b53f6db36261d8b6b790fe.webp) 关注sum和$state ![](https://file.jishuzhan.net/article/1795666631448334338/520646bbfb72848defb610de5f7a7df4.webp) 进一步展开 ![](https://file.jishuzhan.net/article/1795666631448334338/00f0b39f5e7717c5b597ff15bd0b8f3a.webp) sum是个REF对象,其有value,但是contStore是个Reactive对象 怎么样读出reactive里面的ref值呢?不用.value即可 let obj=reactive({ a:1, b:2, c:ref(3) }) // 在reactove里面的ref不用再拆value出来 // console.log(obj.c.value) console.log(obj.c) countStore.sum 也可以从state下面拿到,麻烦一点 countStore.$state.sum 再查看控制台,注意 就是count.ts被组件使用了才会出现在这里 ![](https://file.jishuzhan.net/article/1795666631448334338/1f757e7a7da56f14d1649055f8c5817a.webp) 另外一个例子 talk.ts import { defineStore } from "pinia"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useTalkStore=defineStore( // 第一个参数一般建议与文件名一致 'talk', { // 真正存储数据的地方是state state(){ return{ talkList:[ {id:'0001',title:'秦时明月汉时关'}, {id:'0002',title:'床前明月光'}, {id:'0003',title:'北风卷地百草折'}, {id:'0004',title:'东边不亮西边亮'}, {id:'0005',title:'遥看瀑布挂前川'} ] }} } ) 被使用后 talk.vue talkList是reactive下的reactive, ### ![](https://file.jishuzhan.net/article/1795666631448334338/ee89f97aea13d4e87a46e747cc218494.webp) 修改数据 #### 三种修改方法-1,2 #### 其它知识: 时间线的介绍 ![](https://file.jishuzhan.net/article/1795666631448334338/ef4c6e130041a0c4eb7d2ee3253a2005.webp) Mouse:看鼠标事件 常用的Componet events:组件事件 当用上述第一种直接修改,时间线pinia出现了3次,因为确实直接修改了三次 ![](https://file.jishuzhan.net/article/1795666631448334338/d9da7a5b9529fdc9ff6bcaecbdfd1f45.webp) 当使用上述第二种方法时,只发生了1次 ![](https://file.jishuzhan.net/article/1795666631448334338/4b3216b52b3dfde391c5efbee05abdd8.webp) #### 第三种修改方法 在store/count.ts中使用action import { defineStore } from "pinia"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useCountStore=defineStore( // 第一个参数一般建议与文件名一致 'count', { //action里面放置的是一个一个方法,用于响应组件中的"动作" actions:{ increment(value:any){ console.log("increment被调用了",value,this) // 通过this操作 this.sum+=value // 或者 } }, // 真正存储数据的地方是state state(){ return{ sum:6, school:'btj', address:'beijing' }} } ) 组件 注意this是当前的store,就是countStore可以直接使用sum ![](https://file.jishuzhan.net/article/1795666631448334338/ea1c8b98a693aaa3c1b3c06b42655aed.webp) 还有一个常识:$开始的都是给程序员用的,也可以用里面的$state ![](https://file.jishuzhan.net/article/1795666631448334338/452875d15a7d71ec2d87f6f14ea69b70.webp) ##### 第二个案例 import { defineStore } from "pinia"; import axios from "axios"; import { nanoid } from "nanoid"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useTalkStore=defineStore( // 第一个参数一般建议与文件名一致 'talk', { actions:{ async getPoem(){ // 发请求 let result= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // 以下是从返回中先结构data,再从data中结构content,并且把content重命名为title // let {data:{content:title}}= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // // console.log(result.data.content) // // 把请求回来的字符串包装成一个对象 let obj={id:nanoid(),title:result.data.content} // // let obj={id:nanoid(),title:title} // let obj={id:nanoid(),title} this.talkList.unshift(obj) } }, // 真正存储数据的地方是state state(){ return{ talkList:[ {id:'0001',title:'秦时明月汉时关'}, {id:'0002',title:'床前明月光'}, {id:'0003',title:'北风卷地百草折'}, {id:'0004',title:'东边不亮西边亮'}, {id:'0005',title:'遥看瀑布挂前川'} ] }} } ) #### 优雅化 目的是让vue文件插值中的{{counterStore.sum\]\]中的counterStorm能被去掉 ##### 方法1 使用toRefs解构 代价:太多东西被toRefs count.ts import { defineStore } from "pinia"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useCountStore=defineStore( // 第一个参数一般建议与文件名一致 'count', { //action里面放置的是一个一个方法,用于响应组件中的"动作" actions:{ increment(value:number){ console.log("increment被调用了",value,this) // 通过this操作 this.sum+=value // 或者 } }, // 真正存储数据的地方是state state(){ return{ sum:6, school:'btj', address:'beijing' }} } ) Count.vue 上文中用了toRefs,但是显示把countStores所有属性都变成了ref,没有这个必要 ![](https://file.jishuzhan.net/article/1795666631448334338/c1c60bd2d7127504c3a07288f3e0fa97.webp) ##### 方法2 使用storeToRefs 解构 **pinia替你想到了** import { storeToRefs } from 'pinia'; // 解构 const {sum,school,address} =storeToRefs(countStore) console.log("aaaaa",storeToRefs(countStore)) 可以看到storeToRefs只关注数据,不会对方法进行包裹 ![](https://file.jishuzhan.net/article/1795666631448334338/dcf0f6c375efcf58820af835bc575de4.webp) ### getters 对数据不满意,可以加工下 #### 写法1 import { defineStore } from "pinia"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useCountStore=defineStore( // 第一个参数一般建议与文件名一致 'count', { //action里面放置的是一个一个方法,用于响应组件中的"动作" actions:{ increment(value:number){ console.log("increment被调用了",value,this) // 通过this操作 this.sum+=value // 或者 } }, // 真正存储数据的地方是state state(){ return{ sum:6, school:'btj', address:'beijing' }}, getters:{ // bigSum(){ // return 999 // } // 默认被传递进来state bigSum(state){ return state.sum*10 } } } ) ![](https://file.jishuzhan.net/article/1795666631448334338/b31032727e63fbac4313f2774a1dc48f.webp) #### 写法2 this起始也是state ![](https://file.jishuzhan.net/article/1795666631448334338/5850c03cf8f50dfc4d1aff34d164638c.webp) import { defineStore } from "pinia"; import { compileScript } from "vue/compiler-sfc"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useCountStore=defineStore( // 第一个参数一般建议与文件名一致 'count', { //action里面放置的是一个一个方法,用于响应组件中的"动作" actions:{ increment(value:number){ console.log("increment被调用了",value,this) // 通过this操作 this.sum+=value // 或者 } }, // 真正存储数据的地方是state state(){ return{ sum:6, school:'btj', address:'beijing' }}, getters:{ // bigSum(){ // return 999 // } // 默认被传递进来state // bigSum(state){ // return state.sum*10 // }, //简写 bigSum:state=>state.sum*10, // 也可以用this // upperSchool(state){ // :String是表示返回值是字符串 upperSchool():String{ console.log("upperschool",this) return this.school.toUpperCase() } } } ) Count.vue ### 订阅 相当于监视store数据变化,可以利用来保存到storage,实现持久化 mutate 和 state分别打印 ![](https://file.jishuzhan.net/article/1795666631448334338/79f38a817d267ed969873822e15b83ae.webp) 应用1:修改locaiStorage 比如 locaiStorage里面都是字符串,如果你传的不是字符串会调用toString,如果都是对象就会变成如下: ![](https://file.jishuzhan.net/article/1795666631448334338/db4903f6cac5c9593ff6eda853abfbbd.webp) 修改为 localStorage.setItem('talkList',JSON.stringify(state.talkList)) talk.ts import { defineStore } from "pinia"; import axios from "axios"; import { nanoid } from "nanoid"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} export const useTalkStore=defineStore( // 第一个参数一般建议与文件名一致 'talk', { actions:{ async getPoem(){ // 发请求 let result= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // 以下是从返回中先结构data,再从data中结构content,并且把content重命名为title // let {data:{content:title}}= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // // console.log(result.data.content) // // 把请求回来的字符串包装成一个对象 let obj={id:nanoid(),title:result.data.content} // // let obj={id:nanoid(),title:title} // let obj={id:nanoid(),title} this.talkList.unshift(obj) } }, // 真正存储数据的地方是state state(){ return{ // 可以从localStorage中获取 // talkList:[ // {id:'0001',title:'秦时明月汉时关'}, // {id:'0002',title:'床前明月光'}, // {id:'0003',title:'北风卷地百草折'}, // {id:'0004',title:'东边不亮西边亮'}, // {id:'0005',title:'遥看瀑布挂前川'} // ] // talkList:JSON.parse(localStorage.getItem('talkList')) // 可能去除null,解决方法1:用断言,但是这样初始化就是null,二要添加的时候就会报不能在null上添加unshift,就是null.unshift // talkList:JSON.parse(localStorage.getItem('talkList') as string) //如下解决null的问题 talkList:JSON.parse(localStorage.getItem('talkList') as string)||[] }} } ) talk.vue ### store组合式写法 缺点是return多 import { defineStore } from "pinia"; import axios from "axios"; import { nanoid } from "nanoid"; // const命名规范:以use开始 // 下面的export使用的是分离暴露,也可以使用统一暴露,分离暴露引用时只能用import {} // 1.选项式写法 // export const useTalkStore=defineStore( // // 第一个参数一般建议与文件名一致 // 'talk', // { // actions:{ // async getPoem(){ // // 发请求 // let result= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // // 以下是从返回中先结构data,再从data中结构content,并且把content重命名为title // // let {data:{content:title}}= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') // // // console.log(result.data.content) // // // 把请求回来的字符串包装成一个对象 // let obj={id:nanoid(),title:result.data.content} // // // let obj={id:nanoid(),title:title} // // let obj={id:nanoid(),title} // this.talkList.unshift(obj) // } // }, // // 真正存储数据的地方是state // state(){ // return{ // // 可以从localStorage中获取 // // talkList:[ // // {id:'0001',title:'秦时明月汉时关'}, // // {id:'0002',title:'床前明月光'}, // // {id:'0003',title:'北风卷地百草折'}, // // {id:'0004',title:'东边不亮西边亮'}, // // {id:'0005',title:'遥看瀑布挂前川'} // // ] // // talkList:JSON.parse(localStorage.getItem('talkList')) // // 可能去除null,解决方法1:用断言,但是这样初始化就是null,二要添加的时候就会报不能在null上添加unshift,就是null.unshift // // talkList:JSON.parse(localStorage.getItem('talkList') as string) // //如下解决null的问题 // talkList:JSON.parse(localStorage.getItem('talkList') as string)||[] // }} // } // ) // 1.组合式写法 // 组合式数据直接用reactive定义 import { reactive } from "vue"; export const useTalkStore=defineStore('talk',()=>{ // talklist就是state const talkList=reactive( JSON.parse(localStorage.getItem('talkList') as string)||[] ) // getPoem相当于action async function getPoem(){ let result= await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') let obj={id:nanoid(),title:result.data.content} talkList.unshift(obj) } return {talkList,getPoem} }) ## 组件通信 ![](https://file.jishuzhan.net/article/1795666631448334338/136429cf9b57ec0a9e6b1b84f3200a97.webp) ### 1.props 可以实现父子双向传递 ![](https://file.jishuzhan.net/article/1795666631448334338/3bd6a7ce5e33f19cc421fd65a1e2fd52.webp) 子传父要调用父的函数![](https://file.jishuzhan.net/article/1795666631448334338/947101ff15d44b3b6b7215c3b120a209.webp) ![](https://file.jishuzhan.net/article/1795666631448334338/2afd8322f3e1b424c20f82e48cb65538.webp) 父: 尽可能不要给孙子传,虽然能实现 ![](https://file.jishuzhan.net/article/1795666631448334338/b9ba7b7a6a384142b7f19eaf0a1a4433.webp) ### 2.自定义事件 典型的用于子传父 事件名是多个单词是推荐肉串形式 a-b-c, 使用驼峰可能导致见听不到 ![](https://file.jishuzhan.net/article/1795666631448334338/f5b25899d202fa0d71ba62fbcc2ab3dd.webp) #### $event \
#### 作用域插槽 子组件slot上定义的属性,被传递给了使用者 ![](https://file.jishuzhan.net/article/1795666631448334338/8bbf4c2640549e180ffd114304dc25c3.webp) 父组件通过如下v-slot='a' 中的a接收 ![](https://file.jishuzhan.net/article/1795666631448334338/b2aa47664c7d8db109f9bbb5f094716c.webp) 可以看到a是对象{},有三组key value ![](https://file.jishuzhan.net/article/1795666631448334338/60dce40cb589938099c8bbf36f8e4f3a.webp) ![](https://file.jishuzhan.net/article/1795666631448334338/ceab26c50cca097d86ee632ed6f4b2d5.webp) 最后Father 最后Game ## 其它API ### ShallowRef: 浅层次ref,只处理第一层 如下可修改 可修改sum和整个人,但是名字和年龄不能改 ![](https://file.jishuzhan.net/article/1795666631448334338/9e32b11dd70fb5d884da17381b47c1bd.webp) 第一层就是person.value ![](https://file.jishuzhan.net/article/1795666631448334338/19c0c4439d32fe6e647e62e23cac9367.webp) ### shallowReactive reactive定义的数据不能整体替换,要用Objective. shallowReactive:如下只能改brand这个第一层 ![](https://file.jishuzhan.net/article/1795666631448334338/a7b6a731889dcd1a92513ba00b59819b.webp) ### readOnly\&shallowReadOnly readOnly:复制一份数据,避免原数据被其他组件更改;原数据变化也会跟着变化 复制出来的这个数据不能修改自己 shallowReadOnly:复制出来的这分数据第一层不能修改,但其它层可以修改 ![](https://file.jishuzhan.net/article/1795666631448334338/8fea83864936b5f22f65a3293d4c95d6.webp) ![](https://file.jishuzhan.net/article/1795666631448334338/83eb17a3b59ad9ccf221e7e95b3f2284.webp) ### toRaw和MarkRaw #### toRaw let person2=toRaw(person) lodash #### MarkRaw ![](https://file.jishuzhan.net/article/1795666631448334338/3bd83579436b65e4cc59c582bfc7080d.webp) ### customRef ref立即变化 customRef用于实现比如6秒后变化 //hooks改造 import {customRef} from 'vue' // initValue 初始值 delayTime:延迟时间 export default function(initValue:string,delayTime:number){ // customRef要求必须有get set,用customRef来定义响应式数据 // let initValue="你好" let timer:number // 接收底层所传track 跟踪,trigger触发 let msg=customRef((track,trigger)=>{ return{ // ,sg被读取时调用 因为其在{{ msg }} ,v-model="msg"两处被读取,所以初始化页面打印出现两次 get(){ track() //告诉vue 要对msg关注,一旦msg变化就去更新,如果没有管制就算有了triggger页面也不会更新 console.log('get') return initValue }, //msg修改时 调入修改值value set(value){ clearTimeout(timer) //清除之前的定时器,因为页面每改一次就会调用setTimmeout //msg改变后,要求等1秒页面再变化 timer=setTimeout(() => { console.log('修改',value) initValue=value trigger() //通知vue一下数据变了,如果没有trigger vue不知道数据变化,很无聊 }, delayTime) } } }) return {msg} } ### TelePort 传送:默认子组件在父组件里,但是子组件弹窗需要以视口定位 父组件 弹窗组件 加了传送 弹窗跑到了body下 ![](https://file.jishuzhan.net/article/1795666631448334338/d9d880cdbb79244660087d04195d4752.webp) ### Supense ![](https://file.jishuzhan.net/article/1795666631448334338/89254018b64fbe0866113b2fe5cf74dc.webp) 异步任务导致子组件消失 改为 ### 全局API转移到应用 ![](https://file.jishuzhan.net/article/1795666631448334338/0ac2fe42b5868214de9d689ffa0f1fb5.webp) ### 其它 ![](https://file.jishuzhan.net/article/1795666631448334338/614026e0756d00108b7e04087c51e8ce.webp)

相关推荐
Hilaku几秒前
用好了 defineProps 才叫会用 Vue3,90% 的写法都错了
前端·javascript·vue.js
古夕几秒前
前端模块化与Webpack打包原理详解
前端·webpack
lyc233333几秒前
鸿蒙自定义编辑框:与输入法交互的3个核心步骤📝
前端
英宋3 分钟前
ckeditor5的研究 (2):对 CKEditor5 进行设计,并封装成一个可用的 vue 组件
前端·javascript
古夕3 分钟前
搞定滚动穿透
前端·javascript
英宋3 分钟前
ckeditor5的研究 (3):初步使用 CKEditor5 的 事件系统 和 API
前端·javascript
lyc2333338 分钟前
鸿蒙多子类型输入法:3步实现输入模式自由切换🔤
前端
Danta8 分钟前
从 0 开始学习 Three.js(2)😁
前端·javascript·three.js
凌辰揽月8 分钟前
Web后端基础(基础知识)
java·开发语言·前端·数据库·学习·算法
Dignity_呱9 分钟前
vue3对组件通信做了哪些升级?
前端·vue.js·面试