Skip to content

React 属性 Props

概述

Props(properties 的缩写)是 React 组件间传递数据的机制。Props 使得组件可复用且灵活,是构建动态 React 应用的基础。本章将深入学习 Props 的使用方法、验证和最佳实践。

🔄 Props 基础

什么是 Props

jsx
// Props 是从父组件传递给子组件的数据
function Greeting({ name, age }) {
    return (
        <div>
            <h2>你好,{name}!</h2>
            <p>你今年 {age} 岁了</p>
        </div>
    );
}

function App() {
    return (
        <div>
            <Greeting name="张三" age={25} />
            <Greeting name="李四" age={30} />
        </div>
    );
}

Props 的特点

  • 只读性:组件不能修改自己的 props
  • 单向数据流:数据从父组件流向子组件
  • 动态性:props 可以是变量、表达式或函数

📦 Props 类型

基本数据类型

jsx
function UserCard({
    name,           // string
    age,            // number
    isActive,       // boolean
    hobbies,        // array
    profile,        // object
    onEdit          // function
}) {
    return (
        <div className={`user-card ${isActive ? 'active' : 'inactive'}`}>
            <h3>{name}</h3>
            <p>年龄: {age}</p>
            <p>状态: {isActive ? '在线' : '离线'}</p>
            
            <div className="hobbies">
                <h4>爱好:</h4>
                <ul>
                    {hobbies.map((hobby, index) => (
                        <li key={index}>{hobby}</li>
                    ))}
                </ul>
            </div>
            
            <p>邮箱: {profile.email}</p>
            
            <button onClick={() => onEdit(profile.id)}>
                编辑用户
            </button>
        </div>
    );
}

// 使用组件
function App() {
    const user = {
        id: 1,
        email: 'zhangsan@example.com',
        address: '北京市朝阳区'
    };
    
    const handleEdit = (userId) => {
        console.log(`编辑用户 ID: ${userId}`);
    };
    
    return (
        <UserCard
            name="张三"
            age={28}
            isActive={true}
            hobbies={['读书', '游泳', '编程']}
            profile={user}
            onEdit={handleEdit}
        />
    );
}

特殊的 Props

children 属性

jsx
// children 是特殊的 prop,表示组件的子元素
function Card({ title, children }) {
    return (
        <div className="card">
            <h3 className="card-title">{title}</h3>
            <div className="card-content">
                {children}
            </div>
        </div>
    );
}

function App() {
    return (
        <div>
            <Card title="用户信息">
                <p>姓名: 张三</p>
                <p>邮箱: zhangsan@example.com</p>
                <button>编辑</button>
            </Card>
            
            <Card title="产品列表">
                <ul>
                    <li>产品 A</li>
                    <li>产品 B</li>
                    <li>产品 C</li>
                </ul>
            </Card>
        </div>
    );
}

render props 模式

jsx
function DataProvider({ render, url }) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        fetch(url)
            .then(response => response.json())
            .then(data => {
                setData(data);
                setLoading(false);
            });
    }, [url]);
    
    return render({ data, loading });
}

// 使用 render props
function App() {
    return (
        <DataProvider
            url="/api/users"
            render={({ data, loading }) => (
                <div>
                    {loading ? (
                        <p>加载中...</p>
                    ) : (
                        <ul>
                            {data?.map(user => (
                                <li key={user.id}>{user.name}</li>
                            ))}
                        </ul>
                    )}
                </div>
            )}
        />
    );
}

⚙️ Props 默认值

使用默认参数

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

// 使用默认值
function App() {
    return (
        <div>
            <Button>默认按钮</Button>
            <Button variant="secondary" size="large">
                次要按钮
            </Button>
            <Button variant="danger" disabled>
                禁用按钮
            </Button>
        </div>
    );
}

使用 defaultProps(类组件)

jsx
class Welcome extends Component {
    static defaultProps = {
        name: '访客',
        showTime: false
    };
    
    render() {
        const { name, showTime } = this.props;
        return (
            <div>
                <h1>欢迎,{name}!</h1>
                {showTime && (
                    <p>当前时间: {new Date().toLocaleString()}</p>
                )}
            </div>
        );
    }
}

🔍 Props 解构和传递

Props 解构

jsx
// 基础解构
function UserInfo({ user: { name, email, age } }) {
    return (
        <div>
            <h2>{name}</h2>
            <p>邮箱: {email}</p>
            <p>年龄: {age}</p>
        </div>
    );
}

// 重命名解构
function ProductCard({ 
    product: { 
        name: productName, 
        price: productPrice 
    },
    currency = '¥'
}) {
    return (
        <div>
            <h3>{productName}</h3>
            <p>价格: {currency}{productPrice}</p>
        </div>
    );
}

// 剩余参数
function Input({ label, error, ...inputProps }) {
    return (
        <div className="form-group">
            <label>{label}</label>
            <input {...inputProps} />
            {error && <span className="error">{error}</span>}
        </div>
    );
}

// 使用示例
function LoginForm() {
    return (
        <form>
            <Input
                label="用户名"
                type="text"
                placeholder="请输入用户名"
                required
            />
            <Input
                label="密码"
                type="password"
                placeholder="请输入密码"
                error="密码长度至少 6 位"
                required
            />
        </form>
    );
}

Props 传递模式

jsx
// 透传 props
function FormInput(props) {
    return <input className="form-input" {...props} />;
}

// 选择性传递
function CustomButton({ variant, size, children, ...buttonProps }) {
    const className = `btn btn-${variant} btn-${size}`;
    
    return (
        <button className={className} {...buttonProps}>
            {children}
        </button>
    );
}

// 组合传递
function IconButton({ icon, children, ...props }) {
    return (
        <CustomButton {...props}>
            <span className="icon">{icon}</span>
            {children}
        </CustomButton>
    );
}

🛡️ Props 验证

PropTypes(开发环境)

jsx
import PropTypes from 'prop-types';

function UserProfile({ 
    user, 
    showEmail, 
    onEdit, 
    theme 
}) {
    return (
        <div className={`user-profile theme-${theme}`}>
            <h2>{user.name}</h2>
            <p>年龄: {user.age}</p>
            {showEmail && <p>邮箱: {user.email}</p>}
            <button onClick={() => onEdit(user.id)}>编辑</button>
        </div>
    );
}

UserProfile.propTypes = {
    user: PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        age: PropTypes.number.isRequired,
        email: PropTypes.string
    }).isRequired,
    showEmail: PropTypes.bool,
    onEdit: PropTypes.func.isRequired,
    theme: PropTypes.oneOf(['light', 'dark'])
};

UserProfile.defaultProps = {
    showEmail: false,
    theme: 'light'
};

TypeScript 类型定义

tsx
interface User {
    id: number;
    name: string;
    age: number;
    email?: string;
}

interface UserProfileProps {
    user: User;
    showEmail?: boolean;
    onEdit: (userId: number) => void;
    theme?: 'light' | 'dark';
}

function UserProfile({ 
    user, 
    showEmail = false, 
    onEdit, 
    theme = 'light' 
}: UserProfileProps) {
    return (
        <div className={`user-profile theme-${theme}`}>
            <h2>{user.name}</h2>
            <p>年龄: {user.age}</p>
            {showEmail && user.email && <p>邮箱: {user.email}</p>}
            <button onClick={() => onEdit(user.id)}>编辑</button>
        </div>
    );
}

🎯 Props 最佳实践

1. 保持 Props 接口简洁

jsx
// ❌ 过多的 props
function ComplexComponent({
    title, subtitle, content, imageUrl, imageAlt,
    showImage, showSubtitle, titleColor, contentColor,
    onClick, onHover, onFocus, className, style
}) {
    // 复杂的组件实现
}

// ✅ 使用对象组织相关 props
function BetterComponent({
    content: { title, subtitle, body },
    image: { url, alt, show = true },
    styling: { titleColor, contentColor, className, style },
    handlers: { onClick, onHover, onFocus }
}) {
    // 更清晰的组件实现
}

2. 合理使用回调函数

jsx
function TodoItem({ todo, onUpdate }) {
    const handleToggle = () => {
        onUpdate(todo.id, { completed: !todo.completed });
    };
    
    const handleEdit = (newText) => {
        onUpdate(todo.id, { text: newText });
    };
    
    return (
        <div className="todo-item">
            <input
                type="checkbox"
                checked={todo.completed}
                onChange={handleToggle}
            />
            <EditableText
                text={todo.text}
                onSave={handleEdit}
            />
        </div>
    );
}

3. 避免 Props 突变

jsx
// ❌ 错误:修改 props
function BadComponent({ items }) {
    items.push({ id: Date.now(), text: 'new item' }); // 不要这样做
    return <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>;
}

// ✅ 正确:创建新数组
function GoodComponent({ items, onAddItem }) {
    const handleAdd = () => {
        const newItem = { id: Date.now(), text: 'new item' };
        onAddItem(newItem);
    };
    
    return (
        <div>
            <ul>
                {items.map(item => (
                    <li key={item.id}>{item.text}</li>
                ))}
            </ul>
            <button onClick={handleAdd}>添加项目</button>
        </div>
    );
}

📊 性能考虑

使用 memo 优化

jsx
import { memo } from 'react';

const ExpensiveItem = memo(function Item({ item, onSelect }) {
    console.log(`渲染项目: ${item.id}`);
    
    return (
        <div onClick={() => onSelect(item.id)}>
            <h3>{item.title}</h3>
            <p>{item.description}</p>
        </div>
    );
});

// 自定义比较函数
const OptimizedItem = memo(function Item({ item, selected }) {
    return (
        <div className={selected ? 'selected' : ''}>
            <h3>{item.title}</h3>
        </div>
    );
}, (prevProps, nextProps) => {
    return prevProps.item.id === nextProps.item.id &&
           prevProps.selected === nextProps.selected;
});

📝 本章小结

Props 是 React 组件通信的核心机制,掌握 Props 的使用对于构建可维护的 React 应用至关重要。

关键要点

  • ✅ Props 是只读的,用于父子组件通信
  • ✅ 支持各种数据类型和函数传递
  • ✅ children 是特殊的 prop
  • ✅ 可以设置默认值和进行类型验证
  • ✅ 合理的 props 设计提高组件可用性

最佳实践

  1. 保持 Props 接口简洁明了
  2. 使用 TypeScript 或 PropTypes 进行类型检查
  3. 避免过度传递 props
  4. 合理使用默认值
  5. 考虑性能影响,适当使用 memo

继续学习下一章 - React 状态 State

本站内容仅供学习和研究使用。