在实验室用的react,实习用的vue,干我们这一行,框架再怎么变,基本思想和设计模式不怎么变,但是呢框架也是有区别的,之前学习哔哩哔哩尚硅谷的vue3,记不住就恼火了哇,所以这篇文章就相当于学习笔记了。
核心语法
组合式api
vue2的option(选项式)api 将数据、方法、计算属性分散在data
、methods
、compute
中的,如果要增加或者修改一个需求,就需要分别修改data
、methods
、compute
不利于维护和复用
选项式api
vue 提供了setup钩子函数,从而实现组合式api
选项式api是将数据、方法集中在一起,在我们增加一个数据和方法的时候直接增加,而不需要像组合式那样到处都要加一部分
原始setup
原始的setup我们直接在script里面 写入setup函数,里面的变量就是我们的数据,里面的方法也是供我们使用的方法,但是我们的数据和方法 需要return才能使用
注意: 这种写法没有用setup语法糖,我们仍然可以使用 data,method,compute等,但是 setup是优先执行于data,因此 data是可以访问到setup里面的数据,而setup不能访问到data里面的数据,同时setup不能使用this
js
<script lang="ts">
export default {
name: 'Person',
data(){
return {
c:this.name,// data能访问setup里面的数据
age:13,
}
},
methods:{
},
setup(){//setup不能访问到data里面的数据
let name = 'lisi'
return {name}
},
}
</script>
setup语法糖
setup语法糖需要我们将setup字段写到script标签里面,我们定义的数据和方法他自动帮我返回,我们不需要再写return
js
<script lang="ts" setup>
let name = 'lisi'
</script>
响应式数据
reactive响应式数据
reactive用于声明响应式复杂数据类型,修改对象的元素,直接修改即可,但是要修改整个对象 则需要使用Object.assign
,否则会失去响应式,
js
<template>
<div>{{ Person.name }}</div>
<div>{{ Person.age }}</div>
<button @click="changeName"></button>
</template>
<script lang="ts" setup>
import {ref, reactive} from 'vue'
let Person = reactive({name:'lis',age:18})
const changeName= ()=>{
Person.name='王五'//直接修改
}
const changeAge= ()=>{
Person.age+=1//直接修改
}
const changePerson = ()=>{
Person= Object.assign(Person,{name:'wangwu',age:19})
}
</script>
我们打印Person显示的是一个Proxy代理对象,它也具有原始对象Object的方法.比如:代理的是一个Array 则也会有Array的一些方法和属性
注意: js提供了解构赋值,在reactive里面结构赋值时会失去响应式,要解构赋值的话使用toRefs()
包裹一下即可,toRef
的话 则是 let n = toRef(Person,"name")
ref响应式数据
在vue2中data里面的数据默认是响应式的,在vue3中可以接受vue2的写法。
vue3中,数据的响应式写法通过ref和reactive来声明的
注意 :首先声明这个ref并不是 <h2 ref="xxx">
这个ref
js
<script lang="ts" setup>
import {ref} from 'vue'
let name = ref('lisi')//通过ref声明的数据是响应式的
const changeName= ()=>{
name.value='王五'//修改name 必须.value才能修改
}
</script>
同时ref也可以声明复杂数据类型
js
<script lang="ts" setup>
import {ref} from 'vue'
let Person = ref({name:'lis',age:18})
const changeName= ()=>{
Person.value.name='王五'//直接修改
}
const changeAge= ()=>{
Person.value.age+=1//直接修改
}
const changePerson = ()=>{
Person.value= Object.assign(Person.value,{name:'wangwu',age:19})
}
</script>
注意: 修改时要加上.value属性,同时ref声明的是复杂数据类型, 打印之后其实是proxy对象
computed计算属性
当计算属性所需要的元素变化时,他会自动计算出新的值,而且只会计算一次
js
<template>
<div>{{ firstName }}</div>
<div>{{ lastName }}</div>
<div>{{afterCompute}}</div>
<div>{{afterCompute}}</div>
<div>{{afterCompute}}</div>
<button @click="changePerson">修改名字</button>
</template>
<script lang="ts" setup>
import {computed, ref} from 'vue'
let firstName =ref("王")
let lastName =ref("五")
const changePerson = () => {
firstName.value="李"
}
let afterCompute = computed(()=>{
return firstName.value+lastName.value
})
</script>
注意: 这样写computed属性是只读的,不能修改。比如上面的afterCompute我们不能在其他函数里面修改如果要修改的话,写成一个对象,并且有get和set方法
js
<template>
<div>{{ firstName }}</div>
<div>{{ lastName }}</div>
<div>{{afterCompute}}</div>
<button @click="changePerson">修改名字</button>
<button @click="changeAfterComputed">修改afterCompute</button>
</template>
<script lang="ts" setup>
import {computed, ref} from 'vue'
let firstName =ref("王")
let lastName =ref("五")
const changePerson = () => {
firstName.value="李"
}
let afterCompute = computed({
get(){
return firstName.value+lastName.value
},
set(val){
console.log(val)//最新值
console.log(val.split(""))
firstName.value=val.split("")[0]
lastName.value=val.split("")[1]
},
})
let changeAfterComputed = ()=>{
afterCompute.value ="张三"
}
</script>
watch
watch的作用是监视数据的变化
监视ref基本数据类型
当ref数据变化时,watch会监听到,并且做出相应的变化,监视时 不需要写value
js
let num =ref(0)
const addSum = () => {
num.value+=1
}
const stopWatch = watch(num,(newValue,oldValue)=>{
if(newValue>10){
stopWatch()//停止监视
}
console.log('num+1了')
})
监视ref复杂数据类型
当我们监听的是复杂数据类型时,我们传入的参数是复杂数据Object,那么他监视的话,是看的地址变没变,如果想要监视内部属性变没变,则要开启深度监视deep
js
<template>
<div>{{ Person.name }}</div>
<div>{{ Person.age }}</div>
<button @click="changeAge">age+1</button>
</template>
<script lang="ts" setup>
import {computed, ref, watch} from 'vue'
let Person =ref({
name:'张三',
age:18
})
const changeAge = () => {
Person.value.age+=1
}
const changeName = () => {
Person.value.name+='~'
}
const stopWatch = watch(Person,(newValue,oldValue)=>{//监视Person地址变没变
console.log('ly',newValue,oldValue)
})
const stopWatch = watch(Person,(newValue,oldValue)=>{
console.log('ly',newValue,oldValue)
},{deep:true,immediate:true})//深度监视,监视的值,immediate表示第一次要监视,也就是旧的值为undefined
</script>
监视reactive的响应式数据
监视reactive的响应式数据,默认开启了深度监视,3.4版本之后可以关闭了
监视ref或者reactive定义的对象类型数据中的某个属性
如果该属性值 不是对象类型 ,需要写成getter函数 形式
如果该属性值 依旧是对象类型 可以写成函数形式,也可以直接编写,不过推荐写成getter函数形式
getter函数:能返回一个值的函数
js
watch(()=>Person.name,(newValue,oldValue)=>{
console.log('ly',newValue,oldValue)
})
监视多个数据
写成数组的形式
js
watch([()=>Person.name,()=>Car.speed],(newValue,oldValue)=>{
console.log('ly',newValue,oldValue)
})
watchEffect
watch要指定监视的值,不指定就不监视。而watchEffect则不需要指定监视的属性,直接写逻辑,就可以智能判断,同时在声明的时候 watchEffect会立即运行一个函数
js
watchEffect(()=>{
if(Person.value.age>20){ // 当age 大于20时 就会触发
console.log(Person.value.age)
}
})
Porps使用
vue3的Props 用defineProps
definePropes里面的值是一个数组,值是父组件传过来的key
js
<Person a="1+1" :b="1+1">
js
let s = defineProps(['a','b'])
console.log(s)
生命周期
一般网络请求放在Mounted生命周期里面
自定义hooks
hooks说白了 就是将一个数据和对应修改数据的方法封装成一个hook,hooks本身是 js或者ts文件,命名基本上是useXXX.ts useSum.ts
js
import {onMounted, ref} from "vue";
export default function () {
//数据
let num: number =ref(0)
//方法
function addNum() {
console.log(num.value)
num.value+=1
}
onMounted(() => {
addNum()
})
// 向外部提供东西
return {num,addNum}
}
Person.vue
js
<template>
<div>{{ num }}</div>
<button @click="addNum">点击+1</button>
</template>
<script lang="ts" setup>
import useSum from "@/components/useSum";
const {num,addNum } =useSum()
</script>
路由
组件通信方式
props通信
在vue里面父子通信和react差不多,父传子直接props传递即可,子传父则需要父组件给子组件一个函数,子组件调用这个函数即可,并且将数据通过函数参数的形式传递即可
Father.js
<template>
<div class="Father">
<h2>父组件</h2>
<h4>来自子组件的信息:{{childMessage}}</h4>
<Child :message="fatherMessage" :getChildMessage="getChildMessage"></Child>
</div>
</template>
<script setup lang="ts">
import Child from "./components/Child.vue";
import {ref} from "vue";
let fatherMessage=ref('来自父组件的数据')
let childMessage =ref('')
//传递一个函数,通过数据通过参数的形式获得
let getChildMessage = (data)=>{
childMessage.value = data
}
</script>
<style scoped>
.Father{
background-color: aqua;
}
</style>
自定义事件
自定义事件我们经常用到,说自定义事件之前,我们先说一下获得事件对象,vue是通过$event
来获取
js
<button @click="getEvent($event)" >获取事件对象</button>
let getEvent = (event) => {
console.log(event.target)
}
切回正题,自定义事件 首先子组件先绑定一个haha事件,这个事件调用getData
函数
js
<Child @haha ='getData'></Child>
在子组件里面声明事件 通过defineEmits
js
const emit = defineEmits(['haha'])
在通过点击的时候触发这个事件,当然 你可以在任何你想要的时候触发事件,这里用作点击比较好理解
js
<button @click="emit('haha')" >点击触发自定义事件</button>
结果
v-model
v-modal其实本质做了两个事情,第一个是将数据进行渲染,比如原生的input框将数据进行渲染,第二个则是改变数据,就如下面第二个input框,触发input事件的时候将userName数据更改,从而达到双向绑定,这是在普通的组件上面,那么在自定义组件上面呢?和第二种写法一样的模拟即可
js
<input type="text" v-model="userName">
<input type="text" :value="userName" @input="userName = (<HTMLInputElement>$event.target).value">
Father.js
<Child
:modelValue = 'userName'
@update:modelValue = "userName = $event">
</Child>
child.js
<template>
<input
type="text"
:value="modelValue"
@input = "emit('update:modelValue',$event.target.value)"
/>
</template>
<script lang="ts" setup>
import {ref} from "vue";
//将数据拿过来
defineProps(['modelValue'])
//将数据更改
let emit = defineEmits(['update:modelValue'])
</script>
<style>
.child {
background-color: bisque;
margin-left: 50px;
}
</style>
这样我们就模拟了v-model,但是我们偷懒 可以直接用v-model,总之写这么多代码 就是了解一下v-model的本质是什么
js
<Child v-model = 'userName'></Child>
插槽
插槽的作用在于,我们封装组件的时候,外部引用组件,同时在组件标签之间写代码,标签之间的内容不会显现 比如
App.vue
<template>
<Person a="1+1" :b="1+1">
<div>你好</div>
</Person>
</template>
Person.vue
<template>
<div>{{ num }}</div>
<button @click="addNum">点击+1</button>
</template>
<script lang="ts" setup>
import useSum from "@/components/useSum";
const {num,addNum } =useSum()
</script>
我们在Person组件里面写入了【你好】但是组件内是没有插槽的,所以不会显现
默认插槽
Person.vue
<template>
<div>{{ num }}</div>
<button @click="addNum">点击+1</button>
<slot/>
</template>
<script lang="ts" setup>
import useSum from "@/components/useSum";
const {num,addNum } =useSum()
</script>
这样使用slot之后,div里面的内容就能显现
记名插槽
上面讲的默认插槽只有一个,那如果有多个插槽,顺序该怎么弄呢?这时候就要用到记名插槽了 下面代码纵使s2在上面,但是插槽内 s2写在s1下面,所以展现的时候 s1展现在上面
app.vue
<template>
<Person a="1+1" :b="1+1">
<template #s2>
<div >你好</div>
</template>
<template #s1>
<div >我不好</div>
</template>
</Person>
</template>
Person.vue
<template>
<div>{{ num }}</div>
<button @click="addNum">点击+1</button>
<slot name="s1" />
<slot name="s2" />
</template>
作用域插槽
上面的两种形式都是 父传给子数据,那假如子要传给父数据,那么我们只需要在 slot里面写入数据即可
app.vue
<template>
<Person a="1+1" :b="1+1">
// params是个对象 里面的key有data 值是list
<template v-slot = 'params'>
<div >你好</div>
</template>
</Person>
</template>
Person.vue
<template>
<div>{{ num }}</div>
<button @click="addNum">点击+1</button>
<slot name="s1" :data='list' />
</template>
总结
换框架无所谓,但是基础细节还需要过一遍,当然学一门框架最好的方法就是去不断的实践。这篇文章就算自己的总结吧,方便回头来能快速开发