vue3 知识点的补充 之 第一节
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
01 vue2与vue3的区别
vue2 采用object.defuneProperty()实现 对数组不友好 重写了数组的方法,同时无法监听数组length长度的改变。对于对象只能劫持设置好的数据 新增需要使用vue.set
vue3 采用proxy进行代理,不需要重写数组的方法 同时可以监听数组长度的变化
第二点区别:
对于DOM中静态的数据,不会再去进行对比, 对于{{}}动态的数据 id calss所有会发生改变的,会打一个标记去进行对比。比如文本会打text的标记 id如果会变的话就会增加props的标记。
02 如何使用vscode去开发vue3的项目
第一步 需要安装 volar的插件和Typescript Vue Plugin(Volar)插件, 同时需要将vue2的vetur
03 npm run dev执行的过程:
第一步 去找package.json文件中scripts中的dev的脚步执行
第二步 node_modules中去找vite/pageage.json,然后再找bin目录下的vite.js去执行
04 DOM的点击事件也可以变成动态的
<div @[event]="btn"></div> <script setup lang="ts">const event = 'click'</script>
05 虚拟DOM和diff算法
真实的DOM身上会有几百个属性和方法因此操作真实的DOM消耗性能
虚拟DOM就是通过js来生成一个AST语法树
diff算法分为有key和无key两种的模式:
无key的情况下:
新的虚拟DOM会一项一项的去替换旧的虚拟DOM。
到最后如果新的虚拟DOM多余旧的虚拟DOM,那么新的虚拟DOM就会增加到元素中,
如果最后旧的虚拟DOM多余新的虚拟DOM,那么就会把旧的多余的虚拟DOM进行删除。
有key的情况下:
1前序对比 拿A和A对比 B和B对比 到C的时候发现C和DDD不同就break跳出循环
2 尾序对比 拿D和D对比 C和C对比 发现B和DDD不同的时候跳出循环
3 发现多了一个就会增加
4 发现少了就会进行卸载
5 无序排列的:首先要对元素进行一个排序 记录新节点再旧节点的位置。新的增加 多的删除
如果出现交叉然后使用最长递归子序列算法
06关于Ref相关
如果传递给ref的是基本数据类型会直接返回,如果传递的是引用数据类型会调用reactive函数
第一种 关于 ref定义响应式的数据 const count = ref(10)
第二种 isRef 判断是否是ref的对象 console.log(isRef(count))
第三种shallowRef浅层式的ref,只到.value ,value后面的数据不再进行响应
注意:ref如果在shallowRef在同一个函数的作用域中被使用的时候ref会影响shallowRef
triggerRef() 会强制更新,如果使用shallowRef更新深层的数据视图不发生变化的时候,调用triggerRef(要更新的数据), 参数要更新的数据 然后视图也会强制更新。
customRef 自定义ref,参数是一个函数,函数的返回值是一个对象,对象包含了get和set方法:
const myRef = (value)=>{
return customRef((tarck,trigger)=>{ tarck收集依赖 trigger触发依赖
return {
get(){
track()
return value
},
set(newValue){
。。。在这里可以做点别的事情 比如发送网络请求等
value = newValue
trigger()
}
}
})
}
07 reactive只能传引用类型的数据
reactive proxy 不能直接赋值,否则会破坏响应式对象的,这是因为和内存有关,如果直接赋值的改变了内存的地址。
比如 let list = reactive([])
const add = ()=>{setTimeout(()=>{let res = [1,2,3,4] list = res )})} 这个时候页面是不会发生任何变化的
第一种方案 const add = ()=>{setTimeout(()=>{let res = [1,2,3,4] list.push(...res) )})} 页面发生变化
第二中方案 let list = reactive({arr:[]}) 修改list为一个对象
const add = ()=>{setTimeout(()=>{let res = [1,2,3,4] list.arr = res)})}
08 readonly为只读的
const read = readonly({a:1})
const add = ()=>{read.a = 2} // 无法进行修改
注意点readonly会受reactive的影响
09 shallowReactive浅层的 只会修改第一层的数据
const obj = shallowReactive({a:b:{c:2}}) //只能修改第一层的数据
同样也会受reactive的影响
10 toref 只能修改响应式对象的值,对非响应式对象的值没有办法进行视图的更新
const obj = {name:zs}
const obj1 = reactive({name:'ls'})
const name1 = toref(obj,'name')
const name2 = toref(obj1,'name')
const change = ()=>{name = 'zs1', name2='ls1'} 这个时候name1视图不会发生变化 name2会发生变化
11 torefs将所有响应式的对象中的每一个属性都变成修改后是相应式的:
源码:
const torefs = (object)=>{
const map = {}
for (key in object) {
map[key] = toref(object,key)
}
return map
}
const obj1 = reactive({a:1,b:2,c:3})
const {a,b} = torefs(obj1)
const change = ()=>{a=3,b=4} // 响应式的
12 toRaw()将响应式的对象转化为原始对象 也就是非响应式的对象
const obj = reactive({a:1,b:2,c:3})
const obj2 = toRaw(obj) // 非响应式对象了
13 vue3响应式的原理
这里解释下:receiver这个参数和target相同都代表元素本身
Reflect是用方法的形式来获取和设置属性的值,相当于target[key] target[key]=12
14依赖收集: 收集的格式
副作用函数:
let activeEffect;
export const effect = (fu:function)=>{
const _effect = function(){
activeEffect = _effect
let res = fn()
return res
}
_effect()
return _effect
}
收集依赖的方法:const obj = {name:'zs'}
const targetMap = new WeakMap() // 数据结构是 { {}:{} }
export const track = (target,key)=>{
let depsMap = targetMap.ger(target)
if(!depsMap) {
depsMap = new Map()
targetMap.set(target,depsMap) // 数据结构 { {name:'zs'}: {} }
}
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps) // 此刻的数据结构 { {name:'zs'}:{name:[]} }
}
deps.add(activeEffect) // 收集副作用函数 // { {name:'zs'}:{name:[activeEffect]} }
}
依赖更新的方法
export const trigger = (targer,key)=>{
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach(effect=>effect())
}
15 依赖收集和触发依赖 :
import {tarck,trigger} from './effect.js'
const isObject = (res)=> res!==null && typeof res =='object'
export const reactive = (target)=>{
return new Proxy(target,{
get(target,key,receiver){
let res = reflect.get(target,key,receiver)
tarck(target,key) // 收集依赖的方法
if(isObject(res)){return reactive(res)} // 如果对象值是对象进行递归
return res
},
set(target,key,value,receiver){
let res = reflect.set(target,key,value,receiver)
trigger(target,key) // 触发更新
return res
}
})
}
16 项目中index.html页面中使用effect
<div id="app"></div>
<script type="module">
import {effect} from './effect.js'
const user = reactive({name:'小曼',age:12})
effect(()=>{
document.querySelector('#app').innerText = `${user,name}-${user.age}`
})
</script>
17 computed计算属性:
第一种 选项式写 法:
<div>{{}}</div>
<input v-model="firstName" /> <input v-module="lastName">
<button @click="changeName">修改计算属性name的值</button>
let firstName = ref('张') let lastName = ref(‘三’)
let name = computed({
get(){
return firstName.value + '-' + lastNmae.value // 当input 输入框的值发生改变的时候 这里的值也会随着改变
},
set(newValue){
console.log(newValue) // 王-五
[firstName.value,lastName.value] = newValue.split('-') // 分隔成数组 然后给firstName和lastName重新赋值
}
})
const changeName = ()=>{name.value = '王-五'}
第二种函数的写法: 不允许修改值得
let name =computed(()=>{return firstName.value + '-' + lastNmae.value })
computed计算属性的源码:
interface Options {
scheduler?:Function
}
let activeEffect;
export const effect = (fn,options) = >{
const _effect = function (){
activeEffect = _effect()
let res = fn()
return res
}
_effect.options = options
_effect()
return _effect
}
const targetMap = new WeakMap() // 数据结构是 { {}:{} }
export const track = (target,key)=>{
let depsMap = targetMap.ger(target)
if(!depsMap) {
depsMap = new Map()
targetMap.set(target,depsMap) // 数据结构 { {name:'zs'}: {} }
}
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps) // 此刻的数据结构 { {name:'zs'}:{name:[]} }
}
deps.add(activeEffect) // 收集副作用函数 // { {name:'zs'}:{name:[activeEffect]} }
}
export const trigger = (targer,key)=>{
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach(effect=>{
if(effect?.options?.scheduler){
effect?.options?.scheduler?.()
}else{
effect()
}
})
}
export const computed = (getter:function)=>{
let _value = effect(getter,{scheduler:()=>{_dirty=true}})
let _dirty = true // 脏值检测
let catchValue;
class ComputedRefImpl{
get value () {
if(_dirty){
catchValue = _value()
}
return catchValue
}
}
return new ComputedRefImpl()
}
18 watchEffect监听器的使用:
const message = ref(‘消息’)
const stop = watchEffect((oninvalidate)=>{
console.log(meaasge+'message数据发生改变了')
oninvalidate(console.log('我会先执行')) // 这个函数会先执行 可以清除一些副作用
})
通过调用watchEffect的返回值,可以清除这个监听器
<button @click="clearEffect">清除监听器</button>
const clearEffect = ()=>{ stop() } // 清除了监听器
19 组件之间的传参方式:
第一种 父组件向子组件传递参数:
第一种 父组件向子组件传递参数:
父组件:
<div>
<son :title="title" :arr="arr"></son>
</div>
子组件:
<div>{{title}}</div>
01 第一种不使用ts接收
<script>
const props = defineProps({
title:{type:string, default:'默认值'}
})
console.log(props.title)
</script>
02 第二种使用ts的方式接收
<scipt>
defineProps<{ title:string,arr:number[]}>()
</script>
03 使用ts指定默认值需要使用withDefaults的这个方法
需要接受两个参数 第一个参数正常接受porps传递的参数的类型 第二个参数定义默认值的对象
<script>
withDefault( defineProps<{ title:string,arr:number[]}>(), {arr:()=>[]})
</script>
第二种 子组件给父组件传递参数:
子组件:
第一种 没有使用ts的方式
<button @click="send">给父组件传递参数</button>
<script>
defineEmits(['sendClick'])
const send = ()=>{
emit('sendClick','传递的参数')
}
</script>
第二种使用ts的方式传值
<script>
defineEmits<{
(e:"send-click",str:string):void
}>()
const send = ()=>{
emit('sendClick','传递的参数')
}
</script>
父组件接受:
<son @sendClick="getData"></son>
const getData = (str:string)=>{
console.log(str)
}
第三种 子组件暴露数据给父组件进行调用,父组件通过ref调用
子组件的数据:
defineExpose = ({
name:'小曼',
fn:()=>{console.log('fn')}
})
父组件调用:
<son ref="children"></son>
const children = ref()
console.log(children.value.name)
ts如何读取子组件的类型来定义ref的类型呢:
关于子组件定义ref的时候有自带的类型
const children = ref<InstanceType<Typeof waterFallVue>>()
20 案例封装瀑布流的组件:
父组件给子组件传递的数据:
<son :list="list"></son>
const list = [
{height:300, background:'red'},{height:400, background:'pink'},...}
]
子组件:
<template>
<div class="wraps">
<div class="items" v-for="item in waterList"
:style="{
height:item.height+'px',
background:item.background,
left: item.left+'px',
top:item.top+'px'
}">
<div>
</div>
</template>
<script lang="ts" setup>
const waterList = reactive<any[]>([]) // 接收第一行的数据
const heightList:number[] = [] // 维护高度的数组
const props = defineProps<{list:any[]}>() // ts类型接收传递过来的参数
const init =()=>{
const width = 130 // 定义一个宽度 不要让每个items按着太近
const x = document.body.clientWidth // 获取窗口可视区的宽度
const column = Math.floor(x/width) // 计算一行可以存放几个元素
for(let i = 0; i<props.list.length; i++) {
if(i<column){ // 判断是否属于第一行的元素 如果属于第一行的元素给第一行数据添加left top
props.list[i].left = width * 1; // 定位的左侧距离
props.list[i].top = 20 // 距离顶部定位的距离
waterList.push(props.list[i]) // 将第一行的数据 添加到数组中 接着遍历div第一行数
组的数据
heightList.push(props.list[i].height) // 将第一行的数据的高度传递到数组中
}else { // 当前的数据已经超过了第一行的数据 准备第一行追加数据 这时候要追加到最短的一行
let current = heightList[0] // 先把第一行第一个数据拿出来 假设是最小的
let index = 0
通过遍历先去找出真正的最小的数字
heightList.forEach((h,i)=>{
if(current > h) {
current = h
index = i
}
})
console.log(current) // 找到了最小的高度
props.list[i].top = current + 20 // 设置当前元素定位的高度
props.list[i].left = index*width //定位的left为当前找到元素的索引*设置的width度
heightList[index] = heightList[index] + props.list[i].height + 20 // 再把当前
找到的这个元素的高度 改为新增元素后的高度,
waterList.push(props.list[i]) // 再不断的向数组里面追加数据
}
}
}
onMounted(()=>{init()}) // DOM渲染完毕 调用上面的方法
</script>
<style>
.wraps {
position:relative;
.items:{
position:absolute;
width:120px;
}
}
</style>
21 vue3:异步组件&分包&suspense
异步组件的使用:结合骨架屏的使用
<tempalte>
<Suspense>
<template #default> <asyncComponent/> </template>
<template #fallback> <gujiaping/ > </template>
</Suspense>
</template>
<script>
import gujiaping from './gujiaping.vue'
const asyncComponent = defineAsyncComponet(()=>import('@/component/asyncComponent.vue'))
</script>
在项目打包的时候,所有的组件都会被加载在同一个js文件中, 首次加载因为体积比较大 所以加载时间慢
采用异步组件的时候 打包会自动进行分包,增加组件的js文件,有利于性能优化。
22 传送组件Tekeport
主要解决的问题:
父组件嵌套子组件/ 子组件嵌套一个弹框的组件, 弹框的盒子,需要在屏幕中居中。
如果弹框盒子的父级也就是子组件不设定position:relative的属性,这个盒子是不会受子组件影响
问题: 但是如果子组件设置了position:relative, 那么弹框的盒子就会根据子组件去定位了
解决问题的方法使用:Teleport
使用方法:在子组件中:
<template>
<Teleport :disable="false" to="body"> // disable是否关闭传输门 to传送的位置
<Tankuang/> // 也就是定位以body为准了
</Teleport>
</template>
23 transition动画组件
从 隐藏 到进入的样式
从显示到离开的状态
24 transition动画结合Animate.css动画库来使用:
第一步 需要安装animate.css
npm install animate.css -S
第二步 使用:
在.vue文件中script标签下引入:
<script>
import 'animate.css'
</script>
然后在transtion标签上面使用自定义动画的名字就可以了
<tempalte>
<transition leave-active-class="animate_animated animate_fadeOut">
<div v-if="isShow">显示与隐藏</div>
</transition>
</template>
leave-active-class类名是transition标签自带的 有进入 离开过渡的属性 结合 animate.css的类名使
用便可
25 vue3 的css可以绑定定义的数据 const color = 'pink', .box{color:v-bind(color)}
25 使用一个兄弟组件之间通信的Bus,遵循的还是发布订阅的模式:
type BUsClass = {
emit:(name:string)=>void
on:(name:string,callback:Function)=>void
}
type ParamsKey = string | number | symbol
type List = {
[key:ParamsKey]:Array<Function>
}
class Bus implements BusClass{
list:List
constructor(){
this.list = list
}
emit(name:string,...args:Array<any>){
let evebtName:Arrat<Function> = this.list[name]
evebtName.forEach(fn=>{
fn.apply(this,args)
})
}
on(name:string,callback:Function){
let fn:Array<Function> = this.list[name] || []
fn.push(callback)
this.list[name] = fn
}
}
26 vue3使用tsx的语法:
第一步 安装插件
npm install @vitejs/plugin-vue-jsx -D
第二步 vite.config.ts配置
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins:[vue(),vueJsx()]
})
第三步 测试组件 test.tsx
01 写法 返回一个函数
export default function (){
return (<div>小明</div>)
}
02 写法 optionsApi
import {defineComponent} from 'vue'
export default defineComponent({
data(){return {age:12}},
render(){
return (<div>{this.age}</div>)
}
})
03 setup函数的写法
// 这是另外一个组件 需要给传递插槽的数据
const A = (_,{slots})=>{
<div>{slots.default?slots.default():'默认值'}</div>
<div>{slots.foo?.()}</div>
}
// 这是一个tsx的组件
import {defineComponent,ref} from 'vue'
interface Props {
name:string
}
export default defineComponent({
props:{name:string} // 接收父组件传递的值
setup(props:Props,{emit}){
const isShow = ref(false)
const fn = (v)=>{
emit('sendClick',v)
}
const slot = {default:()=>(<div>插槽的内容</div>), foo:()=>(<span>11122</span>)}
return ()=>(
<>
<div v-show={isShow.value}>小曼</div>
<div>{props.name}</div>
<button onClick={()=>fn(v)}>派发事件</button>
<A v-slot={slot}><A>
</>
)
// 注意这里支持v-show但是不支持v-if 需要使用和react的一样语法{true&&<要渲染内容>}
// map 代替 v-for
// {} 代替 v-bind
// 支持 v-model
}
})
第四步 组件使用
<xiaoming name={name} @sendClick="getData"></xiaoming>
const name = ref('zs')
const getData = (v)=>{
console.log(v)
}
import xiaoming from './test.tsx'
27 自定义指令:声明周期函数:created beforMount mounted beforUpdate updated ... 等:
第一步 创建自定义的指令:
import {Directive} from 'vue'
const vMove:Directive = {
create(){console.log('create')}
beforeMount(){console.log('beforeMount')}
mounted(...args){
conole.log(args)
// 得到一个数组 数组的第一个元素 div.A 是v-move绑定的组件的根元素的div
// 得到的第一个元素 是一个对象
{arg:'aaa', dir:
created:f,beforeMounted:f,...},modifiers: {xiaoman:true}, value:{background:'red'}}
这里分开的写法 为
mounted(el,dir){ // 第一个元素为绑定的属性 第二个元素为bind绑定的值
el.style.background = dir.value.background
}
}
... 同vue2的声明周期函数
}
第二步 给组件身上绑定自定义的指令
<A v-move:aaa.xiaoman="{background:'ref'}"></A>
28 自定义指令 函数简写的方式:只使用mounted 和 updated的函数, 按钮鉴权、
<template>
<div>
<button v-has-show="shop:create">创建</button>
<button v-has-show="shop:deit">编辑</button>
<button v-has-show="shop:delete">删除</button>
</div>
</tempalte>
import type {Directive} from 'vue'
const permission = ['shop:create','shop:deit','shop:delete'] // 后台返回权限的数据
const vHasShow:Directive = (el,bingding)=>{
console.log(el,bingding) // el->button binding->shop:create || deit || delete
if(!permission.includes(bingding.value)){
el.style.display = 'none' // 隐藏按钮
}
}
29 自定义拖拽指令
import {ref,Directive,DirectiveBinding} from 'vue'
const vMove:Directive<any,void> = (el:HTMLElement,bingding:DirectiveBinding)=>{
let moveElement :HTMLDivElement = el.firstElementChild as HTMLDivElement //获取的div拖拽元素
const mouseDown = (e:MouseEvent)=>{ // 鼠标按下的事件
let x = e.clientX - el.offsetLeft // 记录当前鼠标点击在盒子的左侧的位置
let y = e.clientY - el.offsetTop
// 定义鼠标移动的事件
const move = (e:MouseEvent)=>{
console.log(e)
el.style.left = e.clientX -X +'px'
el.style.top = e.clientY - Y + 'px'
}
document.addEventListener('mousmove',move) // 鼠标移动
document.addEventListener('mouseup',()=>{
document.removeEventListener('mousemove',move) // 清除移动事件
})
}
moveElement.addEventListener('mousedown',mouseDown)
}
30 自定义图片懒加载的指令:
一次性加载所有图片的方法
<template>
<div>
<img v-for="item in arr" v-lazy="item" width="300" height:'400'/>
</div>
</template>
<script>
// 获取assets/images下所有的图片
const arr = Object.values(import.meta.glob('./assets/images/*.*'),
{eager:true}).map(v=>v.default)
// 封装懒加载指令
const lazy:Directive<HMLImageElement,string> = async (el,bingding) = >{
const def = await import('./asstes/vue.svg') // 默认的图片
el.src = def.default
const observer = new IntersectionObserver(err=>{
if(err[0].intersectionRatio>0){ // 说明出现在了页面中
el.src = bingding.value
observer.unobserve(el) //停止监听
}
})
observer.observe(el)
}
</script>
GitHub 加速计划 / vu / vue
207.55 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献2条内容
所有评论(0)