做片头片尾比较好的网站,wordpress跳转自适应,html转pdf在线,网站建设找睿智骄阳文章目录过渡 动画Transition 组件基于 CSS 的过渡效果CSS 过渡类名 class为过渡效果命名CSS 过渡 transition实例1#xff1a;实例2#xff1a;CSS 动画自定义过渡的类名同时使用 transition 和 animation深层级过渡与显式过渡时长性能考量JavaScript 动画可复用过渡效…
文章目录过渡 动画Transition 组件基于 CSS 的过渡效果CSS 过渡类名 class为过渡效果命名CSS 过渡 transition实例1实例2CSS 动画自定义过渡的类名同时使用 transition 和 animation深层级过渡与显式过渡时长性能考量JavaScript 动画可复用过渡效果出现时过渡元素间过渡过渡模式组件间过渡动态过渡TransitionGroup 组件和 Transition 的区别列表的进入 / 离开动画移动动画动画技巧基于 CSS class 的动画状态驱动的动画基于侦听器的动画混入 mixin概念Mixin和Vuex的区别注册混入选项合并mixin的优缺点[组合式API 和 Mixin 的对比](https://cn.vuejs.org/guide/reusability/composables.html#comparisons-with-other-techniques)插件插件的功能使用插件开发插件自定义指令指令介绍注册指令指令钩子函数与参数钩子函数钩子函数参数注册指令的两种方式配置对象函数简写对象字面量在组件上使用指令函数中的this实例渲染函数 render虚拟 DOM渲染管线模板 vs. 渲染函数创建虚拟DOMrender函数使用方式深入数据对象使用render函数创建组件访问this单个根标签VNode 必须唯一render函数的应用MVVMMVVM 模型MVVM思想有两个方向MVC 和 MVVM 的区别(关系)常见关于Vue的面试题响应式原理数据代理模拟响应式的实现Vue中的数据代理响应式原理总结响应式属性vue的响应式属性检测对象的属性的变化检测数组元素值的变化检测数组替换数组过渡 动画
Vue 在插入、更新或者移除 DOM 时提供多种不同方式的应用过渡效果。包括以下工具
在 CSS 过渡和动画中自动应用 class可以配合使用第三方 CSS 动画库如 Animate.css在过渡钩子函数中使用 JavaScript 直接操作 DOM可以配合使用第三方 JavaScript 动画库如 anime.js
Vue 提供了两个内置组件可以帮助你制作基于状态变化的过渡和动画
Transition 会在一个元素或组件进入和离开 DOM 时应用动画。TransitionGroup 会在一个 v-for 列表中的元素或组件被插入移动或移除时应用动画。
除了这两个组件我们也可以通过其他技术手段来应用动画比如切换 CSS class 或用状态绑定样式来驱动动画。这些其他的方法会在动画技巧章节中展开。
Transition 组件
Transition 是一个内置组件这意味着它在任意别的组件中都可以被使用无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的单个元素或组件上。进入或离开可以由以下的条件之一触发 由 v-if 所触发的切换 由 v-show 所触发的切换 由特殊元素 component 切换的动态组件 组件根节点
当一个 Transition 组件中的元素被插入或移除时会发生下面这些事情
Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是则一些 CSS 过渡类名 class 会在适当的时机被添加和移除。如果有作为监听器的 JavaScript 钩子这些钩子函数会在适当时机被调用。如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
基于 CSS 的过渡效果
CSS 过渡类名 class
在进入/离开的过渡中一共有 6 个应用于进入与离开过渡效果的 CSS class。
v-enter定义进入过渡的开始状态。在元素被插入之前生效在元素被插入之后的下一帧移除。v-enter-active定义进入过渡生效时的状态。在整个进入过渡的阶段中应用在元素被插入之前生效在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间延迟和曲线函数。v-enter-to定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除)在过渡/动画完成之后移除。v-leave定义离开过渡的开始状态。在离开过渡被触发时立刻生效下一帧被移除。v-leave-active定义离开过渡生效时的状态。在整个离开过渡的阶段中应用在离开过渡被触发时立刻生效在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间延迟和曲线函数。v-leave-to定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除)在过渡/动画完成之后移除。 对于进入动画
.v-enter.v-enter-to.v-enter-active定义过渡的开始状态定义过渡的结束状态定义过渡生效时的状插入之前生效插入之后下—帧生效同时v-enter被移除动画整个过程生效下—帧被移除动画完成之后移除动画整个过程生效
对于离开动画
.v-leave.v-leave-to.v-leave-active定义过渡的开始状态定义过渡的结束状态定义过渡生效时的状态离开过渡被触发时立即生效触发之后下一帧生效与此同时v-leave被删除整个离开过渡的阶段中应用下—帧被移除动画完成之后移除整个离开过渡的阶段中应用
为过渡效果命名
我们可以给 Transition 组件传一个 name prop 来声明一个过渡效果名
Transition namefade...
/Transition对于这些在过渡中切换的类名来说如果你使用一个没有名字的 transition则 v- 是这些类名的默认前缀。
如果你使用了一个有名字的过渡效果 transition namefade对它起作用的过渡 class 会以其名字而不是 v 作为前缀。比如上方例子中被应用的 class v-enter-active 会替换为 fade-enter-active 。
这个“fade”过渡的 class 应该是这样
.fade-enter-active,
.fade-leave-active {transition: opacity 0.5s ease;
}.fade-enter,
.fade-leave-to {opacity: 0;
}CSS 过渡 transition
Transition 一般都会搭配原生 CSS 过渡一起使用配和 transition CSS 属性是使我们可以一次定义一个过渡的各个方面包括需要执行动画的属性、持续时间和速度曲线。
实例1
使用按钮控制标签的显示隐藏
div idappbutton clickshow !showshow:{{show}}/buttontransition namefadep v-showshowhello vue!/p/transition/divscriptnew Vue({el: #app,data: {show: true,},});
/script添加进入和离开的动画
/* 离开的动画 */
/* 1、离开的开始状态 opacity为1是标签的默认值只要是默认值可以省略 */
.fade-leave{opacity: 1;
}
/* 2、离开的结束状态 opacity为0 */
.fade-leave-to{opacity: 0;
}
/* 3、离开的整个过程添加过渡属性 transition */
.fade-leave-active{transition: opacity 10s linear;
}
/* 进入的动画 */
/* 1、进入的开始状态 opacity为0 */
.fade-enter{opacity: 0;
}
/* 2、进入的结束状态 opacity为1也是默认值也可以省略 */
.fade-enter-to{opacity: 1;
}
/* 3、进入的整个过程添加过渡属性 transition */
.fade-enter-active{transition: opacity 10s linear;
}省略默认样式合并相同样式
/* 把默认的样式省略再把相同的样式属性的类名合并 */
.fade-leave-to,
.fade-enter {opacity: 0;
}.fade-leave-active,
.fade-enter-active {transition: opacity 3s linear;
}实例2
下面是一个更高级的例子它使用了不同的持续时间和速度曲线来过渡多个属性
Transition nameslide-fadep v-ifshowhello/p
/Transition/*进入和离开动画可以使用不同持续时间和速度曲线。
*/
.slide-fade-enter-active {transition: all 0.3s ease-out;
}.slide-fade-leave-active {transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}.slide-fade-enter,
.slide-fade-leave-to {transform: translateX(20px);opacity: 0;
}CSS 动画
原生 CSS 动画和 CSS transition 的应用方式基本上是相同的只有一点不同那就是 *-enter 不是在元素插入后立即移除而是在一个 animationend 事件触发时被移除。
对于大多数的 CSS 动画我们可以简单地在 *-enter-active 和 *-leave-active class 下声明它们。下面是一个示例
使用按钮控制标签的显示和隐藏
div idappbutton clickshow !showshow:{{show}}/buttontransition namebouncep v-showshow refp动画/p/transition
/div设置关键帧动画只需要设置leave-active和enter-active样式不需要设置leave和leave-to以及enter和enter-to
/*元素出来时的动画*/
.bounce-enter-active {animation: bounce-in 0.5s;
}
/*元素离开时的动画*/
.bounce-leave-active {animation: bounce-in 0.5s reverse;
}
keyframes bounce-in {0% {transform: scale(0);}50% {transform: scale(1.25);}100% {transform: scale(1);}
}自定义过渡的类名
你也可以向 Transition 传递以下的 props 来指定自定义的过渡 class
enter-classenter-active-classenter-to-classleave-classleave-active-classleave-to-class
你传入的这些 class 他们的优先级高于普通的类名会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用比如 Animate.css
link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.csstransition namemoveleave-active-classanimate__animated animate__bounceOutenter-active-classanimate__animated animate__bounceIn
div classbox v-showshow自定义动画名/div
/transition
同时使用 transition 和 animation
Vue 需要附加事件监听器以便知道过渡何时结束。可以是 transitionend 或 animationend这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一Vue 可以自动探测到正确的类型。
然而在某些场景中你或许想要在同一个元素上同时使用它们两个。举例来说Vue 触发了一个 CSS 动画同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type prop 来声明告诉 Vue 需要关心哪种类型传入的值是 animation 或 transition
Transition typeanimation.../Transition深层级过渡与显式过渡时长
尽管过渡 class 仅能应用在 Transition 的直接子元素上我们还是可以使用深层级的 CSS 选择器在深层级的元素上触发过渡效果。
Transition namenesteddiv v-ifshow classouterdiv classinnerHello/div/div
/Transition/* 应用于嵌套元素的规则 */
.nested-enter-active .inner,
.nested-leave-active .inner {transition: all 0.3s ease-in-out;
}.nested-enter-from .inner,
.nested-leave-to .inner {transform: translateX(30px);opacity: 0;
}/* ... 省略了其他必要的 CSS */我们甚至可以在深层元素上添加一个过渡延迟从而创建一个带渐进延迟的动画序列
/* 延迟嵌套元素的进入以获得交错效果 */
.nested-enter-active .inner {transition-delay: 0.25s;
}然而这会带来一个小问题。默认情况下Transition 组件会通过监听过渡根元素上的第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中期望的行为应该是等待所有内部元素的过渡完成。
在这种情况下你可以通过向 Transition 组件传入 duration prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间
Transition :duration550.../Transition如果有必要的话你也可以用对象的形式传入分开指定进入和离开所需的时间
Transition :duration{ enter: 500, leave: 800 }.../Transition性能考量
你可能注意到我们上面例子中展示的动画所用到的 CSS 属性大多是 transform 和 opacity 之类的。用这些属性制作动画非常高效因为
他们在动画过程中不会影响到 DOM 结构因此不会每一帧都触发昂贵的 CSS 布局重新计算。大多数的现代浏览器都可以在执行 transform 动画时利用 GPU 进行硬件加速。
相比之下像 height 或者 margin 这样的属性会触发 CSS 布局变动因此执行它们的动画效果更昂贵需要谨慎使用。我们可以在 CSS-Triggers 这类的网站查询哪些属性会在执行动画时触发 CSS 布局变动。
JavaScript 动画
你可以通过监听 Transition 组件事件的方式在过渡过程中挂上钩子函数
transition before-enteronBeforeEnterenteronEnterafter-enteronAfterEnterenter-cancelledonEnterCancelledbefore-leaveonBeforeLeaveleaveonLeaveafter-leaveonAfterLeaveleave-cancelledonLeaveCancelled
!-- ... --
/transition// ...
methods: {// 在元素被插入到 DOM 之前被调用// 用这个来设置元素的 enter-from 状态onBeforeEnter(el) {},// 在元素被插入到 DOM 之后的下一帧被调用// 用这个来开始进入动画// el是要做动画的元素对象// done是一个函数是动画结束时的回调函数onEnter(el, done) {// 调用回调函数 done 表示过渡结束// 如果与 CSS 结合使用则这个回调是可选参数done()},// 当进入过渡完成时调用。onAfterEnter(el) {},onEnterCancelled(el) {},// 在 leave 钩子之前调用// 大多数时候你应该只会用到 leave 钩子onBeforeLeave(el) {},// 在离开过渡开始时调用// 用这个来开始离开动画onLeave(el, done) {// 调用回调函数 done 表示过渡结束// 如果与 CSS 结合使用则这个回调是可选参数done()},// 在离开过渡完成、// 且元素已从 DOM 中移除时调用onAfterLeave(el) {},// 仅在 v-show 过渡中可用onLeaveCancelled(el) {}}这些钩子可以与 CSS 过渡或动画结合使用也可以单独使用。
在使用仅由 JavaScript 执行的动画时最好是添加一个 :cssfalse prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外还可以防止 CSS 规则意外地干扰过渡效果。
Transition...:cssfalse
...
/Transition在有了 :cssfalse 后我们就自己全权负责控制什么时候过渡结束了。**这种情况下对于 enter 和 leave 钩子来说必须使用 done进行回调。**否则钩子将被同步调用过渡将立即完成。
动画实例
script srchttps://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js/script
stylep {width: 100px;height: 100px;margin-left: 100px;margin-top: 100px;background-color: red;position: relative;}
/stylediv idappbutton clickshow !showtoggle:{{show}}/buttontransition before-enterbeforeEnter enterenter before-leavebeforeLeave leaveleave v-bind:cssfalsep v-showshowhello/p/transition
/divscriptvar app new Vue({el: #app,data: {show: true,},methods: {// 离开的动画beforeLeave(el) {console.log(离开之前);$(el).css({ transform: scale(0.8) });},leave(el, done) {console.log(正在离开);$(el).animate({ left: 300}, 200, function () {$(this).css({ top: -200, left: 0 });done();});},// 进入的动画beforeEnter(el) {console.log(进入之前);$(el).css({ transform: scale(1) });},enter(el, done) {console.log(进入中);$(el).animate({ top: 0 }, 200, function () {done();});},},});
/script可复用过渡效果
得益于 Vue 的组件系统过渡效果是可以被封装复用的。要创建一个可被复用的过渡我们需要为 Transition 组件创建一个包装组件并向内传入插槽内容
!-- MyTransition.vue --
script
// JavaScript 钩子逻辑...
/scripttemplate!-- 包装内置的 Transition 组件 --Transitionnamemy-transitionenteronEnterleaveonLeaveslot/slot !-- 向内传递插槽内容 --/Transition
/templatestyle
/*必要的 CSS...注意避免在这里使用 style scoped因为那不会应用到插槽内容上
*/
/style现在 MyTransition 可以在导入后像内置组件那样使用了
MyTransitiondiv v-ifshowHello/div
/MyTransition出现时过渡
如果你想在某个节点初次渲染时应用一个过渡效果你可以添加 appear prop
Transition appear...
/Transition这里默认和进入/离开过渡一样同样也可以自定义 CSS 类名。
transitionappearappear-classcustom-appear-classappear-to-classcustom-appear-to-classappear-active-classcustom-appear-active-class
!-- ... --
/transition自定义 JavaScript 钩子
transitionappearv-on:before-appearcustomBeforeAppearHookv-on:appearcustomAppearHookv-on:after-appearcustomAfterAppearHookv-on:appear-cancelledcustomAppearCancelledHook
!-- ... --
/transition在上面的例子中无论是 appear attribute 还是 v-on:appear 钩子都会生成初始渲染过渡。
元素间过渡
除了通过 v-if / v-show 切换一个元素我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换只要确保任一时刻只会有一个元素被渲染即可
div idappdiv classbtn-containertransition nameslide-upbutton v-ifshow keysave clickshow!showSave/buttonbutton v-else keyedit clickshow!showEdit/button/transition/div
/divscriptnew Vue({el: #app,data: {show: true,},});
/script可以这样使用但是有一点需要注意
当有相同标签名的元素切换时需要通过 key attribute 设置唯一的值来标记以让 Vue 区分它们否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要给在 transition 组件中的多个元素设置 key 是一个更好的实践。
.btn-container {margin-top: 30px;position: relative;
}/*
在“Save”按钮和“Edit”按钮的过渡中两个按钮都被重绘了一个离开过渡的时候另一个开始进入过渡。
这是 transition 的默认行为 - 进入和离开同时发生。
需要给button添加定位否则二者同时存在时出现的布局问题。
*/
button {position: absolute;
}.slide-up-enter-active,
.slide-up-leave-active {transition: all .25s ease-out;
}.slide-up-enter {opacity: 0;transform: translateY(30px);
}
.slide-up-leave-to {opacity: 0;transform: translateY(-30px);
}过渡模式
在上前的例子中进入和离开的元素都是在同时开始动画的因此我们不得不将它们设为 position: absolute 以避免二者同时存在时出现的布局问题。
然而很多情况下同时生效的进入和离开的过渡可能并不符合需求。我们可能想要先执行离开动画然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的好在Vue 提供了过渡模式可以通过向 Transition 传入一个 mode prop 来实现这个行为
in-out新元素先进行过渡完成之后当前元素过渡离开。out-in当前元素先进行过渡完成之后新元素过渡进入。
Transition modeout-in...
/Transition将之前的例子改为 modeout-in 后是这样
div classbtn-containertransition nameslide-up modeout-inbutton v-ifshow keysave clickshow!showSave/buttonbutton v-else keyedit clickshow!showEdit/button/transition
/divin-out 模式不是经常用到但对于一些稍微不同的过渡效果还是有用的
div classbtn-containertransition nameslide-up modeout-inbutton v-ifshow keysave clickshow!showSave/buttonbutton v-else keyedit clickshow!showEdit/button/transition
/div组件间过渡
Transition 也可以作用于动态组件之间的切换多个组件的过渡简单很多 - 我们不需要使用 key
div idappbutton clickview viewv-a ? v-b : v-a view{{view}}/buttontransition namefade modeout-incomponent :isview/component/transition
/divscriptnew Vue({el: #app,data () {return {view: v-a}},components: {v-a: {template: divComponent A/div},v-b: {template: divComponent B/div}}});
/scriptstyle.fade-enter-active,.fade-leave-active {transition: opacity .3s ease;}.fade-enter,.fade-leave-to {opacity: 0;}
/style动态过渡
Transition 的 props (比如 name) 也可以是动态的这让我们可以根据状态变化动态地应用不同类型的过渡
Transition :nametransitionName!-- ... --
/Transition这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class然后在它们之间动态切换。
你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。最后创建动态过渡的终极方式还是创建可复用的过渡组件并让这些组件根据动态的 props 来改变过渡的效果。掌握了这些技巧后就真的只有你想不到没有做不到的了。
TransitionGroup 组件
TransitionGroup 是一个内置组件用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
和 Transition 的区别
Transition可以实现的过渡效果
单个节点同一时间渲染多个节点中的一个
那么怎么同时渲染整个列表比如使用 v-for在这种场景中使用 transition-group 组件。
TransitionGroup 支持和 Transition 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器但有以下几点区别
不同于 transition它会以一个真实元素呈现默认为一个 span。但你可以通过传入 tag prop 来更换为其他元素作为容器元素来渲染。过渡模式在这里不可用因为我们不再是在互斥的元素之间进行切换。列表中的每个元素都必须有一个独一无二的 key attribute。CSS 过渡 class 会被应用在列表内的元素上而不是容器元素上。
列表的进入 / 离开动画
这里是 TransitionGroup 对一个 v-for 列表添加进入 / 离开动画的示例
script srchttps://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js/scriptdiv idappbutton clickinsert添加/buttonbutton clickremove移除/buttonbutton clickreset重置/buttonbutton clickshuffle随机排序/buttontransition-group tagul namelist classlistli v-foritem in items classitem :keyitem{{ item }}/li/transition-group
/div
scriptnew Vue({el: #app,data: {items: [1, 2, 3, 4, 5, 6],nextNum: 10,},methods: {randomIndex: function () {return Math.floor(Math.random() * this.items.length);},insert() {this.items.splice(this.randomIndex(), 0, this.nextNum);},remove(item) {this.items.splice(this.randomIndex(), 1);},reset() {this.items [1, 2, 3, 4, 5, 6];},shuffle() {this.items _.shuffle(this.items);console.log(this.items);},},});
/script.list-enter-active,
.list-leave-active {transition: all 0.5s ease;
}
.list-enter,
.list-leave-to {opacity: 0;transform: translateX(30px);
}这个例子有个问题当添加和移除元素的时候周围的元素会瞬间移动到他们的新布局的位置而不是平滑的过渡我们下面会解决这个问题。
移动动画
上面的示例有一些明显的缺陷当某一项被插入或移除时它周围的元素会立即发生“跳跃”而不是平稳地移动。我们可以通过添加一些额外的 CSS 规则来解决这个问题这需要用到 v-move class。
transition-group 组件还有一个特殊之处。不仅可以进入和离开动画还可以改变定位。要使用这个新功能只需了解新增的 v-move class它会在元素的改变定位的过程中应用。像之前的类名一样可以通过 name attribute 来自定义前缀也可以通过 move-class prop手动设置。
v-move 对于设置过渡的切换时机和过渡曲线非常有用你会看到如下的例子
.list-enter-active,
.list-leave-active {transition: all .5s ease;
}
.list-enter,
.list-leave-to {opacity: 0;transform: translateX(30px);
}
/* 对移动中的元素应用的过渡 */
.list-move {transition: all 0.5s ease;
}
/* 确保将离开的元素从布局流中删除以便能够正确地计算移动的动画。 */
.list-leave-active {position: absolute;
}现在它看起来好多了甚至对整个列表执行洗牌的动画也都非常流畅。
这个看起来很神奇内部的实现Vue 使用了一个叫 FLIP 简单的动画队列使用 transforms 将元素从之前的位置平滑过渡新的位置。 需要注意的是使用 FLIP 过渡的元素不能设置为 display: inline 。作为替代方案可以设置为 display: inline-block 或者放置于 flex 中。 动画技巧
Vue 提供了 Transition 和 TransitionGroup 组件来处理元素进入、离开和列表顺序变化的过渡效果。但除此之外还有许多其他制作网页动画的方式在 Vue 应用中也适用。这里我们会探讨一些额外的技巧。
基于 CSS class 的动画
对于那些不是正在进入或离开 DOM 的元素我们可以通过给它们动态添加 CSS class 来触发动画
div idappdiv :class{ shake: disabled }button clickwarnDisabledClick me/buttonspan v-ifdisabledThis feature is disabled!/span/div
/div
scriptnew Vue({el: #app,data: {disabled: false,},methods: {warnDisabled() {this.disabled true;setTimeout(() {this.disabled false;}, 1500);},},});
/scriptstyle.shake {animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;transform: translate3d(0, 0, 0);}keyframes shake {10%,90% {transform: translate3d(-1px, 0, 0);}20%,80% {transform: translate3d(2px, 0, 0);}30%,50%,70% {transform: translate3d(-4px, 0, 0);}40%,60% {transform: translate3d(4px, 0, 0);}}
/style状态驱动的动画
有些过渡效果可以通过动态插值来实现比如在交互时动态地给元素绑定样式。看下面这个例子
style.movearea {border-radius: 10px;padding: 10px;cursor: pointer;transition: 0.3s background-color ease;}
/stylediv idappdiv mousemoveonMousemove :style{ backgroundColor: hsl(${x}, 80%, 50%) } classmoveareap移动你的鼠标穿过这个div.../ppx: {{ x }}/p/div
/divscriptnew Vue({el: #app,data: {x: 0,},methods: {onMousemove(e) {this.x e.clientX;},},});
/script除了颜色外你还可以使用样式绑定 CSS transform、宽度或高度。你甚至可以通过运用弹性物理模拟为 SVG 添加动画毕竟它们也只是 attribute 的数据绑定
SVG动画
基于侦听器的动画
通过发挥一些创意我们可以基于一些数字状态配合侦听器给任何东西加上动画。例如我们可以将数字本身变成动画
script srchttps://cdn.bootcdn.net/ajax/libs/gsap/3.11.3/gsap.min.js/scriptstyle.big-number {font-weight: bold;font-size: 2em;}
/stylediv idappType a number: input v-model.numbernumber /p{{ tweened.toFixed(0) }}/p
/divscriptnew Vue({el: #app,data: {number: 0,tweened: 0,},watch: {number(n) {gsap.to(this, { duration: 0.5, tweened: Number(n) || 0 });},},});
/script混入 mixin
概念
混入 (mixin) 提供了一种非常灵活的方式来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时所有混入对象的选项将被“混合”进入该组件本身的选项。
可以把混入理解为将组件的公共逻辑或者配置提取出来哪个组件需要用到时直接将提取的这部分混入到组件内部即可。这样既可以减少代码冗余度也可以让后期维护起来更加容易。
这里需要注意的是提取的是逻辑或配置而不是HTML代码和CSS代码。mixin就是组件中的组件组件配置的一部分Vue组件化让我们的代码复用性更高那么组件与组件之间还有重复部分我们使用Mixin在抽离一遍。
Mixin和Vuex的区别
上面一点说Mixin就是一个抽离公共部分的作用。在Vue中Vuex状态管理似乎也是做的这一件事它也是将组件之间可能共享的数据抽离出来。两者看似一样实则还是有细微的区别区别如下
Vuex公共状态管理如果在一个组件中更改了Vuex中的某个数据那么其它所有引用了Vuex中该数据的组件也会跟着变化。Mixin中的数据和方法都是独立的组件之间使用后是互相不影响的。
注册混入
混入分为全局混入和局部混入 注册全局混入Vue.directive(混入对象) 注册局部混入new Vue{ mixins:[混入对象1, 混入对象2, ...], }
混入对象
混入对象和Vue的实例一样包含实例选项data、methods、computed、声明周期钩子等这些选项将会被合并到最终的选项中。也就是说如果你的混入包含一个 created 钩子而创建组件本身也有一个那么两个函数都会被调用。
// 定义混入对象
const myMixin1 {created: function () {console.log(hello from myMixin1!)},
}const myMixin2 {created: function () {console.log(hello from myMixin2!)},
}// 重要
// 一个组件中改动了mixin对象中的数据另一个引用了mixin的组件不会受影响
// 因为不同组件中的mixin是相互独立的// 使用全局混入
Vue.mixin(myMixin1);
Vue.mixin(myMixin2);// 使用局部混入
Vue.component(m-v, {template: pm-v/p,mixins:[myMixin1, myMixin2],
});全局混入的注意点
请谨慎使用全局混入一旦使用全局混入它将影响每一个之后创建的 Vue 实例(包括第三方组件)。大多数情况下只应当应用于自定义选项就像下面示例一样。推荐将其作为插件发布以避免重复应用混入。
// 为自定义的选项 myOption 注入一个处理器。
Vue.mixin({created: function () {var myOption this.$options.myOptionif (myOption) {console.log(myOption)}}
})new Vue({myOption: hello!
})选项合并
当把混入对象添加到组件中根据混入规则添加混入的数据当组件和混入对象含有同名选项时这些选项将以恰当的方式进行“合并” data数据对象在内部会进行递归合并发生键名冲突时组件数据覆盖混入对象的数据。 生命周期钩子同名钩子函数将合并为一个数组因此都将被调用。先执行混入对象的钩子函数再执行组件自身钩子函数。 值为对象的选项例如 methods、components 和 directives将被合并为同一个对象。两个对象键名冲突时组件的键值覆盖混入对象的键值最后使用的是组件的方法等。
混入的规则可以总结为两点 组件的选项是对象类型data、computed、methods、watch、components如果混入对象中的属性或者方法名与组件内部的重名组件的数据覆盖混入对象的数据。 组件实例的选项是函数类型生命周期函数同名钩子函数将合并为一个数组因此都将被调用。另外先执行混入对象的钩子函数再执行组件自身钩子函数。
mixin的优缺点
从上面的例子看来使用mixin的好处多多但是凡是都有两面性 优点 提高代码复用性 无需传递状态 维护方便只需要修改一个地方即可 缺点 命名冲突 滥用的话后期很难维护 不好追溯源排查问题稍显麻烦 不能轻易的重复代码
组合式API 和 Mixin 的对比
在 Vue 2 中mixins 是创建可重用组件逻辑的主要方式。尽管在 Vue 3 中保留了 mixins 支持但对于组件间的逻辑复用Composition API 是现在更推荐的方式。
让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板
不清晰的数据来源当使用了多个 mixin 时实例上的数据属性来自哪个 mixin 变得不清晰这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref 解构模式的理由让属性的来源在消费组件时一目了然。命名空间冲突多个来自不同作者的 mixin 可能会注册相同的属性名造成命名冲突。若使用组合式函数你可以通过在解构变量时对变量进行重命名来避免相同的键名。隐式的跨 mixin 交流多个 mixin 需要依赖共享的属性名来进行相互作用这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入像普通函数那样。
基于上述理由我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户。
插件
插件的功能
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种
添加全局方法或者 property。如vue-custom-element添加全局资源指令/过滤器/过渡等。如 vue-touch通过全局混入来添加一些组件选项。如 vue-router添加 Vue 实例方法通过把它们添加到 Vue.prototype 上实现。一个库提供自己的 API同时提供上面提到的一个或多个功能。如 vue-router
使用插件
通过全局方法 Vue.use() 使用插件。需要在 new Vue() 启动应用之前完成Vue.use()的调用
// 创建插件
const MyPlugin {install (Vue, options) {console.log(install);}
}// 使用插件插件的install函数会自动调用 MyPlugin.install(Vue)
Vue.use(MyPlugin)new Vue({// ...组件选项
})也可以传入一个可选的选项对象
Vue.use(MyPlugin, { someOption: true })Vue.use 会自动阻止多次注册相同插件届时即使多次调用也只会注册一次该插件。
比如路由插件在vue脚手架中的使用
import Vue from vue;
import VueRouter from vue-router;Vue.use(VueRouter);// 使用路由插件开发插件
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器第二个参数是一个可选的选项对象
const MyPlugin {install (Vue, options) {// 1. 添加全局过滤器Vue.filter(...)// 2. 添加全局指令Vue.directive(...)// 3. 添加全局混入Vue.mixin(...)// 4. 添加实例方法或属性Vue.prototype.$myMethod function (methodOptions) {...}Vue.prototype.$myProperty xxxx// 5.添加全局方法或属性 Vue.myGlobalMethod function () {}}
}export default MyPlugin;自定义指令
指令介绍
除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外Vue 还允许你注册自定义的指令 (Custom Directives)。
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
下面是一个自定义指令的例子当一个 input 元素被 Vue 插入到 DOM 中后它会被自动聚焦
Vue.directive(focus, {inserted (el) {el.focus();}
});input v-focus假设你还未点击页面中的其他地方那么上面这个 input 元素应该会被自动聚焦。该指令比 autofocus attribute 更有用因为它不仅仅可以在页面加载完成后生效还可以在 Vue 动态插入元素后生效。
和组件类似自定义指令在模板中使用前必须先注册。
注册指令
注册自定义指令分为全局指令和局部指令 注册全局指令Vue.directive(指令名, 回调函数或者配置对象) Vue.directive(color, {inserted (el, binding) {el.style.color binding.value}
});注册局部指令new Vue{directives:{指令名: 回调函数或者配置对象 }} new Vue({directives: {focus: {inserted: function (el) {el.focus()}}}
});指令名 指令注册时不加v-但使用时要加v- 指令名如果是多个单词要使用kebab-case命名方式不要用camelCase命名。
指令钩子函数与参数
钩子函数
一个指令的定义对象可以提供几种钩子函数 (都是可选的)
Vue.directive(directive-name, {// 只调用一次指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。bind(el, binding, vnode, prevVnode) {},// 被绑定元素插入父节点时调用 (仅保证父节点存在但不一定已被插入文档中)。inserted(el, binding, vnode, prevVnode) {},// 所在组件的 VNode 更新时调用但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变也可能没有。// 但是你可以通过比较更新前后的值来忽略不必要的模板更新。update(el, binding, vnode, prevVnode) {},// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。componentUpdated(el, binding, vnode, prevVnode) {},// 只调用一次指令与元素解绑时调用。unbind(el, binding, vnode, prevVnode) {},
})钩子函数参数
指令钩子函数会被传入以下参数
el指令所绑定的元素可以用来直接操作 DOM。binding一个对象包含以下 property name指令名不包括 v- 前缀。value指令的绑定值例如v-my-directive1 1 中绑定值为 2。oldValue指令绑定的前一个值仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。expression字符串形式的指令表达式。例如 v-my-directive1 1 中表达式为 1 1。arg传给指令的参数可选。例如 v-my-directive:foo 中参数为 foo。modifiers一个包含修饰符的对象。例如v-my-directive.foo.bar 中修饰符对象为 { foo: true, bar: true }。 vnodeVue 编译生成的虚拟节点。prevVnode上一个虚拟节点仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外其它参数都应该是只读的切勿进行修改。如果需要在钩子之间共享数据建议通过元素的 dataset 来进行。
举例来说像下面这样使用指令
div v-example:foo.barbazbinding 参数会是一个这样的对象
{arg: foo,modifiers: { bar: true },value: /* baz 的值 */,oldValue: /* 上一次更新时 baz 的值 */
}和内置指令类似自定义指令的参数也可以是动态的。举例来说
div v-example:[arg]value/div这里指令的参数会基于组件的 arg 数据属性响应式地更新。
注册指令的两种方式
配置对象
Vue.directive(directive-name, {bind(el, binding, vnode, prevVnode) {},
})函数简写
在很多时候你可能想在 bind 和 update 时触发相同行为除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令如下所示
Vue.directive(color, function (el, binding) {el.style.color binding.value
})注意函数简写调用的时机有两个 指令与元素成功绑定时。 指令所在的模板被重新解析时。
对象字面量
如果你的指令需要多个值你可以向它传递一个 JavaScript 对象字面量。别忘了指令也可以接收任何合法的 JavaScript 表达式。
div v-demo{ color: white, text: hello! }/divVue.directive(demo, function (el, binding) {console.log(binding.value.color) // whiteconsole.log(binding.value.text) // hello!
})在组件上使用
当在组件上使用自定义指令时它会始终应用于组件的根节点和透传 attributes类似。
MyComponent v-demotest /!-- MyComponent 的模板 --div !-- v-demo 指令会被应用在此处 --spanMy component content/span
/div需要注意的是vue3的组件可能含有多个根节点。当应用到一个多根组件时指令将会被忽略且抛出一个警告。和 attribute 不同指令不能通过 v-bind$attrs 来传递给一个不同的元素。总的来说不推荐在组件上使用自定义指令。
指令函数中的this
不管是使用配置对象还是使用函数简写的形式注册指令指令的钩子函数中的this都是指向window的。因为在自定义指令中已经是需要用户操作DOMVue的数据都是通过指令传入指令的函数。
实例
问题 尝试使用函数和配置对象的两种方式注册以下指令 注册v-color指令改变文字的颜色默认为红色 Vue.directive(color, function (el, binding) {el.style.color binding.value || #f00;
});p v-colorred你好/p注册v-focus-value指令给输入框value传值并让输入框获取焦点 // v-focus-value 不适合使用函数简写的形式
// Vue.directive(focus-value, (el, binding) {
// el.focus();
// el.value binding.value
// });Vue.directive(focus-value, {bind (el, binding) {el.value binding.value},inserted (el, binding) {el.focus();},update (el, binding) {el.value binding.value}
});button clickn点击n加1{{n}} /button
input typetext v-focus-valuen渲染函数 render
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数它比模板更接近编译器。
在学习render函数之前先来了解一下虚拟 DOM的概念。
虚拟 DOM
你可能已经听说过“虚拟 DOM”的概念了Vue 的渲染系统正是基于这个概念构建的。
虚拟 DOM (Virtual DOM简称 VDOM) 是一种编程概念意为将目标所需的 UI 通过数据结构“虚拟”地表示出来保存在内存中然后将真实的 DOM 与之保持同步。这个概念是由 React 率先开拓随后在许多不同的框架中都有不同的实现当然也包括 Vue。
与其说虚拟 DOM 是一种具体的技术不如说是一种模式所以并没有一个标准的实现。我们可以用一个简单的例子来说明
const vnode {type: div,props: {id: hello},children: [/* 更多 vnode */]
}这里所说的 vnode 即一个纯 JavaScript 的对象 (一个“虚拟节点”)它代表着一个 div 元素。它包含我们创建实际元素所需的所有信息。它还包含更多的子节点这使它成为虚拟 DOM 树的根节点。
一个运行时渲染器将会遍历整个虚拟 DOM 树并据此构建真实的 DOM 树。这个过程被称为挂载 (mount)。
如果我们有两份虚拟 DOM 树渲染器将会有比较地遍历它们找出它们之间的区别并应用这其中的变化到真实的 DOM 上。这个过程被称为更新 (patch)又被称为“比对”(diffing) 或“协调”(reconciliation)。
虚拟 DOM 带来的主要收益是它让开发者能够灵活、声明式地创建、检查和组合所需 UI 的结构同时只需把具体的 DOM 操作留给渲染器去处理。
渲染管线
从高层面的视角看Vue 组件挂载时会发生如下几件事
编译Vue 模板被编译为渲染函数即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成也可以通过使用运行时编译器即时完成。挂载运行时渲染器调用渲染函数遍历返回的虚拟 DOM 树并基于它创建实际的 DOM 节点。这一步会作为响应式副作用执行因此它会追踪其中所用到的所有响应式依赖。更新当一个依赖发生变化后副作用会重新运行这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树将它与旧树进行比较然后将必要的更新应用到真实 DOM 上去。 模板 vs. 渲染函数
Vue 模板会被预编译成虚拟 DOM 渲染函数。Vue 也提供了 API 使我们可以不使用模板编译直接手写渲染函数。在处理高度动态的逻辑时渲染函数相比于模板更加灵活因为你可以完全地使用 JavaScript 来构造你想要的 vnode。
那么为什么 Vue 默认推荐使用模板呢有以下几点原因
模板更贴近实际的 HTML。这使得我们能够更方便地重用一些已有的 HTML 代码片段能够带来更好的可访问性体验、能更方便地使用 CSS 应用样式并且更容易使设计师理解和修改。由于其确定的语法更容易对模板做静态分析。这使得 Vue 的模板编译器能够应用许多编译时优化来提升虚拟 DOM 的性能表现 (下面我们将展开讨论)。
在实践中模板对大多数的应用场景都是够用且高效的。渲染函数一般只会在需要处理高度动态渲染逻辑的可重用组件中使用。
创建虚拟DOM
Vue组件实例的选项中提供了render函数来创建虚拟DOM
render函数用法render(createVnode){ return createVnode(); }
createVnode是一个方法用来创建虚拟DOM通常把createVnode简写为hh() 是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。 Vue 选项中的 render 函数若存在则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。 h() 函数返回值是虚拟DOM对象
h 到底会返回什么呢其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription**因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点包括及其子节点的描述信息。**我们把这样的节点描述为“虚拟节点 (virtual node)”也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
render函数使用方式
// h函数返回值为虚拟DOM对象 {VNode}
h(// {String | Object | Function}// 一个 HTML 标签名、组件选项对象或者resolve 了上述任何一种的一个 async 函数。必填项。div,// {Object}// 一个与模板中 attribute 对应的数据对象。可选。{// (详情见下一节) 深入数据对象},// {String | Array}// 子级虚拟节点 (VNodes)由 createElement() 构建而成// 也可以使用字符串来生成“文本虚拟节点”。可选。[先写一些文字,h(h1, 一则头条),h(MyComponent, {props: {someProp: foobar}})]
);深入数据对象
有一点要注意正如 v-bind:class 和 v-bind:style 在模板语法中会被特别对待一样它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML attribute也允许绑定如 innerHTML 这样的 DOM property (这会覆盖 v-html 指令)。
{// 与 v-bind:class 的 API 相同接受一个字符串、对象或字符串和对象组成的数组class: {foo: true,bar: false},// 与 v-bind:style 的 API 相同接受一个字符串、对象或对象组成的数组style: {color: red,fontSize: 14px},// 普通的 HTML attributeattrs: {id: foo},// 组件 propprops: {myProp: bar},// DOM propertydomProps: {innerHTML: baz},// 事件监听器在 on 内// 但不再支持如 v-on:keyup.enter 这样的修饰器。// 需要在处理函数中手动检查 keyCode。on: {click: this.clickHandler},// 仅用于组件用于监听原生事件而不是组件内部使用// vm.$emit 触发的事件。nativeOn: {click: this.nativeClickHandler},// 自定义指令。注意你无法对 binding 中的 oldValue// 赋值因为 Vue 已经自动为你进行了同步。directives: [{name: my-custom-directive,value: 2,expression: 1 1,arg: foo,modifiers: {bar: true}}],// 作用域插槽的格式为// { name: props VNode | ArrayVNode }scopedSlots: {default: props h(span, props.text)},// 如果组件是其它组件的子组件需为插槽指定名称slot: name-of-slot,// 其它特殊顶层 propertykey: myKey,ref: myRef,// 如果你在渲染函数中给多个元素都应用了相同的 ref 名// 那么 $refs.myRef 会变成一个数组。refInFor: true
}使用render函数创建组件
访问this render() 函数可以访问同一个 this 组件实例。 Vue.component(m-c, {render(h) {return h(div, this.msg);},data() {return {msg: hello world!,};},
});单个根标签 虚拟DOM依然是只能有一个根标签 以下写法会报错 [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node. Vue.component(m-c, {render(h) {return [h(div, div1), h(div, div2)];},
});VNode 必须唯一
组件树中的所有 VNode 必须是唯一的。这意味着下面的渲染函数是不合法的
render: function (h) {var p h(p, hi)return h(div, [// 错误 - 重复的 VNodep, p])
}如果你真的非常想在页面上渲染多个重复的元素或者组件你可以使用一个工厂函数来做这件事。比如下面的这个渲染函数就可以完美渲染出 6 个相同的段落
render: function (h) {return h(div,[1,2,3,4,5,6].map(function () {return h(p, hi)}))
}render函数的应用
标题组件
// m-h :level3论文/m-hVue.component(m-h, {render (h) {return h(h this.level, this.$slots.default);},props: {level: {type: Number,require: true,}},
});替代v-for 和 v-if
// m-v :items[选项1, 选项2, 选项3]/m-vVue.component(m-v, {props: [items],render (h) {if (this.items.length) {return h(ul, this.items.map(function (item) {return h(li, item)}))} else {return h(p, No items found.)}}
});自定义v-model
//
Vue.component(m-input, {props: [value],render (h) {var self thisreturn h(input, {domProps: {// 绑定input标签的value属性value: self.value},on: {// 监听input标签的input事件input: function (event) {// 发射input事件self.$emit(input, event.target.value)}}})}
});MVVM
MVVM 模型
MVVM -Model View ViewModel M模型(Model) 对应 data 中的数据 V视图(View) 模板 VM视图模型(ViewModel) Vue 实例对象 最核心的就是 ViewModel 。ViewModel 包含 DOM Listeners 和 Data Bindings。
Data Bindings 用于将数据绑定到 View 上显示DOM Listeners 用于监听操作。
从 Model 到 View 的映射也就是 Data Bindings 。这样可以大量省略我们手动 update View 的代码和时间。从 View 到 Model 的事件监听也就是 DOM Listeners 。这样我们的 Model 就会随着 View 触发事件而改变。
在Vue中的mvvm:
data中所有的属性、computed的计算属性、methods中的方法最后都出现在了vm身上。vm身上所有的属性 及 Vue原型上所有属性在Vue模板{{}}中都可以直接使用。
div idapp{{num}}
/divscript
let vm new Vue({el:#app,data:{num: 10,}
});
/scriptMVVM思想有两个方向
一是将模型转换成视图即将后端传递的数据转换成看到的页面。实现方式是数据绑定。
二是将视图转换成模型即将看到的页面转换成后端的数据。实现的方式是DOM 事件监听。
这两个方向都实现的就称为数据的双向绑定。
MVC 和 MVVM 的区别(关系)
MVC - Model View Controller( controller: 控制器 )M 和 V 和 MVVM 中的 M 和 V 意思一样C 指页面业务逻辑。使用 MVC 的目的就是将 M 和 V 的代码分离但 MVC 是单向通信也就是将 Model 渲染到 View 上必须通过 Controller 来承上启下。
MVC 和 MVVM 的区别(关系)并不是 ViewModel 完全取代了 Controller 。
ViewModel 目的在于抽离 Controller 中的数据渲染功能而不是替代。其他操作业务等还是应该放在 Controller 中实现这样就实现了业务逻辑组件的复用。
常见关于Vue的面试题
什么是MVVM思想 MVVM -Model View ViewModel它包括 DOM Listenters 和 Data bindings前者实现了页面与数据的绑定当页面操作数据的时候 DOM 和 Model 也会发生相应的变化。后者实现了数据与页面的绑定当数据发生变化的时候会自动渲染页面。 MVVM相对于MVC的优势 MVVM 实现了数据与页面的双向绑定MVC 只实现了 Model 和 View 的单向绑定。MVVM 实现了页面业务逻辑和渲染之间的解耦也实现了数据与视图的解耦并且可以组件化开发。 VUE是如何体现MVVM思想的 胡子语法Mustache 语法 {{}} 长的比较像胡子命名为胡子语法实现了数据与视图的绑定。v-on 事件绑定通过事件操作数据时v-model 会发生相应的变化。 响应式原理
数据代理
数据代理通过一个对象代理对另一个对象中属性的操作读/写
let o1 {x: 100};
let o2 {y: 200};
Object.defineProperty(o2, x, {get(){return o1.x;},set(value){o1.x value;}
});o2对象代理了o1对象的属性x当修改o1.x会影响o2.x修改o2.x也会影响o1.x
模拟响应式的实现
响应式原理在改变数据的时候视图会跟着更新。
div idappbutton onclickaddClick(age, 1)点击改变age/buttonbutton onclickaddClick(weight, 2)点击增加weight属性/buttonp idname/pp idage/pp idweight/p
/divscript// 模拟 Vue 中的 datalet data {name: 张三,age: 12}// 更新视图function updateView (key, value) {document.getElementById(key).innerHTML key value}// 模拟Vue的响应式函数reactivefunction reactive (target, key, value) {Object.defineProperty(target, key, {get () {console.log(访问了${key}属性)return value},set (newValue) {console.log(将${key}由-${value}-设置成-${newValue})if (value ! newValue) {value newValue;updateView(key, newValue);}}})}// 模拟 Vue 实例代理data中的每一个属性后增加的属性不具有响应式Object.keys(data).forEach(key reactive(data, key, data[key]));updateView(name, data.name);updateView(age, data.age);// 模拟值的改变function addClick (key, value) {if (data[key]) {data[key] data[key] value;} else {data[key] value;}}/scriptVue给data里所有的属性加上setget这个过程就叫做响应式。
Vue中的数据代理
Vue中的数据代理通过vue实例来代理data对象中属性的操作读/写
Vue中数据代理的好处更加方便的操作data中的数据 响应式原理 vue在实例化时将data中的所有属性都通过Object.defineProperty添加到vue实例上 为每一个添加到vue实例上的属性都指定setter和getter在getter/setter内部去操作读/写data中对应的属性 每个vue实例都对应一个 watcher 实例它会在组件渲染的过程中把“接触”过的数据记录为依赖。之后当依赖属性的 setter 触发时会通知 watcher从而使页面中绑定这个属性的部分重新渲染。
总结
**第一步**组件初始化的时候先给每一个Data属性都注册gettersetter也就是reactive化。然后再new 一个自己的Watcher对象此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候就会需要用到data的属性值此时会触发getter函数将当前的Watcher函数注册进sub里。 **第二步**当data属性发生改变之后就会遍历sub里所有的watcher对象通知它们去重新渲染组件。 响应式属性
vue的响应式属性
当一个 Vue 实例被创建时它将 data 对象中的所有的属性加入到 Vue 的响应式系统中。当这些 属性的值发生改变时视图将会产生“响应”即匹配更新为新的值。
// 我们的数据对象
var data { a: 1 }// 该对象被加入到一个 Vue 实例中
var vm new Vue({data: data
})// 获得这个实例上的 property
// 返回源数据中对应的字段
vm.a data.a // true// 设置 property 也会影响到原始数据
vm.a 2
data.a // 2// ……反之亦然
data.a 3
vm.a // 3由于 Vue 会在初始化实例时对data中的属性执行 getter/setter 转化所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
当这些数据改变时视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 属性 才是响应式的。也就是说如果你添加一个新的 属性比如
var vm new Vue({data:{a:1,}
})// vm.a 是响应式的vm.b 2
// vm.b 是非响应式的那么对 b 的改动将不会触发任何视图的更新。
对于已经创建的实例 Vue 不允许动态添加根级响应式 property。如果你知道你会在晚些时候需要一个 property一开始你必须在初始化实例前在data中声明所有根级响应式属性并为这些属性设置初始值。比如
var vm new Vue({data:{// 声明 message 为一个空值字符串message: ,// 声明 items 为一个空数组items:[],// 声明 zhagnsan 为一个空对象zhagnsan:{}}
})
// 之后设置 message、tems、zhangsan 都是是响应式的
vm.message Hello!;
vm.items [1,2,3,4];
vm.zhangsan {name:张三, age:19};检测对象的属性的变化
给data中的对象添加属性Vue检测不到对象属性的变化比如
var vm new Vue({data:{user: {name: 张三},}
});vm.user.age 12;// age不是是响应式的使用上面的方式添加的age属性不是响应式的。
如果想要Vue检测到age属性可以使用下面的方法向嵌套对象添加响应式 属性
Vue.set(object, propertyName, value)vm.$set(object, propertyName, value)
Vue.set(vm.user, age, 12);
// 或者
vm.$set(vm.user,age,12);如果需要给user对象添加多个属性可以使用 Object.assign
vm.user Object.assign({}, vm.user, {weight:100, height:180});检测数组元素值的变化
检测数组
Vue 不能检测以下数组的变动
当你利用索引直接设置一个数组项时例如vm.items[indexOfItem] newValue当你修改数组的长度时例如vm.items.length newLength
var vm new Vue({data:{hobby: [游戏, 音乐, 运动],}
});vm.hobby[0] 学习;// 不是是响应式的检测数组的变化有两种方式 使用被 Vue 包裹的数组变更方法使用这些方法将会触发视图更新。这些被包裹过的方法包括push()、pop()、shift()、unshift()、splice()、sort()、reverse() 使用set函数改变数组元素Vue.set(array, index, item) 或 vm.$set(array, index, item)
为了解决第一类问题以上两种方式都可以实现
Vue.set(vm.hobby, 0, 学习); vm.$set(vm.hobby, 0, 学习);vm.hobby.splice(0, 1, 学习);为了解决第二类问题vm.items.length newLength你可以使用 splice
vm.items vm.items.splice(newLength);替换数组
变更方法顾名思义会变更调用了这些方法的原始数组。相比之下也有非变更方法例如 filter()、concat() 和 slice()。它们不会变更原始数组而总是返回一个新数组。当使用非变更方法时可以用新数组替换旧数组
this.hobby this.hobby.filter( item !item.includes(运动));