Skip to content

依赖收集

当状态数据发生变更时(即计算属性的依赖发生变化时),AutoStore会自动执行计算属性的Getter函数,然后将计算结果赋值给State中的对应属性。

依赖路径

在了解计算属性的依赖路径之前,先了解下依赖路径的概念。依赖路径是指在状态树中的路径,依赖路径的格式为:

ts
type DependPath  = string | string[]

依赖路径是一个使用.作为分割符的字符串或者string[]数组。

ts

const state = {
  user:{
    name:"Zhang",
    age:18,
    address:[
      {
        city:"Shanghai",
        street:"Nanjing Road"
      },
    ],
    tags:["A","B","C"]
  }
}

// 字符串路径

user
user.name
user.address.0.city  // 数组索引
user.age
user.tags.1          // 数组索引

// 等效的数组路径

["user"]
["user","name"]
["user","address",0,"city"]
["user","age"]
["user","tags",1]

INFO

  • Q: 为什么有字符串路径数组路径的区别?
  • A: 两者是等效的,相较而言,使用.作为分割符的字符串更加友好方便,但缺点是当状态数据的键名称包含.字符时会产生岐义。而数组路径更加精准,不会产生岐义。

依赖类型

计算属性的依赖类型定义如下:

ts
type ComputedDepend = 'CURRENT' | 'ROOT' | 'PARENT' 
  | `/${string}` | `./${string}` | `../${string}` | string | string[]

计算属性的依赖声明包括绝对路径相对路径

依赖类型 说明 示例
CURRENT 当前对象 computed所在状态的对象路径
ROOT 根对象 等效于/
PARENT 父对象 computed所在状态的对象路径的父对象
/${string} 绝对路径 eg. /user/name
./${string} 相对CURRENT的路径 eg. ./name
../${string} 相对PARENT的路径 eg. ../name,../../a/bc
string 绝对字符串路径 等效于/user.name

重点理解一下相对路径的概念:

相对路径的相对指的是computed函数所在的对象,例如:

  • 相对当前对象

CURRENT这里的当前指的是computed函数所在的对象,即total所在的对象是order对象。

ts
const state = {
  order:{
    price:10,
    count:1,
    total:computed(async (scope)=>{
      return scope.price*scope.count
    },[
      './price',
      './count'
    ])
  }
}
  • 相对父对象

total所在的对象是order对象,..表示父对象,指向的就是根对象.

ts
const state = {
  order:{
    price:10,
    count:1,
    total:computed(async (scope)=>{
      return scope.price*scope.count
    },[
      '../order.price',
      '../order.count'
    ])
  }
}

特别注意

相对的是computed函数声明的状态所在的对象,上例中的total所在的对象是order而不是total

通配符依赖

在指定依赖时可以通过*通配符来匹配路径中任意字符,如:

  • orders.*.total 匹配orders的任意对象的total属性。
  • orders.* 匹配orders下的任意成员。
  • orders.*.address.*.city 匹配orders的任意对象的address的任意对象的city属性。

:::success 示例 在快速入门的示例中,我们就使用orders.*.total来匹配orders数组成员中的total属性。 :::

收集依赖

当依赖发生变化时,会自动触发计算属性的重新计算。因此必然有人一个收集依赖的过程,由于同步计算属性和异步计算属性的依赖收集方式不同,因此分别介绍。

同步依赖收集

在同步属性的初始化中,会自动完成同步计算属性的依赖收集,原理如下:

  • 执行同步计算函数Getter前,先调用store.watch函数来侦听对状态的所有读写事件。
  • 然后自动执行一次计算函数Getter,这样侦听函数就可以侦听到Getter函数用到了哪些状态,即依赖了哪些状态,将之记录下来即可,从而完成依赖的收集工作。
  • 最后取消store.watch侦听。

特殊注意的是,必须保证同步计算属性的计算函数执行是幂等的, 即多次调用结果是一样的。才可以保证依赖收集的准确性。 通俗的讲,就是计算函数的执行必须能收集到相同的依赖。

如下示例就无法保证正确收集依赖。

ts
const state = {
  order:{
    price:10,
    count:1,
    total:computed((scope)=>{
      if(a===1){
        return scope.count
      }
      return scope.price*scope.count
    } )
  }
}

以上示例在第一次执行时收集依赖,如果a=1,返回scope.count,就只能收集到scope.count的依赖,而不会收集到scope.price的依赖。这样在scope.price变化时,就无法触发total的重新计算。

异步依赖收集

异步计算属性的依赖收集无法像同步计算属性那样自动完成,需要手动指定依赖路径。

ts
const state = {
  order:{
    price:10,
    count:1,
    total:computed(async (scope)=>{
      return scope.price*scope.count
    },['./price','./count'])
  }
}
  • 异步计算属性的依赖路径是通过第二个参数指定的,这里指定了./price./count,即依赖了pricecount属性。
  • 可以指定一个或多个依赖路径,当依赖路径中的任何一个发生变化时,都会触发计算属性的重新计算。
  • 依赖路径可以是绝对路径,也可以是相对路径,相对路径的相对对象是计算属性所在的对象。