Luna Tech

Tutorials For Dummies.

Vue: Reactivity Fundamentals (Options API)

2022-03-15


Reference


1. Declaring Reactive State

使用 Options API 的话,我们用 data option 来定义组件的 reactive state。

Option value 必须是一个 return an object 的 function。

当创建新的组件 instance 的时候,Vue 会调用这个 function,然后把 return object 加入到它的 reactivity 系统里面。

这个 object 的所有 top-level property 都会附加在组件上,可以用 this 来使用。

Any top-level properties of this object are proxied on the component instance (this in methods and lifecycle hooks)

export default {
  data() {
    return {
      count: 1,
    };
  },

  // `mounted` is a lifecycle hook which we will explain later
  mounted() {
    // `this` refers to the component instance.
    console.log(this.count); // => 1

    // data can be mutated as well
    this.count = 2;
  },
};

Lifecycle:property 什么时候加入 instance

这些 properties 只有当 instance 首次被创建的时候才会加进去,所以我们要确保它们都在 data function 返回的那个 object 里面。

假如我们不确定 value 的话,可以用 null,undefined 或者其他 placeholder value 来给 property 赋值。

不加入 data function 可以吗?

可以直接添加一个 component property,但是这种方式添加的 property 无法触发 reactive updates。

Naming convention

避免使用 $_ 来命名 top-level data properties,因为它们是 reserved character。

Vue uses a $ prefix when exposing its own built-in APIs via the component instance.

It also reserves the prefix _ for internal properties.

Reactive Proxy vs. Original

Vue 3 的 reactive data 是通过 JS proxies 来做的,所以 Vue 2 的一些 edge case(如下)在 Vue 3 里面不会出现。

export default {
  data() {
    return {
      someObject: {},
    };
  },
  mounted() {
    const newObject = {};
    this.someObject = newObject;

    console.log(newObject === this.someObject); // false
  },
};

解释:当你在赋值之后 access this.someObject 时,这个 value 是原始 newObject的一个 reactive proxy,原始的 newObject 不会被改变。

所以我们应该记住要通过 this 的 property 来 access reactive state。

When you access this.someObject after assigning it, the value is a reactive proxy of the original newObject. Unlike in Vue 2, the original newObject is left intact and will not be made reactive: make sure to always access reactive state as a property of this.


2. Declaring Methods

我们可以在 vue option 里面 define methods。

在 template 里面可以直接 access data option 的 property,但是在 methods 里面必须加 this.xxx.

Note: methods cannot be defined as arrow functions, as that prevents Vue from binding the appropriate this value.

export default {
  methods: {
    increment: () => {
      // BAD: no `this` access here!
    },
  },
};

Example

我们可以在 template 里面直接 access method,大部分时候这些 method 都是 event listener。

<div>
  <button @click="items.push({id:items.length+1, label: newItem})">
    Save Item
  </button>
  <button @click="saveItem">Save Item</button>
</div>

<script>
  export default {
    data() {
      return {
        someObject: {},
      };
    },
    methods() {
      saveItem(){
        this.items.push({id:items.length+1, label: newItem});
        this.newItem = "";
      },
      increment() {
        this.count++
      }
    },
    mounted() {
      // methods can be called in lifecycle hooks, or other methods!
      this.increment()
    }
  };
</script>

3. DOM 的更新时间

当你改变 reactive state 的时候,DOM 会自动更新,但是 DOM update 并不是同步进行的,Vue 会通过 buffer 来推迟到 update cycle 的下一步才进行更新,这样每个组件只需要更新一次。

Vue buffers them until the “next tick” in the update cycle to ensure that each component needs to update only once no matter how many state changes you have made.

我们可以使用 nextTick() global API 来进行等待。

import { nextTick } from "vue";

export default {
  methods: {
    increment() {
      this.count++;
      nextTick(() => {
        // access updated DOM
      });
    },
  },
};

4. Deep Reactivity

Vue 的 state 是默认 deeply reactive 的,也就是说即使改变了 nested objects 或者 array,这种改变也会被 detect 到。

export default {
  data() {
    return {
      obj: {
        nested: { count: 0 },
        arr: ["foo", "bar"],
      },
    };
  },
  methods: {
    mutateDeeply() {
      // these will work as expected.
      this.obj.nested.count++;
      this.obj.arr.push("baz");
    },
  },
};

创建 shallow reactive objects

假如我们只需要 track root-level reactivity,就要用到这个方法,但这个属于高阶用法。


5. Stateful Methods

有时候我们需要动态创建一个 method function,比如创建一个 debounced event handler:

import { debounce } from "lodash-es";

export default {
  methods: {
    // Debouncing with Lodash
    click: debounce(function () {
      // ... respond to click ...
    }, 500),
  },
};

上面这种做法会给 reused component 造成问题,因为 debounced function 是 stateful 的,也就是说它是根据 elasped time 来维持 internal state 的。假如多个 component instance 共享同一个 debounce function,它们之间就会互相干扰。

怎么解决?

created lifecycle hook 里面创建这个 method,在 unmounted 的时候 clean up.

也就是把 stateful 和 lifecycle hook 关联在一起,保证它只跟每个 instance 相关。

export default {
  created() {
    // each instance now has its own copy of debounced handler
    this.debouncedClick = _.debounce(this.click, 500);
  },
  unmounted() {
    // also a good idea to cancel the timer
    // when the component is removed
    this.debouncedClick.cancel();
  },
  methods: {
    click() {
      // ... respond to click ...
    },
  },
};