Props属性 #

一、Props 基础 #

1.1 什么是 Props #

Props(Properties)是组件的输入属性,用于父组件向子组件传递数据。

jsx
// 定义接收 props 的组件
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 传递 props
<Greeting name="World" />

1.2 Props 特点 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Props 特点                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 只读性                                                 │
│      └── 子组件不应修改 props                               │
│                                                             │
│   2. 响应式                                                 │
│      └── 传递 Signal 时保持响应性                           │
│                                                             │
│   3. 任意类型                                               │
│      └── 可以传递任何 JavaScript 值                         │
│                                                             │
│   4. 默认值                                                 │
│      └── 可以为 props 设置默认值                            │
│                                                             │
│   5. 解构                                                   │
│      └── 可以解构 props 对象                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、传递 Props #

2.1 基本传递 #

jsx
function Button(props) {
  return (
    <button
      type={props.type}
      disabled={props.disabled}
      onClick={props.onClick}
    >
      {props.label}
    </button>
  );
}

<Button
  type="submit"
  label="Click me"
  disabled={false}
  onClick={() => console.log('clicked')}
/>

2.2 解构 Props #

jsx
function Button({ type = 'button', label, disabled = false, onClick }) {
  return (
    <button type={type} disabled={disabled} onClick={onClick}>
      {label}
    </button>
  );
}

// 注意:解构会失去响应性
// 如果传递的是 Signal,需要特殊处理

2.3 展开传递 #

jsx
function Input(props) {
  return <input {...props} />;
}

<Input
  type="text"
  placeholder="Enter text"
  value={text()}
  onInput={handleInput}
/>

2.4 传递 Signal #

jsx
function Parent() {
  const [count, setCount] = createSignal(0);

  return (
    <Child
      count={count}      // 传递 Signal 本身
      setCount={setCount}
    />
  );
}

function Child(props) {
  // 方式1:调用 Signal
  return <p>Count: {props.count()}</p>;
  
  // 方式2:传递 Signal 值(失去响应性)
  // return <p>Count: {props.count}</p>; // 不会更新
}

三、children 属性 #

3.1 基本用法 #

jsx
function Card(props) {
  return (
    <div class="card">
      {props.children}
    </div>
  );
}

<Card>
  <h2>Title</h2>
  <p>Content</p>
</Card>

3.2 children 是函数 #

jsx
function List(props) {
  return (
    <ul>
      {props.children}
    </ul>
  );
}

<List>
  <li>Item 1</li>
  <li>Item 2</li>
</List>

3.3 children 类型检查 #

jsx
function Wrapper(props) {
  // children 可能是:
  // - 单个元素
  // - 数组
  // - 函数
  // - undefined
  
  const children = props.children;
  
  if (typeof children === 'function') {
    return children();
  }
  
  if (Array.isArray(children)) {
    return <div>{children}</div>;
  }
  
  return children || null;
}

四、默认值 #

4.1 解构默认值 #

jsx
function Button({
  type = 'button',
  variant = 'primary',
  size = 'medium',
  disabled = false,
  children
}) {
  return (
    <button
      type={type}
      class={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

<Button>Default Button</Button>
<Button variant="danger" size="large">Delete</Button>

4.2 函数默认值 #

jsx
function Input(props) {
  const type = props.type ?? 'text';
  const placeholder = props.placeholder ?? 'Enter value';
  const onChange = props.onChange ?? (() => {});

  return (
    <input
      type={type}
      placeholder={placeholder}
      onChange={onChange}
    />
  );
}

4.3 使用 mergeProps #

jsx
import { mergeProps } from 'solid-js';

function Button(props) {
  const merged = mergeProps({
    type: 'button',
    variant: 'primary',
    size: 'medium',
    disabled: false
  }, props);

  return (
    <button
      type={merged.type}
      class={`btn btn-${merged.variant} btn-${merged.size}`}
      disabled={merged.disabled}
    >
      {merged.children}
    </button>
  );
}

4.4 使用 splitProps #

jsx
import { splitProps } from 'solid-js';

function Input(props) {
  // 分离本地 props 和传递给元素的 props
  const [local, others] = splitProps(props, [
    'label',
    'error',
    'helperText'
  ]);

  return (
    <div class="input-wrapper">
      <label>{local.label}</label>
      <input {...others} />
      {local.error && <span class="error">{local.error}</span>}
      {local.helperText && <span class="helper">{local.helperText}</span>}
    </div>
  );
}

五、TypeScript 类型 #

5.1 基本类型定义 #

tsx
interface ButtonProps {
  type?: 'button' | 'submit' | 'reset';
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  onClick?: () => void;
  children: JSX.Element;
}

function Button(props: ButtonProps) {
  return (
    <button
      type={props.type ?? 'button'}
      class={`btn btn-${props.variant ?? 'primary'}`}
      disabled={props.disabled}
      onClick={props.onClick}
    >
      {props.children}
    </button>
  );
}

5.2 泛型 Props #

tsx
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => JSX.Element;
  keyFn?: (item: T) => string | number;
}

function List<T>(props: ListProps<T>) {
  return (
    <ul>
      <For each={props.items}>
        {(item, index) => props.renderItem(item, index())}
      </For>
    </ul>
  );
}

// 使用
<List
  items={users}
  renderItem={(user) => <li>{user.name}</li>}
/>

5.3 Signal 类型 #

tsx
interface CounterProps {
  count: () => number;      // Signal getter
  setCount: (value: number) => void;  // Signal setter
}

function Counter(props: CounterProps) {
  return (
    <div>
      <p>Count: {props.count()}</p>
      <button onClick={() => props.setCount(props.count() + 1)}>
        Increment
      </button>
    </div>
  );
}

5.4 组件类型 #

tsx
// 函数组件类型
type ButtonComponent = (props: ButtonProps) => JSX.Element;

const Button: ButtonComponent = (props) => {
  return <button>{props.children}</button>;
};

六、高级用法 #

6.1 Props 验证 #

tsx
function validateProps<T extends Record<string, unknown>>(
  props: T,
  validators: Partial<Record<keyof T, (value: unknown) => boolean>>
): T {
  for (const key in validators) {
    const validator = validators[key];
    if (validator && !validator(props[key])) {
      console.warn(`Invalid prop "${key}": ${props[key]}`);
    }
  }
  return props;
}

function UserCard(props: { name: string; age: number }) {
  validateProps(props, {
    name: (v) => typeof v === 'string' && v.length > 0,
    age: (v) => typeof v === 'number' && v >= 0
  });

  return (
    <div>
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
    </div>
  );
}

6.2 Props 代理 #

jsx
function withLogging(Component) {
  return (props) => {
    console.log('Props:', props);
    return <Component {...props} />;
  };
}

const LoggedButton = withLogging(Button);

<LoggedButton label="Click" onClick={handleClick} />

6.3 动态 Props #

jsx
function DynamicProps() {
  const [props, setProps] = createSignal({
    color: 'red',
    size: 'large'
  });

  return <Button {...props()} />;
}

七、常见模式 #

7.1 回调 Props #

jsx
function SearchInput(props) {
  const [value, setValue] = createSignal('');

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    props.onSearch?.(newValue);
  };

  return (
    <input
      value={value()}
      onInput={handleChange}
      placeholder={props.placeholder}
    />
  );
}

// 使用
<SearchInput
  placeholder="Search..."
  onSearch={(query) => console.log('Search:', query)}
/>

7.2 Render Props #

jsx
function DataFetcher(props) {
  const [data] = createResource(props.url);

  return (
    <Show when={data()} fallback={<p>Loading...</p>}>
      {(d) => props.render(d())}
    </Show>
  );
}

// 使用
<DataFetcher
  url="/api/users"
  render={(users) => (
    <ul>
      <For each={users}>{(user) => <li>{user.name}</li>}</For>
    </ul>
  )}
/>

7.3 插槽模式 #

jsx
function Modal(props) {
  return (
    <div class="modal">
      <div class="modal-header">
        {props.header ?? <h3>Modal</h3>}
      </div>
      <div class="modal-body">
        {props.children}
      </div>
      <div class="modal-footer">
        {props.footer ?? (
          <button onClick={props.onClose}>Close</button>
        )}
      </div>
    </div>
  );
}

// 使用
<Modal
  header={<h2>Confirm</h2>}
  footer={
    <>
      <button onClick={handleCancel}>Cancel</button>
      <button onClick={handleConfirm}>Confirm</button>
    </>
  }
  onClose={handleClose}
>
  <p>Are you sure?</p>
</Modal>

八、最佳实践 #

8.1 Props 命名 #

jsx
// 好的命名
<Button
  isLoading={loading()}     // 布尔值用 is/has 前缀
  onClick={handleClick}     // 事件用 on 前缀
  variant="primary"         // 枚举用语义化名称
  className="btn"           // 与 HTML 属性区分
/>

// 避免的命名
<Button
  loading={loading()}       // 不清晰
  click={handleClick}       // 不符合惯例
  type={1}                  // 魔法数字
/>

8.2 避免过度传递 #

jsx
// 避免:层层传递 props
function GrandParent() {
  const data = 'data';
  return <Parent data={data} />;
}

function Parent(props) {
  return <Child data={props.data} />;
}

function Child(props) {
  return <p>{props.data}</p>;
}

// 推荐:使用 Context
const DataContext = createContext();

function GrandParent() {
  return (
    <DataContext.Provider value="data">
      <Parent />
    </DataContext.Provider>
  );
}

function Child() {
  const data = useContext(DataContext);
  return <p>{data}</p>;
}

8.3 Props 文档化 #

tsx
/**
 * Button 组件
 * 
 * @param props - 组件属性
 * @param props.variant - 按钮样式变体
 * @param props.size - 按钮尺寸
 * @param props.disabled - 是否禁用
 * @param props.onClick - 点击回调
 * @param props.children - 按钮内容
 */
function Button(props: ButtonProps) {
  // ...
}

九、总结 #

9.1 Props 核心 API #

API 说明
props.name 访问属性
props.children 访问子内容
mergeProps 合并默认值
splitProps 分离属性

9.2 Props 类型 #

类型 说明
基本类型 string, number, boolean
对象/数组 复杂数据结构
函数 回调函数
Signal 响应式状态
JSX.Element React 元素

9.3 最佳实践 #

  1. 保持只读:不修改 props
  2. 设置默认值:提供合理默认
  3. 类型定义:使用 TypeScript
  4. 文档化:添加 JSDoc 注释
  5. 避免过度传递:使用 Context
最后更新:2026-03-28