Attributes
In Qingkuai, you can add attributes to component tags just as you do with normal HTML tags to pass parameters. This is called passing component attributes, and it is used to pass external data or configuration into a component. Through component attributes, a component can behave differently or present itself differently in different scenarios, which improves both reusability and flexibility.
Static Attributes
Static attributes are passed to components as strings. Inside the component, external attribute values are accessed through the built-in props identifier:
- js
- ts
<!-- Outer.qk -->
<Inner attr="value" >
<!-- Inner.qk -->
<lang-js>
console.log(props.attr) // logs: value
</lang-js>
<!-- Outer.qk -->
<Inner attr="value" >
<!-- Inner.qk -->
<lang-ts>
interface Props {
attr: string
}
console.log(props.attr) // logs: value
</lang-ts>
If you add an attribute name to a component tag without giving it a value, the component receives the boolean value true internally:
- js
- ts
<!-- Outer.qk -->
<Inner attr />
<!-- Inner.qk -->
<lang-js>
console.log(props.attr) // logs: true
</lang-js>
<!-- Outer.qk -->
<Inner attr />
<!-- Inner.qk -->
<lang-ts>
interface Props {
attr?: boolean
}
console.log(props.attr) // logs: true
</lang-ts>
Dynamic Attributes
Dynamic attributes passed to a component are also accessed through the built-in props identifier. Unlike static attributes, however, dynamic attributes can pass more than just strings. They can pass booleans, objects, and other kinds of data. When the data source changes, DOM elements inside the component that use that attribute are updated accordingly:
- js
- ts
<!-- Outer.qk -->
<lang-js>
const list = ["js", "ts", "qk"]
setTimeout(list.pop, 1000)
</lang-js>
<Inner !list />
<!-- Inner.qk -->
<p>The length of list is: {props.list.length}</p>
<!-- Outer.qk -->
<lang-ts>
const list = ["js", "ts", "qk"]
setTimeout(list.pop, 1000)
</lang-ts>
<Inner !list />
<!-- Inner.qk -->
<lang-ts>
interface Props {
list: string[]
}
</lang-ts>
<p>The length of list is: {props.list.length}</p>
Events
Component events are accessed through the built-in props identifier inside a component, just like other non-reference attributes:
- js
- ts
<!-- Outer.qk -->
<Inner @someThingHappened={console.log($arg)} />
<!-- Inner.qk -->
<lang-js>
setTimeout(() => {
props.someThingHappened("event is triggered.")
// logs: event is triggered.
}, 1000)
</lang-js>
<!-- Outer.qk -->
<Inner @someThingHappened={console.log($arg)} />
<!-- Inner.qk -->
<lang-ts>
interface Props {
someThingHappened: (msg: string) => void
}
setTimeout(() => {
props.someThingHappened("event is triggered.")
// logs: event is triggered.
}, 1000)
</lang-ts>
Reference Attributes
Reference attributes are an important capability in components because they allow a component to modify values passed in from outside. Since the properties on the props object are essentially read-only getters, they cannot be modified directly. In that case, the reference-passing mechanism provided by reference attributes is required. Qingkuai provides the built-in refs identifier inside component files for accessing externally passed reference attributes. By modifying properties on refs, you can synchronize changes back to external data and trigger its reactive updates. Here is a simple example:
- js
- ts
<!-- Outer.qk -->
<lang-js>
import Inner from "./Inner.qk"
let name = "JavaScript"
</lang-js>
<p>name is: {name}</p>
<Inner &attr={name} />
<!-- Inner.qk -->
<p>refs.attr is: {refs.attr}</p>
<button @click={refs.attr = "Qingkuai"}>Change refs.attr</button>
<!-- Outer.qk -->
<lang-ts>
import Inner from "./Inner.qk"
let name = "JavaScript"
</lang-ts>
<p>name is: {name}</p>
<Inner &attr={name} />
<!-- Inner.qk -->
<lang-ts>
interface Refs {
attr: string
}
</lang-ts>
<p>refs.attr is: {refs.attr}</p>
<button @click={refs.attr = "Qingkuai"}>Change refs.attr</button>
props is itself a complex type such as an object or array, its internal data can still be modified technically. For example, when props.userInfo is an object, props.userInfo.name can still be reassigned. However, this is not recommended, because it makes component state harder to track and maintain.
Attribute Destructuring
Values obtained by destructuring the built-in props or refs objects directly do not have reactive capability themselves. In the following example, access to str is not reactive, because this does not trigger the getter on the props property access:
const { str } = props
If you need to destructure component attributes while preserving reactivity, use the compiler built-in alias together with destructuring:
const { str } = alias(props)
// Accessing str is reactive and equivalent to accessing props.str
Likewise, values obtained by destructuring the built-in refs object with alias are also reactive:
let { str } = alias(refs)
// Accessing or writing str is reactive and equivalent to accessing or writing refs.str
Specifying Default Values
Component attributes support default values. When a parent component does not pass a certain attribute, the component can specify a default value internally to ensure that it still works correctly. Through the compiler built-ins defaultProps and defaultRefs, you can declare default values for component attributes:
defaultRefs({
checked: false
})
defaultProps({
age: 0,
name: "Unknown",
description: "This is a default user info."
})
Attribute Name Format
Just like component names, Qingkuai component attribute names support both kebab-case and camelCase. The following two forms are equivalent:
<Component myAttr />
<Component my-attr />
By default, formatting a component file rewrites kebab-case component attribute and event names into camelCase. However, you can add a .prettierrc file in the component file's directory or one of its parent directories and use the following content to change the preferred format to kebab-case:
{
"qingkuai": {
"componentAttributeFormatPreference": "kebab"
}
}