1.双向绑定和单向绑定
在我们的vue里面,双向绑定和单向绑定是不冲突;
Vue 是 如 何 是 进 行 双 向 绑 定 的 ? 其 实 并 不 是 通 过 defineProperty 这个 API;如下带代码同样的内容,第一种就是所谓的双向绑定,第二种是手写实现的双向绑定也是 v-model 最终编译出来的样子,所以 v-model 仅仅是一个语法糖而已。
<template> <div> <PersonalInfo v-model=""phoneInfo" :zip-code.sync="zipCode"/> <PersonalInfo : phone-info="phoneInfo" :zip-code=""zipCode" @change="val => (phoneInfo = val)" @update:zipCode="val =>(zipCode = val)"/> phaneInfo: {{phoneInfo }} <br/> zipcode: {{ zipCode }} </div> </template> <script> import PersonalInfo from "./PersonalInfo": export default { components: { PersonalInfo }, data() { return { phoneInfo:{ areaCode: "+86”",phone:"" , zipCode: "" }; }}; </script>
当然有时候组件可能需要多个属性的双向绑定则可以通过.sync 对其它属性做双向绑定,编译后就是@update 去改变值即可。 personalInfo 组件内部;
<script> export default { name:"PersonalInfo", model: { prop: "phoncInfo",//默认 value event:"change"//默认input }, props: { phoneInfo: 0bject, zipcode: String }, methods:{ handleAreaCodeChange(e) { this.$emit("change", { ...this.phoneInfo, areaCode: e.target.value }}; }, handlePhoneChange(e) { this.$emit("change",{ ...this.phoneInfo, phone: e.target.value }}; }, handleZipCodeChange(e){ this.$emit("update:zipcode",e.target.value); } } }; </script>
我们发现为什么绑定的名字不一样,其实是因为我们组件内部是用修改了其名字(props 和 event 那个)。最终语法糖的形式(v-model 对应前两个,.sync 对应后两个);
//语法糖 <PersonalInfo v-mode l=" phoneInfo" : zip-code.sync=" zipCode" /> <PersonalInfo :phone- info="phoneInfo" @change="val => (phoneInfo = val)" :zip-code=" zipCode" @update: zipCode="val => (zipCode = val)" />
2.理解虚拟 DOM 及 key 属性的作用
事件操作 DOM
但是事件太多的话:
Vue 的话是通过引入一个数据中间层,然后事件不再直接操作 DOM 而是改变数据再映射到 DOM 上。当然由于操作 DOM 非常耗费性能,所以这里是希望尽可能的复用之前的 DOM,于是引入虚拟 DOM。
查找两棵树的时间复杂度是 O(n3),很差,所以只是比较相同层级的 DOM 节点。
场景 2 比较到第二层节点的时候算法(同层节点比较,其实已经是比较好的了,虽然理想是直接移动 C 节点)是将 C 节点删除而不是移动,第三层的时候新建一个 C 节点,第四层新建 EF,所以说 CEF 都是不能复用的。
场景 4 要注意,B 是属于同一组件,而且理想状态下是直接移动 B1B2,但是这个时候其实是不知道我们要更新还是移动 B1B2 所以只是把 B1 更新成 B2,B2 更新成 B1,EF 就无法复用了,只能新建。所以我们可以加上 key 来优化(与场景 1 相同):
场景四:更新删除新建(没有key)
场景五:移动(有key)
场景六:插入(有key)
场景 6 也是需要注意,如果无 key 的话则可能是 B2 更新 B4,B3 更新成 B4,新建 B3,但是有的话就只需要插入 B4 就行。
3.如何触发组件的更新
任何直接更改DOM的行为都是在作死】
状态data VS属性props;
●状态是组件自身的数据;
●属性是来自父组件的数据;
●状态的改变末必会触发更新;
●属性的改变未必会触发更新。
<script> import PropsAndData from "./PropsAndData"; let name = "world"; export default { components: { PropsAndData }, data() { this.nane = name; return { info:{}, list: [] }; }, methods:{ handleNaneChange() { this.nane = "vue" + Date.now(); console.log("this.nane发生了变化,但是并没有触发子组件更新”,this.nane); }, handleInfoChange() { this.info.number = 1; console.log("this.info发生了变化,但是并没有触发子组件更新”,this.info); }, handleListChange() { this.list.push(1, 2, 3); console.log("this.list并没有发生变化,但是触发了子组件更新”,this.list); }} ); </script>
上图可以看到前两个方法点击了是改变数据但是不会触发组件的变化,因为 name 没有做响应式,而 info 下面的数据并没有做响应式,所以改变 info 下面的字段是没有用(除非是下面的字段有定义在 data 里面就是响应式了)。
这个时候 name 和 number 在 vue 实例化的时候就有响应式了。这个 list 要注意,其实我们
push 的话是没有改变 list 的(因为没有 this.list 等于改变后的数组),但是却能更新组件。
<template> <div> <p>props.info: {{ info }}</p> <p>props.name: {{ nane }}</p> <p>props.list: {{ list }}</p> <p>data.a:{{ a }}</p> <p> <button @click="handleBChange">change data. b</button> </p> </div> </template> <script> export default { name: "PropsAndData", props: { info: Object, nane: String, list: Array }, datal() { return a: "hello", b: "world" }; }, updated() { console.log("触发PropsAndData updated"); }, methods: { handleBChange() { this.b = "vue" + Date.now(); console.log("data.b发生了变化。但是并没有触发组件更新",this.b); } } }; </script>
触发点击事件的时候更改 b 但是组件并没有更新,那是因为组件没有用到 b,所以不会提示组件更新。
Vue 如何做响应式更新,哪些需要依赖收集哪些不需要。Vue实例化的时候会对 data 下面的数据做一个 getter 和 setter 的转化也就是对数据做一个中间的代理层,不管是做什么操作都会经过这个代理层再去进行相应的操作(可以在代理层做任何事情)。而组件在渲染的时候即 render,如果需要用到data 里面的一些数据则把这些数据放到 watcher 里面,没有用到就不会放进去(所以 b 没有用到,更新了不会去通知watcher 也不会去更新组件).
4.合理应用计算属性和侦听器
计算属性就是可以在里面写一些诸如计算的逻辑等等,同时如果数据没有变化它也不会触发是为缓存,当然这个数据必须是放到 data 里面也就是响应式数据而不能是传来的全局数据!!数据量巨大的时候可以很好的提高性能,毕竟变化时才会计算。
<div> <p>Reversed message1: "{{ reversedHessage1 }}"</p> <p>Reversed ressage2: "{{ reversedMessage2() }}"</p> <p>{{ now }}</p> <button @click="() => $forceUpdate()">forceUpdate</button> <br/> <input v-model="message"/> </div> </template> <script> export default { data() { return { message: "hello vue" }; }, computed: { // 计算属性的getter reversedMessage1: function() { console.log("执行reversedHessage1"); return this.message .split("") .reverse() .join(""); now: function() { return Date.now(); } }, methods: { reversedMessage2: function() { console.log("执行reversedMessage2"); return this.message .splt("") .reverse() . join(""); }}
对比上面的例子可以看到,如果值发生变化计算属性和方法都会调用,如果值没有变化但是强制刷新则方法会被调用.
侦听器就是用来监听数据变化的,可以看看某个数据是否改变。
export default { data: function() { return { a: 1, b:{c:2,d:3), e:{ f: { g: 4 } }, h: [] }; }, watch: { a: function(val, oldval) { this.b.c +=1; console.log("new: %s, old: %s", val, oldVal); }, "b.c": function(val, oldVal) { this.b.d += 1; console.log("new:%s,old: %s". val, oldVal); }, "b.d": function(val, oldVal) { this.e.f.g += 1; console.log("new:%s, old: %s", val, oldVal); }, e:{ handler: function(val, oldVal) { this.h. push(" "); console.log("new: %s,old: %s", val, oldVal); }, deep: true }, h(val, oldVal) console. Log("new: %s, old:%s" val, oldVal);
注意这里面是采用了深度监听(deep:true),不管是 f 还是 g 发生变化都会触发 e 的 handler,这里诸如 this.b.c 的值改变恰恰是为了触发下一个监听“b.c”。这里就是触发了一系
列的更新。虽然啥都能做但是最好还是不要操作 DOM。
注意两个例子,如果是侦听器的话两个都需要加逻辑执行,相对于计算属性是比较冗余,所以先计算属性,实在不行就采用比较底层的 watch(当然也不是一定要这样)。
5.生命周期的应用场景和函数式组件
每一个 vue 组件被创建的时候都会经过一系列的初始化过程,更新的时候也会经过一系列的钩子函数,方便执行一些我们自己的业务逻辑.
更新阶段执行多次,创建和销毁阶段只是执行一次。
生命周期
创建阶段
数据做响应式化处理就是数据观测。直接写 render 的话是跳过模板编译阶段的,一般是写 template 的。Render 会生成虚拟 DOM,然后再去挂载我们的真实的 DOM,挂载完才是 mounted(到了这个阶段也不能保证就是把我们操作子组件DOM 的行为挂载到 DOM 上,有时候还需要 nexttick 将操作DOM 丢到回调函数).
事件监听器一般比较少用,除非是做通用组件库什么的,Updated 阶段也是不一样,不承诺子组件已经更新完,操作DOM 依然需要添加到 nexttick。更改依赖就会导致死循环不 断的触发更新
销毁阶段
反正哪里的 render 都是渲染虚拟 DOM 挂载真实 DOM;
只是一个简单的方法,只需要添加 functional:true 即可声明, 一般是展示所用。很经常用到在模板作临时变量,这个很有必要(避免多次重复的逻辑计算),计算属性避免了,但是 不是响应式数据的就不得行(如 v-for 和 v-if、全局数据),还得靠函数式组件。
很简单,只需要 render 的时候把传递的属性返回给调用方就行了。
传完之后通过 template 作用域插槽(v-slot)拿到返回出来的 值即可在 template 里面随时使用了。