1、VUE2和VUE3对比
响应式区别
vue2 的响应式原理是利⽤es5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持结合发布订阅模式的⽅式来实现的。
vue3 中使⽤了 es6 的 proxy API 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。
这⾥是引相⽐于vue2版本,使⽤proxy的优势如下
-
defineProperty只能监听某个属性,不能对全对象监听
-
可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
-
可以监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。 (vue3.x可以检测到数组内部数据的变化)
生命周期
-
创建前:beforeCreate=> 使用setup()
-
创建后:created => 使用setup()
-
挂载前:beforeMount => onBeforeMount
-
挂载后:mounted=> onMounted
-
更新前:beforeUpdate => onBeforeUpdate
-
更新后:updated => onUpdated
-
销毁前:beforeDestroy => onBeforeUnmount
-
缓存前:activated=> onActivated
- 缓存后:deactivated=>onDeactivated
2、vue插槽
插槽就是子组件中的提供给父组件使用的一个占位符,在子组件中用< slot>< /slot > 表示。
父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的< slot>< /slot >标签。
默认插槽
我们定义两个组件:parent.vue
和child.vue
,在parent组件中引入child组件
//parent.vue <child> world </child> //child.vue <template> <div> hello <slot>你好</slot> </div> </template>
这样,组件在渲染的时候,<slot></slot>
就会被渲染为:hello world,如果在<child></child>
中没有放任何内容,就会渲染出子组件的默认内容:hello 你好
具名插槽
有时候,我们在一个组件中需要使用多个插槽,那么为了区分它们,就可以给每个插槽命名,这就是具名插槽。
slot
元素有一个特殊的attribute:name
,他可以用来指定额外的插槽:
// 子组件 <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> // 父组件 <base-layout> <template v-slot:header> <h1>header</h1> </template> <p>content</p> <template v-slot:footer> <p>footer</p> </template> </base-layout>
使用v-slot
指令来指定该部分要渲染的插槽的名字,这样就可以将三部分内容渲染到相应的位置了。不带name的插槽,会有一个默认的名字default
。如果想要更明确一些,可以将它的name设置为default:
<template v-slot:default> <p> content </p> </template>
注意:v-slot
指令只能添加在template
标签上
作用域插槽
一般情况下,是父组件使用查操过程中传入一些内容来决定插槽的内容,他可以访问到父组件中定义的属性,但是不能访问子组件中的数据。如果我们需要子组件以供一些数据,那么就可以使用作用域插槽来解决。
在子组件中动态绑定需要传入的数据:
<slot :data="data"></slot> export default { data () { return { data: { username: 'hello' } } } }
在父组件中访问子元素的数据:
<div> <test v-slot:default="slotProps"> {{slotProps.data.username}} </test> </div> // 也可以写成下面这样 <div> <test v-slot:"slotProps"> {{slotProps.data.username}} </test> </div>
需要注意: 如果有多个插槽,只要将default改为对应的插槽名称即可。
插槽的解构
作用域插槽的内部工作原理是将插槽的内容包含在一个带有单个参数的函数里,所以slot 的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。
原本这样写的代码:
<div> <test v-slot:"slotProps"> {{slotProps.data.username}} </test> </div>
可以改成:
<div> <test v-slot:{data}> {{data.username}} </test> </div>
动态插槽名
在Vue2.6中引入了动态插槽名,动态指令参数使用在v-slot上,来定义动态的插槽名:
<base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout>
具名插槽的缩写
在Vue2.6中,v-slot
可以缩写为#
:
<template v-slot:header> <h1>header</h1> </template> // 缩写后 <template #header> <h1>header</h1> </template>
需要注意,该缩写需要在其有名字的情况下才能使用,下面的情况会弹出警告:
<test #:{data}> {{data.username}} </test>
如果非要使用缩写的话,必须给他指定一个插槽名:
<test #default:{data}> {{data.username}} </test>
3、自定义指令
全局注册
// 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能 } })
局部注册
// 和data平级 directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能 } } }
应用场景
输入框防抖
防抖这种情况设置一个v-debounce
自定义指令来实现
// 1.设置v-debounce自定义指令 Vue.directive('debounce', { bind: (el, binding) => { let debounceTime = binding.value; // 防抖时间 if (!debounceTime) { // 用户若不设置防抖时间,则默认2s debounceTime = 2000; } let cbFun; el.addEventListener('click', event => { if (!cbFun) { // 第一次执行 cbFun = setTimeout(() => { cbFun = null; }, debounceTime); } else { // 阻止事件冒泡并且阻止该元素上同事件类型的监听器被触发 event && event.stopImmediatePropagation(); } }, true); }, }); // 2.为button标签设置v-debounce自定义指令 <button @click="sayHello" v-debounce>提交</button>
图片懒加载
设置一个v-lazy
自定义指令完成图片懒加载
const LazyLoad = { // install方法 install(Vue,options){ // 代替图片的loading图 let defaultSrc = options.default; Vue.directive('lazy',{ bind(el,binding){ LazyLoad.init(el,binding.value,defaultSrc); }, inserted(el){ // 兼容处理 if('IntersectionObserver' in window){ LazyLoad.observe(el); }else{ LazyLoad.listenerScroll(el); } }, }) }, // 初始化 init(el,val,def){ // data-src 储存真实src el.setAttribute('data-src',val); // 设置src为loading图 el.setAttribute('src',def); }, // 利用IntersectionObserver监听el observe(el){ let io = new IntersectionObserver(entries => { let realSrc = el.dataset.src; if(entries[0].isIntersecting){ if(realSrc){ el.src = realSrc; el.removeAttribute('data-src'); } } }); io.observe(el); }, // 监听scroll事件 listenerScroll(el){ let handler = LazyLoad.throttle(LazyLoad.load,300); LazyLoad.load(el); window.addEventListener('scroll',() => { handler(el); }); }, // 加载真实图片 load(el){ let windowHeight = document.documentElement.clientHeight let elTop = el.getBoundingClientRect().top; let elBtm = el.getBoundingClientRect().bottom; let realSrc = el.dataset.src; if(elTop - windowHeight<0&&elBtm > 0){ if(realSrc){ el.src = realSrc; el.removeAttribute('data-src'); } } }, // 节流 throttle(fn,delay){ let timer; let prevTime; return function(...args){ let currTime = Date.now(); let context = this; if(!prevTime) prevTime = currTime; clearTimeout(timer); if(currTime - prevTime > delay){ prevTime = currTime; fn.apply(context,args); clearTimeout(timer); return; } timer = setTimeout(function(){ prevTime = Date.now(); timer = null; fn.apply(context,args); },delay); } } } export default LazyLoad;
一键 Copy的功能
import { Message } from 'ant-design-vue'; const vCopy = { // /* bind 钩子函数,第一次绑定时调用,可以在这里做初始化设置 el: 作用的 dom 对象 value: 传给指令的值,也就是我们要 copy 的值 */ bind(el, { value }) { el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到 el.handler = () => { if (!el.$value) { // 值为空的时候,给出提示 Message.warning('无复制内容'); return; } // 动态创建 textarea 标签 const textarea = document.createElement('textarea'); // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域 textarea.readOnly = 'readonly'; textarea.style.position = 'absolute'; textarea.style.left = '-9999px'; // 将要 copy 的值赋给 textarea 标签的 value 属性 textarea.value = el.$value; // 将 textarea 插入到 body 中 document.body.appendChild(textarea); // 选中值并复制 textarea.select(); // textarea.setSelectionRange(0, textarea.value.length); const result = document.execCommand('Copy'); if (result) { Message.success('复制成功'); } document.body.removeChild(textarea); }; // 绑定点击事件,就是所谓的一键 copy 啦 el.addEventListener('click', el.handler); }, // 当传进来的值更新的时候触发 componentUpdated(el, { value }) { el.$value = value; }, // 指令与元素解绑的时候,移除事件绑定 unbind(el) { el.removeEventListener('click', el.handler); }, }; export default vCopy;
关于自定义指令还有很多应用场景,如:拖拽指令、页面水印、权限校验等等应用场景
4、Vuex共享状态
包含state(数据源)、mutations(数据更新的唯一方法,必须为同步函数)、actions(异步提交mutation)、getters(数据源的计算属性)、modules(模块化store)
Vuex为什么要有异步更新action
mutation
内部必须是同步函数,因为数据的复用,异步会导致内部状态难以追踪,但有些操作又是异步更新的,所以需要通过action提交dispatch给mutation修改state
store.commit()是否可以带第三个参数
不可以,多个参数需要用数组或对象的方式传递
5、vue常识
Vue实例中的data是对象,Vue组件中的data必须是函数返回值
vue实例一般情况不会复用,所以vue实例data可以是一个对象。但是设计组件的目的就是为了复用,所以组件内的data不能是对象。
如果vue实例对象/vue组件复用时,data是一个对象,data属性的数据在不同的页面中会始终同步,那么造成的后果就是不同组件页面的数据会相互影响。比如说 你在首页使用组件时更改了data内数据,其他复用该组件的页面也会同步更新,这在正常情况下是我们不希望看到的,当然vue官方也不希望我们这么做(语法报错)。
V-if和v-show
v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在
v-if显示隐藏是将dom元素整个添加或删除
V-if和v-for能不能同时使用,两者的优先级
vue2内部v-for的优先级是高于v-if,即每一次v-if都需要遍历整个数组,浪费性能
vue3内部v-if的优先级是高于v-for,同时使用报错
$nextTick()的使用场景
Vue.nextTick() 是vue的全局api,它主要用来在下次dom更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的dom。 由于vue的更新机制是异步的,所以当数据修改之后,dom还停留在更新之前,此时想要获取更新后的dom,可以使用nextTick,表示的是下次dom更新循环结束后执行的回调。
应用场景:created 中获取dom可以使用nextTick、异步更新DOM、异步获取数据、异步更新数据
标签:el,Vue,textarea,插槽,let,相关,组件,原理,data From: https://www.cnblogs.com/JC30705/p/18128375