zoukankan      html  css  js  c++  java
  • How Reactivity works in Vue.js(译)

    原文地址:How Reactivity works in Vue.js

    在前端开发人员的世界中,“响应式”是每个人都使用的东西,但很少有人能理解。 确实,这不是谁的错,因为每个人在编程中对响应式的定义不同。 因此,在开始之前,我只给您一个有关前端框架的定义。

    “ JavaScript框架之间的响应式是一种应用程序状态的更改自动反映在DOM中的现象。”

    Reactivity

    Vue.js中的响应式

    Vue.js中的响应式是附带了包里的一些东西。

    这是Vue.js中具有双向绑定(使用v-model)的示例,

    在上面的示例中,您可以清楚地看到数据模型层中的更改,

        new Vue({
          el: "#app",
          data: {
            message: ""
          },
        })
    

    自动反映在视图层中

        <div id="app">
          <h1>Enter your message in the box</h1>
          <p>{{ message }}</p><br>
          <input placeholder="Enter message" v-model="message" />
        </div>
    

    如果您熟悉Vue.js,那么您可能已经习惯了。 但是,您必须记住,香草JS中的工作方式不同。 让我用一个例子来解释它。 在这里,我在vanilla JS中重新创建了上面的Vue.js响应式示例。

    您可以在此处看到JavaScript不是自然反应的,因为当您输入消息时,您不会在HTML视图中看到该消息自动重新渲染。 为什么会这样呢? Vue.js是做什么的?

    好吧,要得到答案,我们必须了解其潜在的反应系统。 一旦我们有了一个清晰的了解,我们将尝试使用vanilla JavaScript重新创建我们自己的反应系统,该系统类似于Vue.js响应式系统。

    Vue.js响应式系统

    Reactivity System

    让我从头为您分解一下

    第一次渲染

    在第一次渲染上,如果“触达”了数据属性(将访问数据属性称为“触达”该属性),则会调用其getter函数。

    Getter:getter函数调用监视程序的目的是为了收集此数据属性作为依赖项。

    (如果data属性是一个依赖项,则意味着每次此属性的值更改时,某些目标代码/函数都将运行。)

    观察者

    每当调用观察程序时,它都会将该数据属性添加为从调用它的getter的依赖项。 观察者还负责调用组件渲染函数。

    组件渲染函数

    实际上,Vue的组件渲染功能并不是那么简单,但是为了理解起见,我们只需要知道它会返回具有更新数据属性的Virtual DOM Tree,该虚拟DOM树就会显示在视图中。

    数据变化!
    这部分,基本上是Vue.js中反应性的核心。 因此,当我们对数据属性(作为依赖项收集)进行更改时,将调用其setter函数。

    Setter:setter函数在数据属性的每次更改时通知观察者。 我们已经知道,观察者运行组件渲染功能。 因此,在data属性中所做的更改将显示在视图中。

    我希望您现在可以清楚该工作流程,因为我们将在原始JavaScript中重新创建此响应式系统。

    在vanilla JavaScript中重新创建Vue.js响应式系统

    现在,我们正在重新创建响应式系统,最好的方法是一对一地理解其构建块(以代码形式),最后我们可以组装所有的系统,

    数据模型

    任务:首先,我们需要一个数据模型

    解决方案:
    我们需要什么样的数据? 由于我们正在重新创建我们先前看到的Vue示例,因此我们将需要一个完全像它的数据模型。

        let data = {
        	message: ""
        }
    

    一个目标函数

    任务:我们需要有一个目标函数,一旦数据模型发生更改,将运行该函数。

    解决方案:
    解释目标函数是什么的最简单方法,

    “嗨,我是一条数据属性消息,我有一个目标函数renderFunction()。 每当我的值改变时,我的目标函数就会运行。

    PS:我可以有多个目标函数,而不仅仅是renderFunction()”

    因此,我们声明一个名为target的全局变量,该变量将帮助我们为所有数据属性记录一个目标函数。

        let target = null
    

    依赖类

    任务:我们需要一种收集数据属性作为依赖的方法

    到目前为止,我们只有数据和目标函数的概念,它们在数据值更改时运行。 但是,我们需要一种方法来分别记录每个数据属性的目标函数,以便当数据属性发生更改时,只有那些针对该数据属性分别存储的目标函数才会运行。

    解决方案:
    我们需要为每个数据属性的目标函数提供一个单独的存储空间。

    假设我们有以下数据,

        let data = {
        	x: '',
        	y: ''
        }
    

    然后,我们要为x和y提供两个单独的存储。 那么,为什么不只定义一个Dependency类,每个数据属性也可以有其唯一的实例呢?

    这可以通过定义一个Dependency类来完成,以便每个数据属性都可以拥有自己的Dependency类实例。 因此,可以为目标功能分配每个数据属性自己的存储空间。

        class Dep {
        	constructor() {
          	this.subscribers = []
          }
        }
    

    依赖类具有订户数组,该数组将用作目标函数的存储。

    Dependency Class

    现在,我们还需要做两件事来使Dependency类完全完成,

    • depend():该函数将目标函数推入订阅者数组。
    • notify():此函数运行存储在订阅服务器数组中的所有目标函数。
        class Dep {
        	constructor() {
          	this.subscribers = []
          }
          depend() {
          	// Saves target function into subscribers array
          	if (target && !this.subscribers.includes(target)) {
            	this.subscribers.push(target);
            }
          }
          notify() {
          	// Replays target functions saved in the subscribers array
            this.subscribers.forEach(sub => sub());
          }
        }
    

    追踪变化

    任务:我们需要找到一种方法,只要该属性发生更改,就可以自动运行该属性的目标函数。

    解决方案:

    到现在为止,我们已有,

    • 数据
    • 数据更改时会发生什么
    • 依赖收集机制

    下一步我们需要的是,

    • 一种在“触达”数据属性时触发depend()的方法。
    • 一种跟踪数据属性中的任何更改然后触发notify()的方法。
      为了实现这一点,我们将使用getter和setter。 Object.defineProperty()允许我们为此类任何数据属性添加getter和setter,
        Object.defineProperty(data, "message", {
        	get() {
          	console.log("This is getter of data.message")
          },
          set(newVal) {
          	console.log("This is setter of data.message")
          }
        })
    

    因此,我们将为所有可用的数据属性定义getter和setter,

        Object.keys(data).forEach(key => {
        	let internalValue = data[key]
    
          // Each property gets a dependency instance
          const dep = new Dep()
    
          Object.defineProperty(data, key, {
          	get() {
            	console.log(`Getting value, ${internalValue}`)
            	dep.depend() // Saves the target function into the subscribers array
              return internalValue
            },
            set(newVal) {
            	console.log(`Setting the internalValue to ${newVal}`)
            	internalValue = newVal
              dep.notify() // Reruns saved target functions in the subscribers array
            }
          })
        })
    

    另外,您可以在上方看到在getter中正在调用dep.depend(),因为当“触达”数据属性时,将调用其getter函数。

    我们在setter内部具有dep.notify(),因为当该数据属性的值发生更改时,将调用setter函数。

    观察者

    任务:我们需要一种方法来封装当数据属性的值更改时必须运行的代码(目标函数)。

    解决方案:

    到目前为止,我们已经创建了一个系统,在该系统中,数据属性在被“触达”时立即作为依赖项添加,并且如果该数据属性有任何更改,则将执行其所有目标函数。

    但是,仍然缺少一些东西,我们还没有使用目标函数的任何代码来初始化该过程。 因此,为了封装目标函数的代码然后初始化该过程,我们将使用观察者。

    观察者是一个函数,它将另一个函数作为参数,然后执行以下三个操作:

    • 将匿名函数的参数赋值给全局目标变量。
    • 运行target()。 (这样做会初始化过程。)
    • 重新分配 target = null
        let watcher = function(func){
          // Here, a watcher is a function that encapsulates the code
          // that needs to recorded/watched.
          target = func // Then it assigns the function to target
          target() // Run the target function
          target = null // Reset target to null
        }
    

    现在,如果我们将一个函数传递给观察者,然后运行它,那么自适应系统将完成初始化的过程。

        let renderFunction = () => {
        	// Function that renders HTML code.
        	document.getElementById("message").innerHTML = data.message;
        }
    
        watcher(renderFunction);
    

    然后,我们完成了!

    现在,组装以上所有代码,我们已经成功地使用原始JavaScript重新创建了Vue.js响应式系统。 这是我使用此响应式系统向您展示的第一个示例的实现,

  • 相关阅读:
    datasnap的监督功能【3】-TCP链接监督功能
    实体服务规则或值更新设置字段锁定性
    设置指定的单据视图
    启动或停止IIS
    SSMS2014清除登录记录
    未授予用户在此计算机上的请求登录类型
    采购合同手动下推采购订单提示没有NAME属性
    审批流消息中无法获取明细字段
    费用申请单反写费用合同提示第2行总金额超出,但是实际未超出
    调试手机端
  • 原文地址:https://www.cnblogs.com/xingguozhiming/p/13782948.html
Copyright © 2011-2022 走看看