Component Architecture
Philosophy
This guide defines a component architecture based on the Lego philosophy: components that work perfectly in default mode, but can be disassembled and reassembled in any form to meet specific design and development needs.
Core Principles
- Total Flexibility: Never restrict the designer or developer
- Substitutability: All parts must be replaceable
- Single Responsibility: Each subcomponent has a specific function
- Configurability: Order and behavior must be configurable
- Ease of Use: Work perfectly "out of the box"
File Structure
ComponentName/
├── index.tsx # Main component and exports
├── context.ts # Context and custom hook
├── types.ts # All typings
├── styles.ts # Style/composition definitions
└── components/
├── Content.tsx # Default content rendering
├── SubComponent1.tsx # Specific subcomponents
├── SubComponent2.tsx
└── ...
1. Context Pattern
Context Creation
// context.ts
import { createContext, useContext } from 'react'
import { ComponentCtxValue } from './types'
export const ComponentContext = createContext({} as ComponentCtxValue)
export const useComponentContext = <T = ComponentCtxValue>(
providedProps: Partial<T> = {}
) => {
const ctx = useContext(ComponentContext)
if (!ctx) {
throw new Error(
'[ComponentName] useComponentContext must be used within a ComponentName component.'
)
}
return {
...ctx,
...providedProps,
}
}
Benefits:
- ✅ Eliminates prop drilling
- ✅ Allows prop override in subcomponents
- ✅ Maintains data consistency
- ✅ Facilitates maintenance
2. Structured Typing
Type Definitions
// types.ts
export type ComponentProps = {
// Main props
title?: string
description?: string
loading?: boolean
// Configuration
order?: Array<'part1' | 'part2' | 'part3'>
// Customization
children?: ReactNode
style?: StyledProp<ComponentComposition>
// Behavior
onAction?: AnyFunction
}
// Specific props for subcomponents
export type SubComponent1Props = Pick<ComponentProps, 'title' | 'description'>
export type SubComponent2Props = Pick<ComponentProps, 'onAction'> &
Omit<OtherComponentProps, 'style' | 'debugName'>
// Context value
export type ComponentCtxValue = Omit<ComponentProps, 'style' | 'children'> & {
styles: StyleRecord<ComponentComposition>
}
3. Main Component
Index Structure
// index.tsx
export const ComponentName = (props: ComponentProps) => {
const {
children,
style,
...contextValue
} = {
...ComponentName.defaultProps,
...props,
}
const styles = useStylesFor(ComponentName.styleRegistryName, style)
return (
<ComponentContext.Provider value={{ ...contextValue, styles }}>
<ComponentContent>
{children}
</ComponentContent>
</ComponentContext.Provider>
)
}
// Static configuration
ComponentName.styleRegistryName = 'ComponentName'
ComponentName.elements = ['wrapper', 'title', 'description', 'button']
ComponentName.rootElement = 'wrapper'
// Attach subcomponents
ComponentName.SubComponent1 = SubComponent1
ComponentName.SubComponent2 = SubComponent2
// Variant types support
ComponentName.withVariantTypes = <S extends AnyRecord>(styles: S) => {
return ComponentName as ComponentWithVariants<S>
}
// Default props
ComponentName.defaultProps = {
order: ['part1', 'part2', 'part3']
} as Partial<ComponentProps>
// Style system registration
StyleRegistry.registerComponent(ComponentName)
4. Content Pattern
Intelligent Content Rendering
// components/Content.tsx
const orderMap: Record<ComponentProps['order'][0], any> = {
part1: SubComponent1,
part2: SubComponent2,
part3: SubComponent3,
}
export const ComponentContent = ({ children }: React.PropsWithChildren) => {
const { styles, order } = useComponentContext()
// Custom content takes precedence
if (children) {
return <View style={styles.wrapper}>{children}</View>
}
// Default rendering based on order
return (
<View style={styles.wrapper}>
{order.map(componentKey => {
const Component = orderMap[componentKey]
if (!Component) {
logger.warn(`Component "${componentKey}" not found in orderMap`)
return null
}
return <Component key={`${componentKey}-${Date.now()}`} />
})}
</View>
)
}
5. Subcomponents
Subcomponent Pattern
// components/SubComponent1.tsx
export const SubComponent1 = (props: SubComponent1Props) => {
const { title, description, styles, ...contextProps } = useComponentContext(props)
// Conditional rendering
if (!title && !description) return null
return (
<>
{title && <Text text={title} style={styles.title} />}
{description && <Text text={description} style={styles.description} />}
</>
)
}
Subcomponent Characteristics:
- ✅ Receive props via context + local override
- ✅ Intelligent conditional rendering
- ✅ Single, well-defined responsibility
- ✅ Can be used independently
6. Usage Patterns
Basic Usage
<ComponentName
title="Title"
description="Description"
onAction={() => {}}
/>
Order Customization
<ComponentName
title="Title"
order={['part2', 'part1', 'part3']}
/>
Complete Replacement
<ComponentName>
<CustomHeader />
<ComponentName.SubComponent2 />
<CustomFooter />
</ComponentName>
Hybrid Usage
<ComponentName title="Title">
<ComponentName.SubComponent1 description="Override" />
<CustomComponent />
<ComponentName.SubComponent3 />
</ComponentName>
7. Implementation Checklist
✅ Basic Structure
- Context created with custom hook
- Types defined for all components
- Main component with Provider
- Style system configured
✅ Core Functionality
- Content component with intelligent rendering
- Subcomponents with single responsibilities
- Default props defined
✅ Flexibility
- Children support implemented
- Props override in subcomponents
- Special states handled
- Conditional rendering in all subcomponents
✅ DX (Developer Experience)
- Error handling with clear messages
- Debug logging
- Complete TypeScript
- Usage documentation
This architecture transforms components into true "Digital Legos" - pieces that fit together perfectly by default, but can be rearranged in infinite ways to create unique experiences.