Computed functions are implemented internally. Take a function that returns a value as an argument. The return value of this function is the value of the evaluated property. We will listen for changes in the reactive data used by the function contents and return the result of the function execution.

export functon computed(getter){

    // 1. Finally return an object created by ref with the value attribute

    const result = ref();

    // 2. Listen for changes in responsive data. When the data changes, the effect function is executed again, and the result of the getter is stored in the result

    effect(() = >(the result value = getter ()));return result

}
Copy the code

So, you must think: so easy? You got to be kidding me, you asshole.

Start with reactive

Reactive:

  • Receive a parameter and determine if the parameter Object is Object
  • Create an interceptor objecthandlerTo set upset/get/deletePropertymethods
  • return Proxy
export function reactive (target) {

  // Check whether target is an object, not between objects

  if(! isObject(target))return target

  

  // The interceptor object

  const handler = {

    get (target, key, receiver) {
    // code...
    },

    set (target, key, value, receiver) {
    // code...
    },

    deleteProperty (target, key) {
    // code...}}return new Proxy(target, handler)

}
Copy the code

So the purpose of our get method is to get the key value of the target. If the key itself is still an Object, then you have to keep recursing, right? Let’s go to coding.


const convert = target= > isObject(target) ? reactive(target) : target;


// In order not to write the helper functions again, write them together first. Just look at the name of the function and you get the idea.

const isObject = val= >val ! = =null && typeof val === 'object'

const convert = target= > isObject(target) ? reactive(target) : target

const hasOwnProperty = Object.prototype.hasOwnProperty

const hasOwn = (target, key) = > hasOwnProperty.call(target, key);

let targetMap = new WeakMap(a);// code ...



get (target, key, receiver) {
      // Collect dependencies
      // track(target, key)
      console.log('get:',key);
      // Return the key value of the target flood
      const result = Reflect.get(target, key, receiver);

      return convert(result)

},
Copy the code

So, everyone says recursion, closures, nested loops are bad. But you can’t deny it: it smells good…

To continue. Our set method.

set (target, key, value, receiver) {

      const oldValue = Reflect.get(target, key, receiver)

      let result = true

      if(oldValue ! == value) { result =Reflect.set(target, key, value, receiver)

        // Trigger the update

        // trigger(target, key);

        console.log('set key:',key,'value:',value);

      }

      return result

},
Copy the code

Yes, you read that right, you set the value, so of course it triggers the update. But do you think you’re writing trigger now?

👌 Let’s look at the deleteProperty method

deleteProperty (target, key) { const hadKey = hasOwn(target, key); // Make your head hot. Const result = reflect.deleteProperty (target, key) if (hadKey && result) {// trigger(target, key) console.log('delete:',key); } return result }Copy the code

The trigger:

export function trigger (target, key) {

  const depsMap = targetMap.get(target); // "targetMap" = "targetMap";

  if(! depsMap)return 

  const dep = depsMap.get(key)

  if (dep) {

    dep.forEach(effect= > {

      effect()

    })

  }

}
Copy the code

Effect & Track – Collect dependencies

Let’s start with using chestnuts

<! DOCTYPEhtml>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">

  <title>Document</title>

</head>

<body>

  <script type="module">

    import { reactive, effect } from './text2.js'

    const product = reactive({

      name: 'Your uncle'.price: 100.count: 1

    })

    let total = 0 

    effect(() = > {

      total = product.price * product.count

    })

    console.log(total) / / 100

    product.price = 2000

    console.log(total) / / 2000

    product.count = 10

    console.log(total) / / 20000

  </script>

</body>

</html>
Copy the code

If the result of three consoles in chestnuts is correct, what exactly did Effect do?

  • First load, execute the arrow function inside effect. The arrow function accesses product. Product is the reactive object returned by reactive. When we access the product. price property we trigger the get method on the price property. Dependencies are collected in the GET method. The procedure is to store the property and the callback function. Properties are related to objects, so the target object is first stored in the get method of the proxy object. Then there are the properties of the target object, and the arrow function.
  • When an update is triggered, the corresponding function is found based on the property.
  • Continue collecting dependencies for the next property.
let activeEffect = null

export function effect (callback) {

  activeEffect = callback

  callback() // Access the reactive object properties to collect dependencies

  activeEffect = null

}
Copy the code

(Just kidding: Like React, what the trolls say is not totally unreasonable.)

Track method

let targetMap = new WeakMap(a)// Collect dependencies

export function track (target, key) {

  if(! activeEffect)return // If there is no dependency, go straight out

  let depsMap = targetMap.get(target) // Find the dependencies of the target object

  if(! depsMap) { targetMap.set(target, (depsMap =new Map()));// 没找到就新创建一个depsMap

  }

  let dep = depsMap.get(key) // Find the dep of the current key

  if(! dep) { depsMap.set(key, (dep =new Set())) // Not found, add new Set

  }

  dep.add(activeEffect) 

}



// Then go to the get method to collect dependencies
Copy the code

3 trigger – Triggers the update

export function trigger (target, key) {

  const depsMap = targetMap.get(target)

  if(! depsMap)return

  const dep = depsMap.get(key)

  if (dep) {

    dep.forEach(effect= > {

      effect()

    })

  }

}
Copy the code

Let’s see.

4. ref

export function ref (raw) {

  // Check whether raw is an object created by ref, and return if it is

  if (isObject(raw) && raw.__v_isRef) {

    return

  }

  let value = convert(raw)

  const r = {

    __v_isRef: true.// Don't ask why the property name has to look like this, it looks like this in the Vue3 source code

    get value () {
      track(r, 'value')
      return value
    },

    set value (newValue) {
      if(newValue ! == value) { raw = newValue value = convert(raw) trigger(r,'value')}}}return r

}
Copy the code

5. computed

export function computed (getter) {
  const result = ref()
  effect(() = > (result.value = getter()))
  return result
}
Copy the code

Please go back to the beginning of the article. Do you still think I am a dish J? Is it simple enough to cry?

End

Finally, let’s comparereactive å’Œ refThe difference between

  • refYou can turn basic data types into responsive objects
  • refThe returned object is responsive even if it is assigned a new object
  • reactiveObject that is returned. Reassigning will lose the response
  • reactiveThe returned object cannot be deconstructed.