Урок 04

Кастомные компоненты

Создание переиспользуемых компонентов с пропсами.

Простой компонент

type CardProps = {
    children: React.ReactNode;
};

export function Card({ children }: CardProps) {
    return <View style={styles.card}>{children}</View>;
}

const styles = StyleSheet.create({
    card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        marginBottom: 12,
    },
});

С вариантами

type BadgeProps = {
    label: string;
    variant?: 'success' | 'error' | 'warning';
};

export function Badge({ label, variant = 'success' }: BadgeProps) {
    return (
        <View style={[styles.base, styles[variant]]}>
            <Text style={styles.label}>{label}</Text>
        </View>
    );
}

const styles = StyleSheet.create({
    base: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 999 },
    success: { backgroundColor: '#d1fae5' },
    error: { backgroundColor: '#fee2e2' },
    warning: { backgroundColor: '#fef3c7' },
    label: { fontSize: 12, fontWeight: '600' },
});

С стилями извне

type CardProps = {
    children: React.ReactNode;
    style?: StyleProp<ViewStyle>;
};

export function Card({ children, style }: CardProps) {
    return <View style={[styles.card, style]}>{children}</View>;
}

StyleProp<ViewStyle> — тип для стилей. Позволяет передавать массив, объект, undefined.

С событиями

type ListItemProps = {
    title: string;
    subtitle?: string;
    onPress: () => void;
};

export function ListItem({ title, subtitle, onPress }: ListItemProps) {
    return (
        <Pressable
            onPress={onPress}
            style={({ pressed }) => [styles.item, pressed && styles.pressed]}
        >
            <Text style={styles.title}>{title}</Text>
            {subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : null}
        </Pressable>
    );
}

С иконкой

type IconButtonProps = {
    icon: keyof typeof Ionicons.glyphMap;
    onPress: () => void;
    size?: number;
    color?: string;
};

export function IconButton({ icon, onPress, size = 24, color = '#333' }: IconButtonProps) {
    return (
        <Pressable onPress={onPress} hitSlop={8}>
            <Ionicons name={icon} size={size} color={color} />
        </Pressable>
    );
}

hitSlop увеличивает область нажатия без изменения размера.

Состояние внутри

export function Collapsible({ title, children }: { title: string; children: React.ReactNode }) {
    const [open, setOpen] = useState(false);

    return (
        <View>
            <Pressable onPress={() => setOpen((o) => !o)} style={styles.header}>
                <Text style={styles.headerText}>{title}</Text>
                <Ionicons name={open ? 'chevron-up' : 'chevron-down'} size={20} />
            </Pressable>
            {open ? <View style={styles.content}>{children}</View> : null}
        </View>
    );
}

Compound components

function CardRoot({ children, style }: { children: React.ReactNode; style?: StyleProp<ViewStyle> }) {
    return <View style={[styles.card, style]}>{children}</View>;
}

function CardHeader({ children }: { children: React.ReactNode }) {
    return <View style={styles.header}>{children}</View>;
}

function CardBody({ children }: { children: React.ReactNode }) {
    return <View style={styles.body}>{children}</View>;
}

export const Card = Object.assign(CardRoot, {
    Header: CardHeader,
    Body: CardBody,
});

// использование
<Card>
    <Card.Header><Text>Заголовок</Text></Card.Header>
    <Card.Body><Text>Контент</Text></Card.Body>
</Card>

memo

Мемоизация для оптимизации:

export const ItemCard = memo(function ItemCard({ item }: { item: Item }) {
    return (
        <View>
            <Text>{item.title}</Text>
        </View>
    );
});

Применяй там, где компонент часто перерисовывается с теми же пропсами.

Если ты уже знаешь React

Всё то же, что в вебе: пропсы, children, композиция. Отличия — в стилях (style вместо className) и компонентах (View вместо div).

Итоги

  • Пропсы и children как в React
  • StyleProp<ViewStyle> для стилей извне
  • hitSlop для увеличения области нажатия
  • Compound components для сложных UI
  • memo для оптимизации