Reactivity Inference Rules

In Qingkuai, the compiler uses a set of inference rules to determine the reactivity type of each identifier automatically. Understanding these rules helps developers manage state more precisely and override default behavior when necessary through explicit markers.


Inference Flow

For each identifier in the top-level scope of a script block, the compiler performs reactivity inference in the following order:

  1. Check explicit markers: if a variable declaration calls reactive, shallow, or raw in its initializer, that explicit marker takes priority.

  2. Apply implicit rules: if no explicit marker is used, inference is based on how the identifier is used in the template and how it is declared.


Explicit Markers

Built-in reactivity markers must be called in the initializer of a variable declaration. When an identifier uses one of these built-in methods as an explicit marker, the compiler infers the corresponding reactivity type with priority:

qk
<lang-ts>
    const config = raw([1, 2, 3])               // raw value
    const list = shallow({ debug: false })      // shallow reactive
    const user = reactive({ name: "Qingkuai" }) // deeply reactive
</lang-ts>

Degeneration

Even when an explicit marker is used, the identifier degenerates into a raw value and the marker is ignored if both of the following conditions are met:

qk
<lang-ts>
    const a = shallow(1)    // degenerates into a raw value; shallow is ignored
    const b = reactive("")  // degenerates into a raw value; reactive is ignored
    const c = reactive({})  // inferred normally; final reactivity depends on later usage
</lang-ts>

If degeneration does not occur, the identifier is inferred as the reactivity type specified by the explicit marker.


Aliases and Derived Values

These rules are independent of the explicit marking flow for reactive, shallow, and raw:

qk
<lang-ts>
    const firstName = reactive("Qing")
    const lastName = reactive("kuai")

    const userName = alias(props.userInfo.name)

    const fullName = derived(() => firstName + " " + lastName)
    const shortName = derivedExp(firstName + "-" + lastName)

</lang-ts>

Implicit Inference

When an identifier does not use any explicit marker, the compiler applies the following implicit rules.

Not Used in the Template

If an identifier is not accessed in the template, the compiler infers it as a raw value. Such identifiers exist only in script logic and do not participate in dependency collection and update scheduling.

qk
<lang-ts>
    let count = 0     // never used in the template -> raw value
    let message = ""  // never used in the template -> raw value
</lang-ts>

<p> count and message are not accessed here </p>

Used in the Template

If an identifier is accessed in the template and is declared with let or var with a literal initial value, the compiler also checks whether it is modified anywhere in the script:

qk
<lang-ts>
    let a = 1    // used in the template, but never modified -> raw value
    let b = 0    // used in the template and modified -> type depends on the current reactivity mode
    function increment() {
        b++
    }
</lang-ts>

<p>{ a }</p>
<button @click={increment}>{ b }</button>

Other Declaration Forms

For non-variable declarations such as class declarations, function declarations, and TypeScript enum declarations, the compiler treats them as mutable declarations during implicit inference.


Inference Hints

If the Qingkuai VS Code extension is installed, hovering over an identifier in top-level scope shows the reactivity type inferred by the compiler in the language server tooltip:

vscode-hover vscode-hover vscode-hover

Edit this page on github (This page has been translated from the Chinese version of the site. There may be inaccuracies in the translation. We welcome your help to improve the accuracy of this document.)