组件属性

组件属性(props)是父组件向子组件传递数据的主要方式。在 Vitarx 中,props 通过函数参数接收,且是只读的。

声明组件 Props

使用 TypeScript 接口或类型别名来声明组件的 props:

tsx
import { ref } from 'vitarx'

// 用接口声明 props 类型
interface GreetingProps {
  name: string
  age?: number // 可选属性
}

function Greeting(props: GreetingProps) {
  return (
    <div>
      <p>你好,{props.name}!</p>
      {props.age && <p>年龄:{props.age}</p>}
    </div>
  )
}

// 使用组件
function App() {
  return <Greeting name="小明" age={18} />
}

也可以直接在参数中声明类型:

tsx
function Greeting(props: { name: string; age?: number }) {
  return <div>你好,{props.name}!</div>
}

Props 是只读的

组件接收到的 props 对象是只读的,不要直接修改它。在开发模式下,尝试修改 props 会抛出错误。

tsx
// ❌ 错误:不要修改 props
function BadComponent(props: { count: number }) {
  props.count = 10 // 开发模式下会报错
  return <div>{props.count}</div>
}

// ✅ 正确:用 ref 管理本地状态
import { ref } from 'vitarx'

function GoodComponent(props: { count: number }) {
  const localCount = ref(props.count)
  return (
    <div>
      <span>{localCount}</span>
      <button
        onClick={() => {
          localCount.value++
        }}
      >
        增加
      </button>
    </div>
  )
}

内置特殊属性

Vitarx 为所有元素和组件提供了一些内置的特殊属性:

ref — 引用元素或组件实例

通过 ref 属性可以获取 DOM 元素或组件实例的引用:

tsx
import { useRef, onMounted } from 'vitarx'

function App() {
  const elRef = useRef<HTMLDivElement>()

  onMounted(() => {
    // 挂载后可以访问 DOM 元素
    console.log(elRef.value?.textContent)
  })

  return <div ref={elRef}>Hello</div>
}

详细的 ref 用法请参考 组件引用

children — 子节点

组件标签内的内容会通过 children 属性传递给组件:

tsx
function Card(props: { title: string; children?: any }) {
  return (
    <div class="card">
      <h2>{props.title}</h2>
      <div class="card-body">{props.children}</div>
    </div>
  )
}

// 使用
function App() {
  return (
    <Card title="用户信息">
      <p>这是卡片的内容</p>
    </Card>
  )
}

详细的 children 用法请参考 子节点与插槽

v-bind — 属性透传绑定

v-bind 用于将一个对象的所有属性绑定到元素上,常用于属性透传场景:

tsx
// v-bind 接受一个对象,将所有属性绑定到元素
function MyButton(props: { class?: string; onClick?: () => void }) {
  return <button v-bind={props}>按钮</button>
}

// 也可以排除某些属性
function MyInput(props: { class?: string; value?: string; onChange?: () => void }) {
  return (
    <input
      v-bind={[props, ['onChange']]} // 透传 props,但排除 onChange
      onInput={(e) => {
        /* 自定义处理 */
      }}
    />
  )
}

v-bind 的两种形式:

  • 对象形式v-bind={obj} — 将 obj 的所有属性绑定到元素
  • 数组形式v-bind={[obj, exclude]} — 绑定 obj 的属性,但排除 exclude 数组中指定的属性

注意:v-bind 不能用于绑定全局属性(如 ref、children)。

v-html — 绑定 HTML 内容

v-html 用于设置元素的 innerHTML,可以传入字符串或 ref:

tsx
import { ref } from 'vitarx'

function RichText() {
  const html = ref('<strong>加粗文本</strong>')

  return <div v-html={html} />
}

v-html 仅支持用于 <div><p><span> 等块级 HTML 元素。注意防范 XSS 攻击,不要将用户输入直接作为 HTML 渲染。

v-show — 显示/隐藏元素

v-show 通过设置 display: none 来控制元素的显示与隐藏:

tsx
import { ref } from 'vitarx'

function TogglePanel() {
  const visible = ref(true)

  return (
    <div>
      <button
        onClick={() => {
          visible.value = !visible.value
        }}
      >
        切换
      </button>
      <div v-show={visible}>这段内容可以显示或隐藏</div>
    </div>
  )
}

v-text — 绑定文本内容

v-text 用于设置元素的文本内容:

tsx
import { ref } from 'vitarx'

function Message() {
  const text = ref('Hello World')
  return <div v-text={text} />
}

默认值 — defaultProps

当外部未传入某个属性时,可以通过组件的 defaultProps 静态属性提供默认值:

tsx
import { ref } from 'vitarx'

interface ButtonProps {
  type?: 'primary' | 'default'
  size?: 'small' | 'medium' | 'large'
  disabled?: boolean
  children?: any
}

function Button(props: ButtonProps) {
  // props.type、props.size、props.disabled 都会有默认值
  const classList = `btn btn-#123;props.type} btn-#123;props.size}`

  return (
    <button class={classList} disabled={props.disabled}>
      {props.children}
    </button>
  )
}

// 设置默认属性
Button.defaultProps = {
  type: 'default',
  size: 'medium',
  disabled: false
}

// 使用 — 不传 type 和 size 时使用默认值
function App() {
  return (
    <div>
      <Button>默认按钮</Button>
      <Button type="primary" size="large">
        主要按钮
      </Button>
    </div>
  )
}

属性验证 — defineValidate

defineValidate 用于在开发模式下校验传入的 props 是否符合预期。校验只在开发模式下生效,生产模式下不会执行。

校验函数的返回值:

  • false:打印默认的校验失败错误信息
  • string:打印该字符串作为警告信息
  • 抛出异常:阻止组件继续渲染
  • 其他值 / void:校验通过
tsx
import { defineValidate } from 'vitarx'

interface UserProps {
  name: string
  age: number
  email?: string
}

function UserCard(props: UserProps) {
  return (
    <div>
      <p>姓名:{props.name}</p>
      <p>年龄:{props.age}</p>
      {props.email && <p>邮箱:{props.email}</p>}
    </div>
  )
}

// 定义属性验证
defineValidate(UserCard, (props) => {
  if (!props.name) {
    return 'name 属性不能为空'
  }
  if (props.age < 0) {
    return 'age 不能为负数'
  }
  if (props.age > 150) {
    return 'age 不合理,请检查'
  }
  // 返回 void 表示校验通过
})

完整示例

下面是一个综合运用 props 各项特性的完整示例:

tsx
import { ref, defineValidate } from 'vitarx'

// 声明 props 类型
interface AlertProps {
  type?: 'info' | 'success' | 'warning' | 'error'
  title: string
  closable?: boolean
  children?: any
}

function Alert(props: AlertProps) {
  const visible = ref(true)

  const handleClose = () => {
    visible.value = false
  }

  // 根据 type 选择样式类名
  const alertClass = `alert alert-#123;props.type}`

  return (
    <div class={alertClass} v-show={visible}>
      <div class="alert-header">
        <span class="alert-title">{props.title}</span>
        {props.closable && (
          <button class="alert-close" onClick={handleClose}>
            ×
          </button>
        )}
      </div>
      <div class="alert-body">{props.children}</div>
    </div>
  )
}

// 设置默认值
Alert.defaultProps = {
  type: 'info',
  closable: false
}

// 属性验证
defineValidate(Alert, (props) => {
  if (!props.title) {
    return 'Alert 组件的 title 属性是必填的'
  }
})

// 使用
function App() {
  return (
    <div>
      <Alert title="提示">这是一条普通提示信息</Alert>
      <Alert type="success" title="成功" closable>
        操作已完成!
      </Alert>
      <Alert type="error" title="错误" closable>
        操作失败,请重试。
      </Alert>
    </div>
  )
}

下一步