个人网站备案与企业备案,网络设计专业介绍,成免费crm特色vip,东莞建设年审网站01 vue2与vue3的区别
vue2 采用object.defuneProperty()实现 对数组不友好 重写了数组的方法#xff0c;同时无法监听数组length长度的改变。对于对象只能劫持设置好的数据 新增需要使用vue.set
vue3 采用proxy进行代理#xff0c;不需要重写数组的方法 同时可以监听数组长度…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 langtsconst 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 ref10
第二种 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(){trackreturn value},set(newValue){。。。在这里可以做点别的事情 比如发送网络请求等value newValuetrigger}}})} 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, name2ls1} 这个时候name1视图不会发生变化 name2会发生变化 11 torefs将所有响应式的对象中的每一个属性都变成修改后是相应式的 源码
const torefs object{const map {}for (key in object) {map[key] torefobject,key
}
return map
}
const obj1 reactive{a:1,b:2,c:3}
const {ab} torefs(obj1)
const change (){a3,b4} // 响应式的 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 _effectlet 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(effecteffect())
} 15 依赖收集和触发依赖
import {tarck,trigger} from ./effect.jsconst isObject (res) res!null typeof res objectexport 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 idapp/divscript typemoduleimport {effect} from ./effect.jsconst user reactive({name:小曼age:12})effect((){document.querySelector(#app).innerText ${user,name}-${user.age}})
/script 17 computed计算属性 第一种 选项式写 法
div{{}}/divinput v-modelfirstName / input v-modulelastNamebutton clickchangeName修改计算属性name的值/buttonlet 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:(){_dirtytrue}})let _dirty true // 脏值检测let catchValueclass ComputedRefImpl{get value () {if(_dirty){catchValue _value()}return catchValue}}return new ComputedRefImpl()}
18 watchEffect监听器的使用
const message ref‘消息’const stop watchEffect((oninvalidate){console.log(meaasgemessage数据发生改变了)oninvalidateconsole.log(我会先执行) // 这个函数会先执行 可以清除一些副作用
})
通过调用watchEffect的返回值可以清除这个监听器button clickclearEffect清除监听器/buttonconst clearEffect (){ stop() } // 清除了监听器 19 组件之间的传参方式
第一种 父组件向子组件传递参数
第一种 父组件向子组件传递参数父组件divson :titletitle :arrarr/son
/div子组件div{{title}}/div01 第一种不使用ts接收scriptconst props defineProps({title:{type:string, default:默认值}})console.log(props.title)/script02 第二种使用ts的方式接收sciptdefineProps{ title:string,arr:number[]}()/script03 使用ts指定默认值需要使用withDefaults的这个方法需要接受两个参数 第一个参数正常接受porps传递的参数的类型 第二个参数定义默认值的对象scriptwithDefault( defineProps{ title:string,arr:number[]}(), {arr:()[]})/script
第二种 子组件给父组件传递参数
子组件第一种 没有使用ts的方式button clicksend给父组件传递参数/buttonscriptdefineEmits([sendClick])const send (){emit(sendClick,传递的参数)}/script第二种使用ts的方式传值scriptdefineEmits{(e:send-click,str:string):void}()const send (){emit(sendClick,传递的参数)}/script父组件接受son sendClickgetData/sonconst getData (str:string){console.log(str)}
第三种 子组件暴露数据给父组件进行调用父组件通过ref调用
子组件的数据defineExpose ({name:小曼fn:(){console.log(fn)}})父组件调用son refchildren/sonconst children ref()console.log(children.value.name)ts如何读取子组件的类型来定义ref的类型呢关于子组件定义ref的时候有自带的类型const children refInstanceTypeTypeof waterFallVue() 20 案例封装瀑布流的组件
父组件给子组件传递的数据son :listlist/sonconst list [{height:300, background:red},{height:400, background:pink},...}]子组件templatediv classwrapsdiv classitems v-foritem in waterList :style{height:item.heightpx,background:item.background,left: item.leftpx,top:item.toppx}div/div/templatescript langts setupconst waterList reactiveany[]([]) // 接收第一行的数据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; iprops.list.length; i) {if(icolumn){ // 判断是否属于第一行的元素 如果属于第一行的元素给第一行数据添加left topprops.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 hindex 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渲染完毕 调用上面的方法/scriptstyle.wraps {position:relative;.items:{position:absolute;width:120px;}}/style
21 vue3异步组件分包suspense
异步组件的使用结合骨架屏的使用tempalteSuspensetemplate #default asyncComponent/ /templatetemplate #fallback gujiaping/ /template/Suspense/templatescriptimport gujiaping from ./gujiaping.vueconst asyncComponent defineAsyncComponet(()import(/component/asyncComponent.vue))/script在项目打包的时候所有的组件都会被加载在同一个js文件中 首次加载因为体积比较大 所以加载时间慢采用异步组件的时候 打包会自动进行分包增加组件的js文件有利于性能优化。
22 传送组件Tekeport
主要解决的问题父组件嵌套子组件/ 子组件嵌套一个弹框的组件, 弹框的盒子需要在屏幕中居中。如果弹框盒子的父级也就是子组件不设定positionrelative的属性这个盒子是不会受子组件影响问题 但是如果子组件设置了position:relative, 那么弹框的盒子就会根据子组件去定位了解决问题的方法使用Teleport使用方法在子组件中templateTeleport :disablefalse tobody // disable是否关闭传输门 to传送的位置Tankuang/ // 也就是定位以body为准了/Teleport/template
23 transition动画组件 从 隐藏 到进入的样式 从显示到离开的状态
24 transition动画结合Animate.css动画库来使用
第一步 需要安装animate.cssnpm install animate.css -S第二步 使用在.vue文件中script标签下引入scriptimport animate.css/script然后在transtion标签上面使用自定义动画的名字就可以了tempaltetransition leave-active-classanimate_animated animate_fadeOutdiv v-ifisShow显示与隐藏/div/transition/templateleave-active-class类名是transition标签自带的 有进入 离开过渡的属性 结合 animate.css的类名使用便可
25 vue3 的css可以绑定定义的数据 const color pink, .box{color:v-bind(color)}
25 使用一个兄弟组件之间通信的Bus遵循的还是发布订阅的模式
type BUsClass {emit:(name:string)voidon:(name:string,callback:Function)void}type ParamsKey string | number | symboltype List {[key:ParamsKey]:ArrayFunction}class Bus implements BusClass{list:Listconstructor(){this.list list}emit(name:string,...args:Arrayany){let evebtName:ArratFunction this.list[name]evebtName.forEach(fn{fn.apply(this,args)})}on(name:string,callback:Function){let fn:ArrayFunction 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-jsxexport default defineConfig({plugins:[vue(),vueJsx()]})第三步 测试组件 test.tsx01 写法 返回一个函数export default function (){return (div小明/div)}02 写法 optionsApiimport {defineComponent} from vueexport default defineComponent({data(){return {age:12}},render(){return div{this.age}/div}})03 setup函数的写法// 这是另外一个组件 需要给传递插槽的数据const A (_,{slots}){div{slots.default?slots.default():默认值}/divdiv{slots.foo?.()}/div}// 这是一个tsx的组件 import {defineComponent,ref} from vueinterface 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:()(span11122/span)}return ()(div v-show{isShow.value}小曼/divdiv{props.name}/divbutton onClick{()fn(v)}派发事件/buttonA v-slot{slot}A/)// 注意这里支持v-show但是不支持v-if 需要使用和react的一样语法{true要渲染内容}// map 代替 v-for// {} 代替 v-bind // 支持 v-model}})第四步 组件使用xiaoming name{name} sendClickgetData/xiaomingconst name ref(zs)const getData (v){console.log(v)}import xiaoming from ./test.tsx
27 自定义指令声明周期函数created beforMount mounted beforUpdate updated ... 等
第一步 创建自定义的指令import {Directive} from vueconst 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}}这里分开的写法 为mountedel,dir{ // 第一个元素为绑定的属性 第二个元素为bind绑定的值el.style.background dir.value.background }}... 同vue2的声明周期函数 }第二步 给组件身上绑定自定义的指令A v-move:aaa.xiaoman{background:ref}/A
28 自定义指令 函数简写的方式只使用mounted 和 updated的函数 按钮鉴权、
templatedivbutton v-has-showshop:create创建/buttonbutton v-has-showshop:deit编辑/buttonbutton v-has-showshop:delete删除/button/div/tempalteimport type {Directive} from vueconst permission [shop:create,shop:deit,shop:delete] // 后台返回权限的数据const vHasShow:Directive (el,bingding){console.log(el,bingding) // el-button binding-shop:create || deit || deleteif(!permission.includes(bingding.value)){el.style.display none // 隐藏按钮}}
29 自定义拖拽指令
import {ref,Directive,DirectiveBinding} from vueconst vMove:Directiveany,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 pxel.style.top e.clientY - Y px}document.addEventListener(mousmove,move) // 鼠标移动document.addEventListener(mouseup,(){document.removeEventListener(mousemove,move) // 清除移动事件})}moveElement.addEventListener(mousedown,mouseDown)}
30 自定义图片懒加载的指令 一次性加载所有图片的方法
templatedivimg v-foritem in arr v-lazyitem width300 height:400//div/templatescript// 获取assets/images下所有的图片const arr Object.values(import.meta.glob(./assets/images/*.*),{eager:true}).map(vv.default)// 封装懒加载指令const lazy:DirectiveHMLImageElement,string async (el,bingding) {const def await import(./asstes/vue.svg) // 默认的图片el.src def.default const observer new IntersectionObserver(err{if(err[0].intersectionRatio0){ // 说明出现在了页面中el.src bingding.valueobserver.unobserve(el) //停止监听}})observer.observe(el)}/script