mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 14:14:57 +00:00
392 lines
8.1 KiB
Markdown
392 lines
8.1 KiB
Markdown
# Data Source Binding Guide
|
|
|
|
## Overview
|
|
|
|
The Data Source Binding system enables declarative data management in CodeForge applications. Instead of manually managing React state, you define data sources and bind them directly to component properties.
|
|
|
|
## Data Source Types
|
|
|
|
### 1. KV Store (`kv`)
|
|
Persistent data storage backed by the Spark KV API. Perfect for user preferences, application state, and any data that needs to survive page refreshes.
|
|
|
|
```typescript
|
|
{
|
|
id: 'userProfile',
|
|
type: 'kv',
|
|
key: 'user-profile-data',
|
|
defaultValue: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
preferences: {
|
|
theme: 'dark',
|
|
notifications: true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use cases:**
|
|
- User profiles and preferences
|
|
- Todo lists and task management
|
|
- Shopping cart data
|
|
- Form drafts
|
|
- Application settings
|
|
|
|
### 2. Computed Values (`computed`)
|
|
Derived data that automatically updates when dependencies change. Great for calculations, formatted strings, and aggregated data.
|
|
|
|
```typescript
|
|
{
|
|
id: 'displayName',
|
|
type: 'computed',
|
|
compute: (data) => {
|
|
const profile = data.userProfile
|
|
return `${profile?.name || 'Guest'} (${profile?.email || 'N/A'})`
|
|
},
|
|
dependencies: ['userProfile']
|
|
}
|
|
```
|
|
|
|
**Use cases:**
|
|
- Formatted display text
|
|
- Calculated totals and statistics
|
|
- Filtered/sorted lists
|
|
- Conditional values
|
|
- Data transformations
|
|
|
|
### 3. Static Data (`static`)
|
|
Constant values that don't change during the session. Useful for configuration and reference data.
|
|
|
|
```typescript
|
|
{
|
|
id: 'appConfig',
|
|
type: 'static',
|
|
defaultValue: {
|
|
apiUrl: 'https://api.example.com',
|
|
version: '1.0.0',
|
|
features: ['chat', 'notifications']
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use cases:**
|
|
- API endpoints and configuration
|
|
- Feature flags
|
|
- Reference data (countries, categories)
|
|
- Constants
|
|
- Initial form values
|
|
|
|
## Binding Properties
|
|
|
|
Once you have data sources, bind them to component properties:
|
|
|
|
```typescript
|
|
{
|
|
id: 'welcome-heading',
|
|
type: 'Heading',
|
|
bindings: {
|
|
children: {
|
|
source: 'displayName'
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Path-based Bindings
|
|
|
|
Access nested properties using dot notation:
|
|
|
|
```typescript
|
|
{
|
|
id: 'email-input',
|
|
type: 'Input',
|
|
bindings: {
|
|
value: {
|
|
source: 'userProfile',
|
|
path: 'email'
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Transform Functions
|
|
|
|
Apply transformations to bound values:
|
|
|
|
```typescript
|
|
{
|
|
id: 'price-display',
|
|
type: 'Text',
|
|
bindings: {
|
|
children: {
|
|
source: 'price',
|
|
transform: (value) => `$${(value / 100).toFixed(2)}`
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dependency Tracking
|
|
|
|
Computed sources automatically re-calculate when their dependencies change:
|
|
|
|
```typescript
|
|
// Stats computed source depends on todos
|
|
{
|
|
id: 'stats',
|
|
type: 'computed',
|
|
compute: (data) => ({
|
|
total: data.todos?.length || 0,
|
|
completed: data.todos?.filter(t => t.completed).length || 0,
|
|
remaining: data.todos?.filter(t => !t.completed).length || 0
|
|
}),
|
|
dependencies: ['todos']
|
|
}
|
|
|
|
// When todos updates, stats automatically updates too
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use KV for Persistence
|
|
If data needs to survive page refreshes, use a KV source:
|
|
```typescript
|
|
✅ { id: 'cart', type: 'kv', key: 'shopping-cart', defaultValue: [] }
|
|
❌ { id: 'cart', type: 'static', defaultValue: [] } // Will reset on refresh
|
|
```
|
|
|
|
### 2. Keep Computed Functions Pure
|
|
Computed functions should be deterministic and not have side effects:
|
|
```typescript
|
|
✅ compute: (data) => data.items.filter(i => i.active)
|
|
❌ compute: (data) => {
|
|
toast.info('Computing...') // Side effect!
|
|
return data.items.filter(i => i.active)
|
|
}
|
|
```
|
|
|
|
### 3. Declare All Dependencies
|
|
Always list dependencies for computed sources:
|
|
```typescript
|
|
✅ dependencies: ['todos', 'filter']
|
|
❌ dependencies: [] // Missing dependencies!
|
|
```
|
|
|
|
### 4. Use Meaningful IDs
|
|
Choose descriptive IDs that clearly indicate the data's purpose:
|
|
```typescript
|
|
✅ id: 'userProfile'
|
|
✅ id: 'todoStats'
|
|
❌ id: 'data1'
|
|
❌ id: 'temp'
|
|
```
|
|
|
|
### 5. Structure Data Logically
|
|
Organize related data in nested objects:
|
|
```typescript
|
|
✅ {
|
|
id: 'settings',
|
|
type: 'kv',
|
|
defaultValue: {
|
|
theme: 'dark',
|
|
notifications: true,
|
|
language: 'en'
|
|
}
|
|
}
|
|
|
|
❌ Multiple separate sources for related data
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
Here's a full example with multiple data sources and bindings:
|
|
|
|
```typescript
|
|
{
|
|
dataSources: [
|
|
// KV storage for tasks
|
|
{
|
|
id: 'tasks',
|
|
type: 'kv',
|
|
key: 'user-tasks',
|
|
defaultValue: []
|
|
},
|
|
|
|
// Static filter options
|
|
{
|
|
id: 'filterOptions',
|
|
type: 'static',
|
|
defaultValue: ['all', 'active', 'completed']
|
|
},
|
|
|
|
// Current filter selection
|
|
{
|
|
id: 'currentFilter',
|
|
type: 'kv',
|
|
key: 'task-filter',
|
|
defaultValue: 'all'
|
|
},
|
|
|
|
// Computed filtered tasks
|
|
{
|
|
id: 'filteredTasks',
|
|
type: 'computed',
|
|
compute: (data) => {
|
|
const filter = data.currentFilter
|
|
const tasks = data.tasks || []
|
|
|
|
if (filter === 'all') return tasks
|
|
if (filter === 'active') return tasks.filter(t => !t.completed)
|
|
if (filter === 'completed') return tasks.filter(t => t.completed)
|
|
|
|
return tasks
|
|
},
|
|
dependencies: ['tasks', 'currentFilter']
|
|
},
|
|
|
|
// Computed statistics
|
|
{
|
|
id: 'taskStats',
|
|
type: 'computed',
|
|
compute: (data) => ({
|
|
total: data.tasks?.length || 0,
|
|
active: data.tasks?.filter(t => !t.completed).length || 0,
|
|
completed: data.tasks?.filter(t => t.completed).length || 0
|
|
}),
|
|
dependencies: ['tasks']
|
|
}
|
|
],
|
|
|
|
components: [
|
|
// Display total count
|
|
{
|
|
id: 'total-badge',
|
|
type: 'Badge',
|
|
bindings: {
|
|
children: {
|
|
source: 'taskStats',
|
|
path: 'total'
|
|
}
|
|
}
|
|
},
|
|
|
|
// List filtered tasks
|
|
{
|
|
id: 'task-list',
|
|
type: 'List',
|
|
bindings: {
|
|
items: {
|
|
source: 'filteredTasks'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## UI Components
|
|
|
|
### Data Source Manager
|
|
The `DataSourceManager` component provides a visual interface for creating and managing data sources:
|
|
- Create KV, computed, and static sources
|
|
- Edit source configuration
|
|
- View dependency relationships
|
|
- Delete sources (with safety checks)
|
|
|
|
### Binding Editor
|
|
The `BindingEditor` component allows you to bind component properties to data sources:
|
|
- Select properties to bind
|
|
- Choose data sources
|
|
- Specify nested paths
|
|
- Preview bindings
|
|
|
|
### Component Binding Dialog
|
|
Open a dialog to edit all bindings for a specific component with live preview.
|
|
|
|
## Hooks
|
|
|
|
### useDataSources
|
|
The core hook that manages all data sources:
|
|
|
|
```typescript
|
|
import { useDataSources } from '@/hooks/data/use-data-sources'
|
|
|
|
const { data, updateData, updatePath, loading } = useDataSources(dataSources)
|
|
|
|
// Access data
|
|
const userProfile = data.userProfile
|
|
|
|
// Update entire source
|
|
updateData('userProfile', newProfile)
|
|
|
|
// Update nested property
|
|
updatePath('userProfile', 'email', 'newemail@example.com')
|
|
```
|
|
|
|
### useDataSourceManager
|
|
Hook for managing the data source configuration:
|
|
|
|
```typescript
|
|
import { useDataSourceManager } from '@/hooks/data/use-data-source-manager'
|
|
|
|
const {
|
|
dataSources,
|
|
addDataSource,
|
|
updateDataSource,
|
|
deleteDataSource,
|
|
getDataSource,
|
|
getDependents
|
|
} = useDataSourceManager(initialSources)
|
|
```
|
|
|
|
## Tips & Tricks
|
|
|
|
### Avoiding Circular Dependencies
|
|
Never create circular dependencies between computed sources:
|
|
```typescript
|
|
❌ Bad:
|
|
{
|
|
id: 'a',
|
|
type: 'computed',
|
|
compute: (data) => data.b + 1,
|
|
dependencies: ['b']
|
|
},
|
|
{
|
|
id: 'b',
|
|
type: 'computed',
|
|
compute: (data) => data.a + 1,
|
|
dependencies: ['a']
|
|
}
|
|
```
|
|
|
|
### Optimizing Computed Sources
|
|
Keep compute functions fast and efficient:
|
|
```typescript
|
|
✅ Fast:
|
|
compute: (data) => data.items.length
|
|
|
|
❌ Slow:
|
|
compute: (data) => {
|
|
let result = 0
|
|
for (let i = 0; i < 1000000; i++) {
|
|
result += Math.random()
|
|
}
|
|
return result
|
|
}
|
|
```
|
|
|
|
### Testing Data Sources
|
|
Test your data sources independently:
|
|
```typescript
|
|
const source = {
|
|
id: 'stats',
|
|
type: 'computed',
|
|
compute: (data) => ({ total: data.items.length }),
|
|
dependencies: ['items']
|
|
}
|
|
|
|
const testData = { items: [1, 2, 3] }
|
|
const result = source.compute(testData)
|
|
// result: { total: 3 }
|
|
```
|