当用户在页面进行一些交互时,比如点击一个按钮进行查询,这样一个简单的动作,实际上需要先捕获到用户操作的DOM元素上对应的事件,在这个事件的具体实现中,会发起一个异步请求来获取后端返回的数据,然后将这个数据处理后再渲染到页面上。这一整套的流程,其实涉及两个数据流向,一是从HTML流向JS,另一个是从JS流向HTML。如果使用纯原生JS开发,则需要开发者自行实现对DOM元素的事件监听(HTML流向JS),以及将数据传递给DOM(JS流向HTML)。
纯原生开发的缺点是,开发者需要自行处理的事件监听太多,还要懂得什么时候将数据的变化更新到DOM上,并且还可能出现数据的频繁变化导致DOM渲染过于频繁而出现页面卡顿。
早期的JQuery框架,将开发者对DOM元素的捕获以及事件监听封装起来,提供了很多便捷的API,而现在流行的React、Vue、Angular等框架,则进一步弱化了开发者对DOM的处理,开发者可以专注于逻辑处理(JS的部分)。Vue这样的框架使得数据在更新后能适时地渲染到页面上,同时也简化了事件监听的处理,开发者无需再写一大堆addEventListener了。
响应式是Vue的主要特性之一,其作用概括来说,就是对数据进行劫持并收集相关依赖,在数据更新时能触发视图更新。
// 在组件的data函数中声明需要转换成响应式的属性Vue.component('test-responsive', {data: function() {return {count: 0,message: '' // 即使是一个空字符串也必须提前声明srcObj: {}}},mounted() {const newObj = {}this.srcObj = newObjconsole.log(this.srcObj === newObj) // true}})
// 留个问题:data返回的对象中的属性是如何绑定到vue组件实例上的(即为什么可以直接使用this.count)
Vue.component('test-computed', {data: function() {return {firstName: '',lastName: '',}},computed: {fullName: function() {// 这一段如果直接写到模板表达式中,会比较长return this.firstName + ' ' + this.lastName}}})
Vue.component('test-computed', {data: function() {return {firstName: '',lastName: '',}},computed: {fullName: {get: function() {return this.firstName + ' ' + this.lastName},set: function(nFirstName, nLastName) {// 注意:如果在这里尝试直接修改计算属性的值,会导致一个警告并且忽略setter中的修改,因为setter只用于修改计算属性的原始依赖this.firstName = nFirstNamethis.lastName = nLastName}}}})
Vue.component('test-watch', {data: function() {return {question: '',answer: 'This is an empty question'}},watch: {question: function() {this.answer = 'Waiting for your input'this.getAnswer()}},methods: {getAnswer: function() {axios.get('https://test-watch/getanswer').then((res) => {this.answer = res.answer})}}})
// 留个问题:vue2.6是如何实现computed和watch的
在Vue3中允许使用选项式API或者组合式API两种编码风格来书写组件。
选项式API
与Vue2.6的声明方式一致。
export default {data() {return {count: 0,message: '',srcObj: {}}},mounted() {const newObj = {}this.srcObj = newObjconsole.log(this.srcObj === newObj) // false,这里与Vue2.6是有本质区别的}}
组合式API
组合式API的方式提供了两个API来声明响应式数据,一个是ref(),一个是reactive()。
// 留个问题,构建工具是如何实现
reactive()
import { reactive } from "vue";export default {setup() {const state = reactive({ count: 0 });return {state // 暴露state到模板}}}
// 或者<script setup>import { reactive } from "vue";const state = reactive({ count: 0 });</script>
ref()
import { ref } from 'vue'const count = ref(0) // 等价于 ref({ value: 0 })
或者
import { ref } from 'vue'const count = ref({ count: 0})
ref()与reactive()的区别总结
选项式API | 组合式API | ||
---|---|---|---|
实现原理 | getter/setter | Proxy | getter/setter |
声明响应式对象 | data()函数 | reactive()函数 | ref()函数 |
解构响应式对象 (将对象属性结构为局部变量,对局部变量是否仍具有响应性) | 局部变量失去响应性 (因为对局部变量的访问不会触发getter/setter) |
从实现原理上来深入理解Vue响应式系统的。
// 在深层、浅层对象上的响应式表现,在数组和对象上的响应式表现
如何实现依赖跟踪
flowchart TBid1((JS对象obj))id2((JS对象obj))id2-1(getter)id2-2(setter)id3(data函数)id4{{"遍历obj的所有属性,利用Object.defineProperty函数将每个属性转换成getter/setter"}}id5((watcher实例))id6(组件的render函数)id7((虚拟DOM树))subgraph ide1 [data选项]ide2-->|输入|id3id3-->|输出|ide3id3==>|"做了什么?"|id4endsubgraph ide2 [封装前]id1endsubgraph ide3 [封装后]id2---id2-1id2---id2-2endid2-1-.->|进行依赖收集|id5id2-2-.->|数据有变化时通知|id5id5-.->|重新渲染|id6id6-.->|渲染|id7id7-.->|将组件渲染时用到的数据记录为依赖|id2-1