2
头图

foreword

In Vue2, there is a clichéd topic, how to avoid data the process of a complex object (self or property object) being created as a non-reactive object by default? For example, there is a Vue2 component data :

 <script>
export default {
  data() {
    return {
      list: [
        {
          title: 'item1'
          msg: 'I am item1',
          extData: {
            type: 1
          }
        },
        ...
      ]
    }
  }
}
</script>

这里list.extData不被创建为响应式的对象,相信很多同学都知道,我们可以通过Object.defineProperty list.extDataconfigurable attribute is false to achieve.

In Vue2, we can do this, but back to Vue3, how to solve the same problem? I think this should be the question that many students have in mind at this time. So, let us solve this problem from shallow to deep.

1 Know the basics of Reactivity Object

First, let's take a look at the Reactivity Object responsive object, which is based on the use of Proxy to create a proxy object of the original object and the use of Reflect to proxy the JavaScript operation method to complete the dependency. The process of collecting and distributing updates.

然后,我们可以根据需要通过使用Vue3 提供的refcomputereactivereadonly API 来创建对应的响应式object.

Here, let's look at a simple example:

 import { reactive } from '@vue/reactivity'
const list = reactive([
  {
    title: 'item1'
    msg: 'I am item1',
    extData: {
      type: 1
    }
  }
])

As you can see, we created a reactive data list reactive And, by default list the property value in each item will be processed as responsive , in this example is extData , we can use Vue3 to provide The isReactive function to verify it:

 console.log(`extData is reactive: ${isReactive(list[0].extData)}`)
// 输出 true

Console output:

It can be seen that the corresponding object extData is indeed processed as responsive. Suppose, list is a very long array, and there is no need for the list extData property of each item in ---ec064c38e69aab56c5a6ce5a207d7b30--- to be reactive. Then the process of silently creating a reactive object will generate an unexpected performance overhead (Overhead) .

Since it is the behavior we do not want, we must find a way to solve it. So, let's figure out how to solve this problem from the source code level.

2 Processing of Non-reactivity Object in source code

First of all, we can establish a simple understanding that the processing of Non-reactivity Object must happen before the reactive object is created , I think this is also well understood. In the source code, the process of creating a responsive object is implemented by a function named createReactiveObject in the packages/reactivity/src/reactive.ts file.

2.1 createReactiveObject

Here, let's take a look at the signature of the createReactiveObject function:

 // core/packages/reactivity/reactive.ts
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {}

It can be seen that createReactiveObject the function will receive a total of 5 parameters. Let's understand the meanings of these 5 function parameters:

  • target represents the original object that needs to be created as a responsive object
  • isReadonly indicates that the created responsive object is to be set to read-only
  • baseHandlers Proxy所需要的基础handler ,主要有getsetdeleteProperty , has and ownKeys etc.
  • collectionHandlers类型( MapSet )所handleradd , delete , forEach and other prototype methods to avoid the original object being accessed in the invocation of the prototype method , resulting in the problem of unresponsiveness
  • proxyMap WeekMap mapping representing the created responsive object and the original object, to avoid duplicating the creation of a responsive object based on an original object

Then, in the createReactiveObject function, a series of pre-judgment processing will be done, such as judging whether target is an object, target whether a responsive object has been created (hereinafter collectively referred to as Proxy instance), etc., and then finally create the Proxy instance.

Then, obviously the processing of Non-reactivity Object also happens createReactiveObject The pre-judgment processing of the function is at this stage, and its corresponding implementation will be like this (pseudo code):

 // core/packages/reactivity/src/reactive.ts
function createReactiveObject(...) {
  // ...
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // ...
}

It can be seen that as long as the getTargetType function is used to obtain the incoming target type targetType , it will directly return the original object of TargetType.INVALID target , that is, the subsequent responsive object creation process will not be performed .

So, at this time, I think everyone will have 2 questions:

  • getTargetType What does the function do?
  • TargetType.INVALID What does this mean, the meaning of this enumeration?

Now, let us solve these two questions one by one.

2.2 getTargetType and targetType

Likewise, let's first look at the implementation of the getTargetType function:

 // core/packages/reactivity/src/reactive.ts
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

Among them getTargetType mainly do these 3 things:

  • Judging that target exists on the ReactiveFlags.SKIP attribute, which is a string enumeration with a value of __v_ship , and returns TargetType.INVALID if it exists
  • target f7df43b9b83252af7103749627e1d6cb---是否可扩展Object.isExtensible返回true falsetrue返回TargetType.INVALID
  • When the above two conditions are not met, return targetTypeMap(toRawType(value))

target 1、2 点可以得出,只要你在传入的---238bda88476cf77c5099f2a5674744a6---上设置了__v_ship e34035aee18a77726dd62ff214979444---属性、或者使用Object.preventExtensionsObject.freezeObject.seal target扩展,那么target对应的响应式对象,即直接返回TargetType.INVALID ( TargetType is a numeric enumeration, which will be introduced later).

In our example above is to set extData :

 {
  type: 1,
  __v_ship: true
}

or:

 Object.freeze({
  type: 1
})

Then, if the first and second points are not satisfied, it will return targetTypeMap(toRawType(value)) , of which toRawType function is based on the package of Object.prototype.toString.call , and it finally It will return a specific data type, for example, an object will return Object :

 // core/packages/shared/src/index.ts
const toRawType = (value: unknown): string => {
  // 等于 Object.prototype.toString.call(value).slice(8, -1)
  return toTypeString(value).slice(8, -1)
}

Then, followed by the targetTypeMap function:

 // core/packages/reactivity/src/reactive.ts
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}

It can be seen that the targetTypeMap function actually makes three classifications of the data types we know:

  • TargetType.COMMON represents object Object , array Array
  • TargetType.COLLECTION类型, MapSetWeakMapWeakSet
  • TargetType.INVALID indicates an illegal type, not an object, an array, or a collection

Among them, TargetType corresponds to the enumeration implementation:

 const enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2
}

那么,回到我们上面的这个例子, list.extData 4e9ea03a10ff7e1aa02068279648d211---在toRawType函数中返回的ArraytargetTypeMap函数返回The type will be TargetType.COMMON (not equal to TargetType.INVALID ), which will eventually create a reactive object for it.

So here we can draw a conclusion that if we need to skip the process of creating a reactive object, we must let target satisfy value[ReactiveFlags.SKIP] || !Object.isExtensible(value) or hit targetTypeMap The default logic in the function.

Epilogue

After reading this, I think everyone understands how to skip the process of creating a responsive object for some nested objects in the object when creating a responsive object of a complex object. Moreover, this little trick is undeniably a good optimization method in some scenarios, so it is also very important to do the necessary cognition in advance.

Finally, if there are inappropriate expressions or mistakes in the text, you are welcome to raise an Issue~

like

By reading this article, if you have gained something, you can give a like, this will become the motivation for me to continue to share, thank you~

I am Wuliu. I like to innovate and fiddle with source code. I focus on learning and sharing technologies such as source code (Vue 3, Vite), front-end engineering, and cross-end. Welcome to my WeChat public account Code center or GitHub .

五柳
1.1k 声望1.4k 粉丝

你好,我是五柳,希望能带给大家一些别样的知识和生活感悟,春华秋实,年年长茂。