Home  >  Article  >  Web Front-end  >  What is the implementation principle of Vue3 listener

What is the implementation principle of Vue3 listener

王林
王林forward
2023-05-16 15:04:061386browse

Listening to responsive objects

We talked about calculated properties earlier, which can automatically calculate and cache the value of responsive data. And if we only need to perform some preset operations when the responsive data changes, we can use the watch listener. Let’s implement the simplest example first, and then expand it bit by bit.

const data = {foo: 1}
const obj = reactive(data)
watch(obj, () => {
  console.log('obj已改变')
})

In this example, we use the watch listener. When the property of obj is changed, the console should print out obj has been changed. Based on our previous implementation of calculated properties, we already have a general idea here. Think of watch as a side effect function of the responsive object. When the responsive object changes, the execution of the side effect function is triggered.

If you want to trigger a side-effect function, you must first collect it. Do you remember how the side-effect function is collected? Yes, When the reactive data is get, the side effect function is collected. So first, we need to make watch collected by the reactive object.​

function watch(getter, cb) {
  effect(
    () => getter.foo
  )
}

Next, we also need to let our default method be executed. When responsive data is set, the side effect function is triggered. What we want to trigger here is the callback function passed in cb. Here we can use the scheduler when implementing calculated properties. When the scheduler exists, set triggers The trigger will first execute the function in the scheduler.

function watch(getter, cb) {
  effect(
    () => getter.foo,
    {
      scheduler() {
        cb()
      }
    }
  )
}

What is the implementation principle of Vue3 listener

A simple listener is complete! Here, for the sake of simplicity, we have written the function to death and only supports listening to obj.foo. Next, we have to think about how to listen to any properties of the responsive object?

According to the previous idea, if we want to listen to any attribute of the responsive object, we need to get to each attribute of the object, which requires us to The object is traversed recursively.

function traverse(value, seen = new Set()) { // (1)
  if(typeof value !== 'object' || value === null || seen.has(value)) return
  seen.add(value)
  for(const key in value) {
    traverse(value[key], seen)
  }
  return value
}

In order to avoid the infinite loop caused by circular references when recursively traversing objects, we created Set at (1). When the same object appears repeatedly , return directly.

Listening to property values

In Vue3, we cannot directly listen to the property values ​​of responsive objects. If you need to listen to the property value of the responsive object, you need a getter function so that the listener can be collected by the responsive object.

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  () => {
  console.log('obj.foo已改变')
})

Specifying attributes means that the current listener will only be triggered by the specified attributes, and there is no need to recursively traverse the entire responsive object.

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter) // (2)
  effect(
    () => getter(),
    {
      scheduler() {
        cb()
      }
    }
  )
}

What is the implementation principle of Vue3 listener

At (2), we added a judgment. If the incoming function is already a getter function, we use it directly. If notgetter function is considered a responsive object and needs to be traversed recursively.

Listen to get the new value and the old value

In Vue we also need to be able to get the new value before and after the responsive data update in the callback function cb() with the old value.

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

The next question is, how to obtain newValue and oldValue. newValue is easy to solve. After executing the callback function cb(), what you get is newValue, but how to get the value of oldValue here? To get the old value from watch, the side-effect function cannot be executed immediately. What comes to mind here? Yes, when implementing computed properties, we have used lazy, which can prohibit the automatic execution of side-effect functions.

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true, // (3)
      scheduler() {
          cb(oldValue)
      }
    }
  )
  oldValue = effectFn() // (4)
}

At (3) we set the lazy switch. After setting lazy, the execution rights of the side effect function are handed over to ourselves. At (4), we manually execute the side-effect function. We need to look back here. The getter we passed in earlier is a function () => obj.foo, and the effect function One parameter is the side effect function that is actually executed, so what we execute manually is actually the function () => obj.foo, so we get the old value.

How to get the new value? After the value of the responsive data is updated, the side effect function effect will be triggered to execute. When the scheduler attribute exists, the scheduler will be executed. In the scheduler, we can execute the side effect function again and get the changed new value through () => obj.foo.

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler() {
        newValue = effectFn()
        cb(newValue, oldValue)
        oldValue = newValue // (5)
      }
    }
  )
  oldValue = effectFn()
}

At (5), after executing the callback function cb(), we did some aftermath work and updated the value of oldValue for the next callback prepare for.

What is the implementation principle of Vue3 listener

#Sometimes, we also want the listener to execute the callback function immediately when it is created.

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
      console.log('newValue:', newValue,', oldValue:', oldValue)
  },
  { immediate: true }
)

When the value of immediate is true, it needs to be executed immediately. After clarifying the requirements, let's improve the watch listener.

function watch(getter, cb, options = {}) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  function job() { // (6)
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }

  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler: job,
    }
  )

  if(options.immediate) {  // (7)
    job()
  } else {
    oldValue = effectFn()
  } 
}

在(6)处,我们抽离了回调函数的执行逻辑,当options.immediate存在时,直接触发执行。

What is the implementation principle of Vue3 listener

实现效果

What is the implementation principle of Vue3 listener

The above is the detailed content of What is the implementation principle of Vue3 listener. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete