直接跳到内容

响应式 API:核心

参考

要更好地了解响应式 API,推荐阅读下面几个指南中的章节:

ref()

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

  • 类型

    py
    def ref(value: Any, debug_msg='') -> Ref:
    
    class Ref(RefImpl):
      value: Any
  • 详细信息

    ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

    如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

    若要避免这种深层次的转换,请使用 shallowRef() 来替代。

  • 示例

    py
    count = ref(0)
    print(count.value) # 0
    
    count.value = 1
    print(count.value) # 1
  • 参考

computed()

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

  • 类型

    py
    # 只读
    def computed(
      getter: Callable[[], Any] | DebuggerOptions
      # 查看下方的 "计算属性调试" 链接
      debuggerOptions: DebuggerOptions | None
    ) -> ComputedRefImpl:
    
    # 可写的(暂不支持)
    def computed(
      options: WritableComputedOptions | DebuggerOptions,
      debuggerOptions: DebuggerOptions | None
    ) -> Ref:
    
    class WritableComputedOptions:
      def get(self) -> Any:
      def set(self, val: Any) -> None:
  • 示例

    创建一个只读的计算属性 ref:

    py
    count = ref(1)
    def plusOne():
       return count.value + 1
    
    plusOne = computed(plusOne)
    
    print(plusOne.value) # 2
    
    plusOne.value += 1 # 错误

    computed也可以作为装饰器使用(推荐),创建一个只读的计算属性 ref:

    py
    count = ref(1)
    
    @computed
    def plusOne():
       return count.value + 1
    
    print(plusOne.value) # 2
    
    plusOne.value += 1 # 错误

    创建一个可写的计算属性 ref:(暂不支持)

    js
    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: (val) => {
        count.value = val - 1
      }
    })
    
    plusOne.value = 1
    console.log(count.value) // 0

    调试:

    py
    from vuepy.reactivity.computed import DebuggerOptions
    from vuepy.reactivity.effect import IgnoreTracking
    
    count = ref(1)
    
    def onTrack(info):
      with IgnoreTracking():
          print(f"track: {info}")
    
    def onTrigger(info):
        with IgnoreTracking():
            print(f"trigger: {info}")
    
    @computed(DebuggerOptions(onTrack, onTrigger))
    def plusOne():
       return count.value + 1
    
    print(plusOne.value) # 2
    
    plusOne.value += 1 # 错误
  • 参考

reactive()

返回一个对象的响应式代理。

  • 类型

    py
    def reactive(target: dict | list) -> "ReactiveProxy" | "DictProxy" | "ListProxy":
  • 详细信息

    响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。

若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。

返回的对象以及其中嵌套的对象都会通过 ReactiveProxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。

  • 示例

    创建一个响应式对象:

    py
    obj = reactive({ 'count': 0 })
    obj.count += 1

    ref 的解包:

    py
    count = ref(1)
    obj = reactive({ 'count': count })
    
    # ref 需要手动解包
    print(obj.count.value == count.value) // True
    
    # 会更新 `obj.count`
    count.value += 1
    print(count.value) # 2
    print(obj.count.value) # 2
    
    # 也会更新 `count` ref
    obj.count.value += 1
    print(obj.count.value) # 3
    print(count.value) # 3

注意当访问到某个响应式数组或 dict 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包:

py
books = reactive([ref('Vue.py Guide')])
# 这里需要 .value
print(books[0].value)

d = reactive({'count': ref(0)})
# 这里需要 .value
print(d['count'].value)

将一个 ref 赋值给一个 reactive 属性时,ref 需要手动解包:

py
count = ref(1)
obj = reactive({})

obj.count = count.value

print(obj.count) # 1
obj.count == count.value # true

readonly()

WARNING

请注意,这是一个预留的语法,当前版本未实现。

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

  • 类型

    ts
    function readonly<T extends object>(
      target: T
    ): DeepReadonly<UnwrapNestedRefs<T>>
  • 详细信息

    只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

    要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

  • 示例

    js
    const original = reactive({ count: 0 })
    
    const copy = readonly(original)
    
    watchEffect(() => {
      // 用来做响应性追踪
      console.log(copy.count)
    })
    
    // 更改源属性会触发其依赖的侦听器
    original.count++
    
    // 更改该只读副本将会失败,并会得到一个警告
    copy.count++ // warning!

watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • 类型

    py
    def watchEffect(
        effect_or_options: WatchEffect | WatchOptionsBase = None,
        debuggerOptions: DebuggerOptions = None
    ) -> WatchStopHandle:
    
    # (cleanupFn: () => void) => void
    OnCleanUp = Callable[[Callable[[], None]], None]  
    # (onCleanup: OnCleanup) => void
    WatchEffect = Callable[[OnCleanUp], None]  
    WatchStopHandle = Callable[[], None]  # () => void
    
    @dataclasses.dataclass
    class WatchOptionsBase:
      flush: FlushEnum = FlushEnum.PRE
      onTrack: Callable[[DebuggerEvent], None] = None
      onTrigger: Callable[[DebuggerEvent], None] = None
    
    class FlushEnum(enum.Enum):
      PRE = 'pre'    # 暂不支持
      POST = 'post'  # 暂不支持
      SYNC = 'sync'
  • 详细信息

    第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。

    第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

    返回值是一个用来停止该副作用的函数。

  • 示例

    py
    count = ref(0)
    
    def print_val(on_cleanup):
      print(count.value)
    
    watchEffect(prin_val)
    # -> print 0
    
    count.value += 1
    # -> print 1

    推荐使用装饰器的方式:

    py
    count = ref(0)
    
    @watchEffect
    def print_val(on_cleanup):
      print(count.value)
    # -> print 0
    
    count.value += 1
    # -> print 1

    副作用清除:

    py
    @watchEffect
    def query(on_cleanup):
      response, cancel = doAsyncWork(id.value)
      # `cancel` 会在 `id` 更改时调用
      # 以便取消之前
      # 未完成的请求
      on_cleanup(cancel)
      data.value = response()

    停止侦听器:

    py
    @watchEffect
    def f(on_cleanup):
      pass
    
    # 重命名便于理解
    stop = f
    
    # 当不再需要此侦听器时:
    stop()

watchPostEffect()

WARNING

请注意,这是一个预留的语法,当前版本未实现。

watchEffect() 使用 flush: 'post' 选项时的别名。

watchSyncEffect()

WARNING

请注意,这是一个预留的语法,当前版本未实现。

watchEffect() 使用 flush: 'sync' 选项时的别名。

watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

  • 类型

    py
    # WatchSource 侦听单个来源
    # MultiWatchSources 侦听多个来源
    def watch(
      source: WatchSource | MultiWatchSources,
      cb_or_options: WatchCallback | WatchOptions = None,
      options: WatchOptions = None
    ) -> WatchStopHandle :
    
    WatchSource = Union[RefImpl, ComputedRefImpl, Callable[[], Any], ReactiveProxy]
    # (val: Any, oldVal: Any, onCleanup: OnCleanup) => any
    WatchCallback = Callable[[Any, Any, OnCleanUp], Any]
    MultiWatchSources = List[WatchSource]
    
    @dataclasses.dataclass
    class WatchOptionsBase:
      immediate: bool = False
      deep: bool = False
      flush: FlushEnum = FlushEnum.PRE
      onTrack: Callable[[DebuggerEvent], None] = None
      onTrigger: Callable[[DebuggerEvent], None] = None
    
    class FlushEnum(enum.Enum):
      PRE = 'pre'    # 暂不支持
      POST = 'post'  # 暂不支持
      SYNC = 'sync'

为了便于阅读,对类型进行了简化。

  • 详细信息

    watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

    第一个参数是侦听器的。这个来源可以是以下几种:

    • 一个函数,返回一个值
    • 一个 ref
    • 一个响应式对象
    • ...或是由以上类型的值组成的数组

    第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

    当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

    第三个可选的参数是一个对象,支持以下这些选项:

    • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 None
    • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器
    • flush:调整回调函数的刷新时机。参考回调的刷新时机watchEffect()
    • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器
    • once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。

    watchEffect() 相比,watch() 使我们可以:

    • 懒执行副作用;
    • 更加明确是应该由哪个状态触发侦听器重新执行;
    • 可以访问所侦听状态的前一个值和当前值。
  • 示例

    侦听一个 getter 函数:

    py
    state = reactive({ 'count': 0 })
    
    def getter():
      return state.count
    
    @watch(getter)
    def handle(count, prev_count, on_cleanup):
        ...

    侦听一个 ref:

    py
    count = ref(0)
    
    @watch(count)
    def handle(count, prevCount):
        ...

    当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

    py
    # <!-- todo add test case -->
    watch([fooRef, barRef])
    def handle([foo, bar], [prevFoo, prevBar]):
        ...

    当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 deep=True 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

    py
    # <!-- todo add test case -->
    state = reactive({ 'count': 0 })
    
    @watch(state, WatchOptions(deep=True))
    def handle(new_val, old_val, on_cleanup):
        # new_val is old_val 
        ...

    当直接侦听一个响应式对象时,侦听器会自动启用深层模式:

    py
    state = reactive({ 'count': 0 })
    
    @watch(state)
    def handle(new_val, old_val, on_cleanup):
      # 深层级变更状态所触发的回调
      ...

    watch()watchEffect() 享有相同的刷新时机和调试选项:

    py
    def onTrack(info):
        print(info)
        
    def onTrigger(info):
        print(info)
    
    @watch(source, WatchOptions(deep=True, onTrack=onTrack onTrigger=onTrigger))
    def callback(new_val, old_val, on_cleanup):
        ...

    停止侦听器:

    py
    @watch(source)
    def callback(new_val, old_val, on_cleanup):
        ...
    
    # 重命名便于理解
    stop = callback
    
    # 当已不再需要该侦听器时:
    stop()

    副作用清理:

    py
    @watch(query_id)
    def query(newId, oldId, on_cleanup):
      response, cancel = doAsyncWork(newId)
      # 当 `query_id` 变化时,`cancel` 将被调用,
      # 取消之前的未完成的请求
      on_cleanup(cancel)
      data.value = response()
  • 参考

响应式 API:核心已经加载完毕