ueca-react 1.0.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ueca-react might be problematic. Click here for more details.
- package/LICENSE +1 -1
- package/README.md +109 -119
- package/dist/index.d.ts +266 -4
- package/dist/ueca-react.js +1453 -0
- package/docs/Arrays and Reactivity in UECA-React.md +158 -0
- package/docs/Automatic onChange Events in UECA-React.md +142 -0
- package/docs/Automatic onChanging Events in UECA-React.md +157 -0
- package/docs/Automatic onPropChange and onPropChanging Events in UECA-React.md +112 -0
- package/docs/Component Extension in UECA-React.md +275 -0
- package/docs/Component IDs in UECA-React.md +181 -0
- package/docs/{component-intergation-model.md → Component Integration Model in UECA-React.md } +4 -3
- package/docs/{component-mental-model.md → Component Mental Model in UECA-React.md } +4 -3
- package/docs/Introduction to UECA-React Components.md +190 -0
- package/docs/Introduction to UECA-React.md +24 -0
- package/docs/Lifecycle Hooks in UECA-React.md +237 -0
- package/docs/Message Bus in UECA-React.md +260 -0
- package/docs/Model Caching in UECA-React.md +144 -0
- package/docs/Property Bindings in UECA-React.md +191 -0
- package/docs/State Management in UECA-React.md +128 -0
- package/docs/Technology of UECA-React.md +45 -0
- package/docs/Tracing in UECA-React.md +110 -0
- package/docs/code-template.md +53 -27
- package/docs/index.md +31 -11
- package/package.json +68 -72
- package/dist/componentModel.d.ts +0 -127
- package/dist/componentModel.js +0 -772
- package/dist/dynamicContent.d.ts +0 -22
- package/dist/dynamicContent.js +0 -80
- package/dist/index.js +0 -29
- package/dist/messageBus.d.ts +0 -46
- package/dist/messageBus.js +0 -141
- package/dist/utils.d.ts +0 -8
- package/dist/utils.js +0 -52
- package/docs/base-concepts.md +0 -192
- package/docs/bindings-overview.md +0 -164
- package/docs/general-code-structure.md +0 -177
- package/docs/introduction.md +0 -56
- package/docs/message-bus.md +0 -177
- package/docs/technology.md +0 -45
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Introduction to UECA-React
|
|
2
|
+
|
|
3
|
+
UECA-React is a framework designed to simplify the development of large-scale React applications by providing a unified and encapsulated component architecture. It encapsulates React and MobX behind a consistent component pattern, allowing developers to focus on high-level logic and structure rather than low-level implementation details.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
- **Unified Component Structure**: All components follow a consistent pattern, reducing complexity and improving maintainability.
|
|
7
|
+
- **Strong Typing**: Full TypeScript support ensures type safety and clarity.
|
|
8
|
+
- **State Management**: Seamless integration with MobX for reactive state management.
|
|
9
|
+
- **Property Bindings**: Powerful component property bindings for state synchronization.
|
|
10
|
+
- **Event System**: Built-in support for events like `onChange<Property>` with safe handler invocation.
|
|
11
|
+
- **Message Bus**: Lightweight messaging system for inter-component communication.
|
|
12
|
+
- **Lifecycle Hooks**: Comprehensive set of hooks for fine-grained control over component lifecycle.
|
|
13
|
+
- **Tracing Tools**: Enhanced trace log capabilities.
|
|
14
|
+
- **Automated UI Testing Ready**: Automated UI testing tools can use component IDs as locators to identify and interact with UI elements.
|
|
15
|
+
|
|
16
|
+
## Target Audience
|
|
17
|
+
UECA-React is ideal for developers and teams working on large-scale React applications who are looking for a more structured and maintainable approach to building user interfaces. It is particularly beneficial for those who value type safety, consistency, and ease of debugging.
|
|
18
|
+
|
|
19
|
+
## Why Choose UECA-React?
|
|
20
|
+
- **Reduced Complexity**: Focus on business logic rather than React-specific boilerplate.
|
|
21
|
+
- **Improved Maintainability**: Consistent component structure makes code easier to read, review, and test.
|
|
22
|
+
- **Enhanced Productivity**: Simplified state management and event handling accelerate development.
|
|
23
|
+
- **Scalability**: Designed to handle the demands of large-scale applications with ease.
|
|
24
|
+
- **AI Code Generation**: UECA principles pave the way for AI-driven code generation. This documentation was created by a pretrained AI model.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Lifecycle Hooks in UECA-React
|
|
2
|
+
#lifecycle #hooks #basics #api #example #best_practices
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
Lifecycle hooks in UECA-React provide a structured and predictable approach to managing a component’s behavior across its lifecycle stages. Unlike React’s `useEffect`, which relies on dependency arrays and can lead to complex logic, UECA-React offers a granular set of hooks that execute at specific points, such as model creation, DOM mounting, or UI rendering. Each hook receives the component’s `model`, enabling direct access to state, methods, and metadata, which is particularly powerful for dynamically created JSX content. This article details the lifecycle hooks, their sequence, chaining behavior, and centralized error handling.
|
|
6
|
+
|
|
7
|
+
### Key Concepts
|
|
8
|
+
- **Lifecycle Hooks**: Functions invoked at defined lifecycle stages.
|
|
9
|
+
- **Granular Control**: Hooks for creation, initialization, mounting, rendering, and cleanup.
|
|
10
|
+
- **Model Parameter**: Provides access to component properties, methods, and metadata (e.g., `fullId()`, `birthMark()`).
|
|
11
|
+
- **Chaining**: Hooks and event handlers passed in JSX chain with those defined in the component’s model.
|
|
12
|
+
- **Centralized Error Handling**: All errors (sync or async) are managed via `UECA.globalSettings.errorHandler`.
|
|
13
|
+
|
|
14
|
+
## Overview of Lifecycle Hooks
|
|
15
|
+
|
|
16
|
+
UECA-React provides the following hooks, each receiving the component’s `model`:
|
|
17
|
+
|
|
18
|
+
1. **`constr(model)`**
|
|
19
|
+
- **When**: Called once when the model is instantiated.
|
|
20
|
+
- **Purpose**: One-time setup, like initializing non-reactive state or API clients.
|
|
21
|
+
- **Example Use Case**: Fetching initial data.
|
|
22
|
+
|
|
23
|
+
2. **`init(model)`**
|
|
24
|
+
- **When**: Called after `constr()` on creation or cache retrieval.
|
|
25
|
+
- **Purpose**: Initialization tasks for model activation, like resetting state.
|
|
26
|
+
- **Example Use Case**: Subscribing to a WebSocket.
|
|
27
|
+
|
|
28
|
+
3. **`mount(model)`**
|
|
29
|
+
- **When**: Called when the component mounts to the DOM.
|
|
30
|
+
- **Purpose**: DOM-related setup, like adding listeners.
|
|
31
|
+
- **Example Use Case**: Attaching a resize listener.
|
|
32
|
+
|
|
33
|
+
4. **`draw(model)`**
|
|
34
|
+
- **When**: Called after view rendering.
|
|
35
|
+
- **Purpose**: Post-render logic, similar to `useLayoutEffect`.
|
|
36
|
+
- **Example Use Case**: Adjusting UI positions.
|
|
37
|
+
|
|
38
|
+
5. **`erase(model)`**
|
|
39
|
+
- **When**: Called before UI removal.
|
|
40
|
+
- **Purpose**: Cleanup of UI resources before unmounting.
|
|
41
|
+
- **Example Use Case**: Clearing animations.
|
|
42
|
+
|
|
43
|
+
6. **`unmount(model)`**
|
|
44
|
+
- **When**: Called when the component is removed from the DOM.
|
|
45
|
+
- **Purpose**: DOM resource cleanup, like removing listeners.
|
|
46
|
+
- **Example Use Case**: Detaching a resize listener.
|
|
47
|
+
|
|
48
|
+
7. **`deinit(model)`**
|
|
49
|
+
- **When**: Called when the component loses React context (e.g., cached but inactive).
|
|
50
|
+
- **Purpose**: Cleanup of resources during deactivation.
|
|
51
|
+
- **Example Use Case**: Unsubscribing from a WebSocket.
|
|
52
|
+
|
|
53
|
+
**Note**: No `destr()` hook exists due to JavaScript garbage collection unpredictability; use `deinit()` for cleanup.
|
|
54
|
+
|
|
55
|
+
## Sequence of Lifecycle Hooks
|
|
56
|
+
Hooks execute in this order:
|
|
57
|
+
1. `constr()` → Model creation.
|
|
58
|
+
2. `init()` → Model activation.
|
|
59
|
+
3. `mount()` → DOM attachment.
|
|
60
|
+
4. `draw()` → UI rendering.
|
|
61
|
+
5. `erase()` → UI removal preparation.
|
|
62
|
+
6. `unmount()` → DOM detachment.
|
|
63
|
+
7. `deinit()` → Model deactivation.
|
|
64
|
+
|
|
65
|
+
## Model Parameter
|
|
66
|
+
Each hook receives the component’s `model`, enabling access to properties (`model.count`), methods (`model.increment`), and metadata (`fullId()`, `birthMark()`). This is critical for **dynamic JSX**, where inline lifecycle hooks can customize behavior using `model`.
|
|
67
|
+
|
|
68
|
+
## Centralized Error Handling
|
|
69
|
+
All errors—synchronous or asynchronous—in hooks, methods, event handlers, or custom code are captured by `UECA.globalSettings.errorHandler`. This eliminates the need for try-catch blocks, streamlining error management.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import * as UECA from "ueca-react";
|
|
73
|
+
|
|
74
|
+
// Configure global error handler
|
|
75
|
+
UECA.globalSettings.errorHandler = (error: Error) => {
|
|
76
|
+
console.error(`Error: ${error.message}`);
|
|
77
|
+
// Send a message to Message Bus to display the error dialog
|
|
78
|
+
UECA.defaultMessageBus<AppMessage>().unicast("App.UnhandledException", error);
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **Sync Errors**: Caught automatically (e.g., invalid state in `init`).
|
|
83
|
+
- **Async Errors**: Handled from `async` hooks or methods without try-catch.
|
|
84
|
+
|
|
85
|
+
## Chaining Hooks and Event Handlers
|
|
86
|
+
Hooks and event handlers passed as `params` in JSX are **chained** with those defined in the component’s model hook. Chaining executes the model-defined handler first, followed by the JSX-passed handler, allowing layered logic.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Code Examples
|
|
91
|
+
|
|
92
|
+
### Example 1: Lifecycle Hooks with Centralized Error Handling
|
|
93
|
+
A counter component logs lifecycle events and uses global error handling.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import * as UECA from "ueca-react";
|
|
97
|
+
|
|
98
|
+
type CounterStruct = UECA.ComponentStruct<{
|
|
99
|
+
props: { count: number };
|
|
100
|
+
methods: { increment: () => void };
|
|
101
|
+
}>;
|
|
102
|
+
|
|
103
|
+
type CounterParams = UECA.ComponentParams<CounterStruct>;
|
|
104
|
+
type CounterModel = UECA.ComponentModel<CounterStruct>;
|
|
105
|
+
|
|
106
|
+
function useCounter(params?: CounterParams): CounterModel {
|
|
107
|
+
const struct: CounterStruct = {
|
|
108
|
+
props: {
|
|
109
|
+
id: useCounter.name,
|
|
110
|
+
count: 0
|
|
111
|
+
},
|
|
112
|
+
constr: (model) => console.log(`constr: Model created: ${model.birthMark()}`),
|
|
113
|
+
init: async (model) => {
|
|
114
|
+
console.log(`init: Model initialized: ${model.fullId()}`);
|
|
115
|
+
// Simulate async error (handled by globalSettings.errorHandler)
|
|
116
|
+
if (Math.random() > 0.8) throw new Error("Init failed");
|
|
117
|
+
model.count = 0;
|
|
118
|
+
},
|
|
119
|
+
deinit: (model) => console.log(`deinit: Model deactivated: ${model.fullId()}`),
|
|
120
|
+
mount: (model) => console.log(`mount: Component mounted: ${model.fullId()}`),
|
|
121
|
+
unmount: (model) => console.log(`unmount: Component removed: ${model.fullId()}`),
|
|
122
|
+
draw: (model) => console.log(`draw: UI rendered: ${model.fullId()}`),
|
|
123
|
+
erase: (model) => console.log(`erase: UI about to be removed: ${model.fullId()}`),
|
|
124
|
+
methods: {
|
|
125
|
+
increment: () => model.count++,
|
|
126
|
+
},
|
|
127
|
+
View: () => (
|
|
128
|
+
<div id={model.htmlId()}>
|
|
129
|
+
<p>Counter: {model.count}</p>
|
|
130
|
+
<button onClick={model.increment}>Increment</button>
|
|
131
|
+
</div>
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
const model = UECA.useComponent(struct, params);
|
|
135
|
+
return model;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const Counter = UECA.getFC(useCounter);
|
|
139
|
+
export { CounterModel, useCounter, Counter };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **Explanation**:
|
|
143
|
+
- Hooks use `model` parameter to log `birthMark()` or `fullId()`.
|
|
144
|
+
- `init` includes an async operation with potential errors, managed globally.
|
|
145
|
+
- The component resets `count` and logs lifecycle stages.
|
|
146
|
+
|
|
147
|
+
### Example 2: Dynamic JSX with Chained Hooks
|
|
148
|
+
This example shows a dynamically created `Counter` with chained lifecycle hooks.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import * as UECA from "ueca-react";
|
|
152
|
+
|
|
153
|
+
type AppStruct = UECA.ComponentStruct<{
|
|
154
|
+
props: { showCounter: boolean };
|
|
155
|
+
}>;
|
|
156
|
+
|
|
157
|
+
function useApp(): UECA.ComponentModel<AppStruct> {
|
|
158
|
+
const struct: AppStruct = {
|
|
159
|
+
props: {
|
|
160
|
+
id: useApp.name,
|
|
161
|
+
showCounter: true;
|
|
162
|
+
},
|
|
163
|
+
View: () => (
|
|
164
|
+
<div id={model.htmlId()}>
|
|
165
|
+
<button onClick={() => (model.showCounter = !model.showCounter)}>
|
|
166
|
+
Toggle Counter
|
|
167
|
+
</button>
|
|
168
|
+
{model.showCounter && (
|
|
169
|
+
<Counter
|
|
170
|
+
id={"counter1"}
|
|
171
|
+
constr={(counter_model) => console.log(`JSX constr: ${counter_model.birthMark()}`)}
|
|
172
|
+
init={(counter_model) => {
|
|
173
|
+
console.log(`JSX init: ${counter_model.fullId()}`);
|
|
174
|
+
counter_model.count = 10;
|
|
175
|
+
}}
|
|
176
|
+
onChangeCount={(newValue) => console.log(`JSX count changed: ${newValue}`)}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
),
|
|
181
|
+
};
|
|
182
|
+
const model = UECA.useComponent(struct, params);
|
|
183
|
+
return model;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const App = UECA.getFC(useApp);
|
|
187
|
+
export { App };
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- **Explanation**:
|
|
191
|
+
- The `Counter` is instantiated dynamically in JSX with inline `constr`, `init`, and `onChangeCount`.
|
|
192
|
+
- Model-defined hooks (e.g., `init` in `useCounter`) execute first, followed by JSX-passed hooks, chaining their logic.
|
|
193
|
+
- The `onChangeCount` event handler in JSX chains with any model-defined handler, logging count changes.
|
|
194
|
+
- Errors in inline hooks are caught by `globalSettings.errorHandler`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Best Practices
|
|
199
|
+
- **Use `constr` for One-Time Setup**: Initialize static resources like API clients.
|
|
200
|
+
- **Leverage `init` and `deinit` for Activation**: Manage subscriptions or state resets.
|
|
201
|
+
- **Keep `mount` and `unmount` DOM-Specific**: Focus on DOM interactions.
|
|
202
|
+
- **Optimize `draw` and `erase` for UI**: Handle render-related tasks.
|
|
203
|
+
- **Use Global Error Handling**: Rely on `globalSettings.errorHandler` for all errors.
|
|
204
|
+
- **Chain Hooks Judiciously**: Ensure JSX-passed hooks complement model-defined ones without redundancy.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Comparison to Traditional React
|
|
209
|
+
React’s `useEffect` requires dependency arrays, often leading to stale closures or complex cleanup. UECA-React hooks:
|
|
210
|
+
- **Eliminate Dependencies**: Fixed execution order simplifies logic.
|
|
211
|
+
- **Clarify Intent**: Named hooks are self-explanatory.
|
|
212
|
+
- **Enable Chaining**: JSX-passed hooks extend model-defined logic.
|
|
213
|
+
- **Streamline Errors**: Centralized handling reduces boilerplate.
|
|
214
|
+
|
|
215
|
+
Example in React:
|
|
216
|
+
```typescript
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
console.log("Mounted");
|
|
219
|
+
return () => console.log("Unmounted");
|
|
220
|
+
}, []);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
In UECA-React:
|
|
224
|
+
```typescript
|
|
225
|
+
mount: (model) => console.log(`Mounted: ${model.fullId()}`),
|
|
226
|
+
unmount: (model) => console.log(`Unmounted: ${model.fullId()}`),
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Notes
|
|
232
|
+
- Hooks are optional; omit unused ones for efficiency.
|
|
233
|
+
- Asynchronous hooks support tasks like API calls, with errors managed globally.
|
|
234
|
+
- The `model` parameter enables dynamic JSX customization, accessing model state, methods, etc.
|
|
235
|
+
- Chaining applies to both hooks and event handlers, executing model-defined logic first.
|
|
236
|
+
- When using model caching (version 2.0), `init` and `deinit` manage cache cycles.
|
|
237
|
+
- Test chained hooks/handlers to ensure combined logic behaves as expected.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Message Bus in UECA-React
|
|
2
|
+
#message_bus #communication #events #decouple
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
The UECA Message Bus is a centralized communication system in UECA-React that enables decoupled interaction between components. It allows components to post messages to a bus and subscribe to specific message types, promoting a modular and scalable architecture. By using the message bus, components can communicate without direct dependencies, making the application easier to maintain and extend.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## API/Methods
|
|
10
|
+
|
|
11
|
+
### Message Type Declaration
|
|
12
|
+
#message_type
|
|
13
|
+
|
|
14
|
+
To use the message bus, you must first define the message types that your application will use. Message types are declared as a TypeScript type, specifying the input (`in`) and output (`out`) for each message. This ensures type safety when posting and handling messages.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
type AppMessage = {
|
|
18
|
+
"GetCurrentTime": {
|
|
19
|
+
out: { time: string };
|
|
20
|
+
};
|
|
21
|
+
"Api.GetWeatherForecast": {
|
|
22
|
+
in: { zip_code: string };
|
|
23
|
+
out: { temperature: number; rainProbability: number };
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
- Each message type (e.g., `Api.GetWeatherForecast`) defines:
|
|
28
|
+
- `in`: The input parameters (optional).
|
|
29
|
+
- `out`: The expected output (optional).
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Posting a Message
|
|
33
|
+
#post #broadcast #unicast
|
|
34
|
+
|
|
35
|
+
- **model.bus.broadcast(modelIdFilter, messageType, payload)**
|
|
36
|
+
Posts a message to the bus for subscribers matching `modelIdFilter` with the specified `messageType` and optional `payload`. This method sends a message to be handled by all subscribers whose `model.fullId()` matches the `modelIdFilter` condition.
|
|
37
|
+
- **Parameters**:
|
|
38
|
+
- `modelIdFilter`: A string or RegExp identifying subscriber `model.fullId()` (e.g., `app.ui.screen.nameInput`, `^app\.ui\.screen\.editor\.[a-zA-Z0-9_]+Input$`). Use `undefined`, `null`, or `""` to target all subscribers.
|
|
39
|
+
- `messageType`: A string identifying the message type (e.g., `"UI.Screen.DisableControls"`).
|
|
40
|
+
- `payload`: An optional object containing data sent with the message.
|
|
41
|
+
- **Returns**: A Promise that resolves with an array of responses from all message handlers (if any).
|
|
42
|
+
|
|
43
|
+
- **model.bus.unicast(messageType, payload)**
|
|
44
|
+
Posts a message to the bus with the specified `messageType` and optional `payload`. This method sends a message to be handled by one subscriber. Note: if multiple subscribers listen for the same message, only the first subscriber will receive it.
|
|
45
|
+
- **Parameters**:
|
|
46
|
+
- `messageType`: A string identifying the type of message (e.g., `Api.GetWeatherForecast`).
|
|
47
|
+
- `payload`: An optional object containing data to be sent with the message.
|
|
48
|
+
- **Returns**: A promise that resolves with the response from the message handler (if any).
|
|
49
|
+
|
|
50
|
+
- **model.bus.castTo(modelId, messageType, payload)**
|
|
51
|
+
Posts a message to the bus for the subscriber matching `modelId` with the specified `messageType` and optional `payload`. This method sends a message to be handled by one subscriber whose `model.fullId()` is `modelId`.
|
|
52
|
+
- **Parameters**:
|
|
53
|
+
- `modelId`: A string identifying subscriber `model.fullId()` (e.g., `app.ui.screen.nameInput`).
|
|
54
|
+
- `messageType`: A string identifying the message type (e.g., `"UI.Screen.ResetControls"`).
|
|
55
|
+
- `payload`: An optional object containing data sent with the message.
|
|
56
|
+
- **Returns**: A promise that resolves with the response from the message handler (if any).
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Subscribing to Messages
|
|
60
|
+
- **messages**
|
|
61
|
+
A section within the component structure where handlers for specific message types are defined. Each handler is a function that processes the message and can return a response.
|
|
62
|
+
- **Format**: An object where keys are message types and values are handler functions.
|
|
63
|
+
- **Handler Signature**: `(payload) => response`, where `payload` is the input data and `response` is a promise wih the output (if applicable).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Registering Message Bus Events
|
|
68
|
+
#handlers #subscription
|
|
69
|
+
|
|
70
|
+
To handle specific message types (i.e., register message bus events), a component must define handlers in its structure. This is done by including a `messages` object within the component's `struct`, where each key is a message type and the value is a handler function that processes the message.
|
|
71
|
+
|
|
72
|
+
### Steps to Register Message Bus Events
|
|
73
|
+
1. **Include Message Type in Component Structure**:
|
|
74
|
+
Pass the message type (e.g., `AppMessage`) as the second type parameter to `UECA.ComponentStruct` when defining the component's structure. This ensures type safety for both message handlers and message posting.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
type ServiceStruct = UECA.ComponentStruct<{}, AppMessage>;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. **Define Handlers in `messages`**:
|
|
81
|
+
Within the `struct`, add the `messages` object and specify handlers for the message types you want to handle. Each handler must match the `in` and `out` types defined in `AppMessage`.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const struct: ServiceStruct = {
|
|
85
|
+
messages: {
|
|
86
|
+
"GetCurrentTime": async () => {
|
|
87
|
+
return { time: new Date().toLocaleTimeString() };
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Key Points
|
|
94
|
+
- **Type Safety**: Including the message type in `UECA.ComponentStruct` ensures that handler functions align with the defined message types.
|
|
95
|
+
- **Automatic Subscription**: The framework automatically subscribes the component to the message types listed in `messages`; no additional subscription code is required.
|
|
96
|
+
- **Async Handlers**: Handlers are asynchronous returning a promise that resolves with the response.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Passing the Message Type to `UECA.ComponentStruct`
|
|
101
|
+
#generic_type #typescript
|
|
102
|
+
|
|
103
|
+
The `UECA.ComponentStruct` generic type is used to define the structure of a component, including its props, events, children, methods, and message handlers. To enable a component to interact with the message bus (either by posting or handling messages), you must pass the application’s message type (e.g., `AppMessage`) as the second type parameter.
|
|
104
|
+
|
|
105
|
+
### Syntax
|
|
106
|
+
```typescript
|
|
107
|
+
type ComponentStruct = UECA.ComponentStruct<ComponentStructure, MessageType>;
|
|
108
|
+
```
|
|
109
|
+
- `ComponentStructure`: The first type parameter defines the component’s structure (e.g., props, events, children, methods).
|
|
110
|
+
- `MessageType`: The second type parameter specifies the message type (e.g., `AppMessage`), ensuring type safety for message-related operations.
|
|
111
|
+
|
|
112
|
+
### Why It’s Required
|
|
113
|
+
- **For Handlers**: Ensures the `messages` object’s handlers match the input and output types in `AppMessage`.
|
|
114
|
+
- **For Posting**: Allows the bus methods (e.g. `model.bus.unicast`) to enforce correct message types and payloads when sending messages.
|
|
115
|
+
|
|
116
|
+
### Example Usage
|
|
117
|
+
For a component that handles messages:
|
|
118
|
+
```typescript
|
|
119
|
+
type TimeServiceStruct = UECA.ComponentStruct<{}, AppMessage>;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
For a component that posts messages:
|
|
123
|
+
```typescript
|
|
124
|
+
type TimeDisplayStruct = UECA.ComponentStruct<{
|
|
125
|
+
props: { currentTime: string };
|
|
126
|
+
methods: { updateTime: () => void };
|
|
127
|
+
}, AppMessage>;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Posting Messages
|
|
133
|
+
#posting #unicast
|
|
134
|
+
|
|
135
|
+
To post a message, use the `model.bus.unicast` method (or `model.bus.broadcast`, `model.bus.castTo`), which sends a message to the bus and returns a promise with the handler’s response.
|
|
136
|
+
|
|
137
|
+
### Example: Posting a Message
|
|
138
|
+
```typescript
|
|
139
|
+
async function updateTime() {
|
|
140
|
+
const response = await model.bus.unicast("GetCurrentTime");
|
|
141
|
+
model.currentTime = response.time;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **Parameters**:
|
|
146
|
+
- `messageType`: The type of message (e.g., `"GetCurrentTime"`).
|
|
147
|
+
- `payload`: Optional data matching the `in` type (not needed here since `in` is not requred).
|
|
148
|
+
- **Returns**: A promise resolving to the handler’s response (e.g., `{ time: string }`).
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Example: Weather Application
|
|
153
|
+
#example #tutorial
|
|
154
|
+
|
|
155
|
+
This example demonstrates how to register message bus events and pass the message type to `UECA.ComponentStruct` in a weather application with two components: one that handles weather data requests and one that posts them.
|
|
156
|
+
|
|
157
|
+
### Message Type
|
|
158
|
+
```typescript
|
|
159
|
+
type AppMessage = {
|
|
160
|
+
"Api.GetWeatherForecast": {
|
|
161
|
+
in: { zip_code: string };
|
|
162
|
+
out: { temperature: number; rainProbability: number };
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### WeatherService Component (Handler)
|
|
168
|
+
```typescript
|
|
169
|
+
type WeatherServiceStruct = UECA.ComponentStruct<{}, AppMessage>;
|
|
170
|
+
|
|
171
|
+
function useWeatherService(): UECA.ComponentModel<WeatherServiceStruct> {
|
|
172
|
+
const struct: WeatherServiceStruct = {
|
|
173
|
+
messages: {
|
|
174
|
+
"Api.GetWeatherForecast": async ({ zip_code }) => {
|
|
175
|
+
const response = await fetch(`https://api.weather.com/v3/wx/forecast/daily/5day?postalKey=${zip_code}&format=json`);
|
|
176
|
+
const data = await response.json();
|
|
177
|
+
return { temperature: data.temperature, rainProbability: data.rainProbability };
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
return UECA.useComponent(struct);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const WeatherService = UECA.getFC(useWeatherService);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### WeatherForm Component (Poster)
|
|
188
|
+
```typescript
|
|
189
|
+
type WeatherFormStruct = UECA.ComponentStruct<{
|
|
190
|
+
props: {
|
|
191
|
+
zip_code: string;
|
|
192
|
+
weatherData: {
|
|
193
|
+
temperature: number;
|
|
194
|
+
rainProbability: number
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
methods: {
|
|
198
|
+
submitForm: () => void
|
|
199
|
+
};
|
|
200
|
+
}, AppMessage>;
|
|
201
|
+
|
|
202
|
+
function useWeatherForm(): UECA.ComponentModel<WeatherFormStruct> {
|
|
203
|
+
const struct: WeatherFormStruct = {
|
|
204
|
+
props: {
|
|
205
|
+
zip_code: "",
|
|
206
|
+
weatherData: undefined
|
|
207
|
+
},
|
|
208
|
+
methods: {
|
|
209
|
+
submitForm: async () => {
|
|
210
|
+
model.weatherData = await model.bus.unicast("Api.GetWeatherForecast", {zip_code: model.zip_code });
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
View: () => (
|
|
214
|
+
<div id={model.htmlId()}>
|
|
215
|
+
<input value={model.zip_code} onChange={(e) => model.zip_code = e.target.value} placeholder="Zip Code" />
|
|
216
|
+
<button onClick={model.submitForm}>Get Weather</button>
|
|
217
|
+
{model.weatherData && (
|
|
218
|
+
<p>Temperature: {model.weatherData.temperature}°C, Rain Probability: {model.weatherData.rainProbability}%</p>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
),
|
|
222
|
+
};
|
|
223
|
+
return UECA.useComponent(struct);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const WeatherForm = UECA.getFC(useWeatherForm);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Integrating in the App
|
|
230
|
+
```typescript
|
|
231
|
+
function App() {
|
|
232
|
+
return (
|
|
233
|
+
<div>
|
|
234
|
+
<WeatherForm />
|
|
235
|
+
<WeatherService />
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Explanation**:
|
|
242
|
+
- **Message Type**: `AppMessage` defines `"Api.GetWeatherForecast"` with input (`zip_code`) and output (`temperature`, `rainProbability`).
|
|
243
|
+
- **Handler Registration**: `WeatherService` registers a handler for `"Api.GetWeatherForecast"` in its `messages` object.
|
|
244
|
+
- **Type Passing**: Both components pass `AppMessage` to `UECA.ComponentStruct` for type safety.
|
|
245
|
+
- **Posting**: `WeatherForm` uses `model.bus.unicast` to request weather data, updating its display with the response.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Best Practices
|
|
250
|
+
- **Use Clear Message Names**: Choose descriptive names (e.g., `"Api.GetWeatherForecast"`) to indicate purpose.
|
|
251
|
+
- **Handle Errors**: Implement error handling in asynchronous handlers or configure the global error handler `globalSettings.errorHandler` to handle all application errors in one place (e.g. write to the log, pop up a message, etc.).
|
|
252
|
+
- **Keep Payloads Minimal**: Only include necessary data in the message payload.
|
|
253
|
+
- **Centralize Message Types**: Define all message types in a single file for consistency.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Notes
|
|
258
|
+
- The message bus routes messages to the handler registered for the message type.
|
|
259
|
+
- Only one component should handle a specific message type to avoid conflicts (unless it's a broadcast message).
|
|
260
|
+
- Components interacting with the message bus (posting or handling) must include the message type in their structure.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Model Caching in UECA-React
|
|
2
|
+
#model_caching #state #reactivity #performance
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
Model caching in UECA-React is a powerful feature introduced in version 2.0 that enables components to persist their state across mounts and unmounts. By caching component models, UECA-React ensures that state is preserved when components are temporarily removed from the DOM (e.g., due to conditional rendering) and restored when they reappear. This enhances performance and improves user experience by maintaining state continuity without requiring manual state management.
|
|
6
|
+
|
|
7
|
+
Key aspects of model caching:
|
|
8
|
+
- **State Persistence**: Retains model state (e.g., form inputs, counters) during unmount/mount cycles.
|
|
9
|
+
- **Configurable Modes**: Supports `no-cache`, `cache`, and `auto-cache` modes via `UECA.globalSettings.modelCacheMode`.
|
|
10
|
+
- **Type-Safe**: Fully integrated with UECA’s TypeScript-based architecture.
|
|
11
|
+
- **Reactive**: Works seamlessly with MobX-driven reactivity.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## API/Methods
|
|
16
|
+
|
|
17
|
+
### Configuring Model Caching
|
|
18
|
+
- **UECA.globalSettings.modelCacheMode**
|
|
19
|
+
Sets the global caching mode for all component models.
|
|
20
|
+
- **Values**:
|
|
21
|
+
- `"no-cache"`: Disables caching; models are recreated on each mount.
|
|
22
|
+
- `"cache"`: Caches models only if their `cacheable` property is explicitly set to `true`.
|
|
23
|
+
- `"auto-cache"`: Automatically caches models unless `cacheable` is explicitly set to `false`.
|
|
24
|
+
- **Default**: `"auto-cache"`.
|
|
25
|
+
|
|
26
|
+
### Model Properties
|
|
27
|
+
- **cacheable**
|
|
28
|
+
A property in the component’s `props` that controls whether the model is cached.
|
|
29
|
+
- **Type**: `boolean | undefined`
|
|
30
|
+
- **Behavior**:
|
|
31
|
+
- `true`: Model is cached (in `"cache"` mode).
|
|
32
|
+
- `false`: Model is not cached (in `"auto-cache"` mode).
|
|
33
|
+
- `undefined`: Follows the global `modelCacheMode` (cached in `"auto-cache"`).
|
|
34
|
+
|
|
35
|
+
### Lifecycle Hooks
|
|
36
|
+
- **init()**: Called when a model is activated, either on creation or when retrieved from cache.
|
|
37
|
+
- **deinit()**: Called when a model is deactivated, typically when it’s cached but no longer in use.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Code Examples
|
|
42
|
+
|
|
43
|
+
### Example: Caching a Counter Component
|
|
44
|
+
This example demonstrates model caching with a `Counter` component that persists its state across mounts and a `Display` component that toggles its visibility.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import * as UECA from "ueca-react";
|
|
48
|
+
|
|
49
|
+
// Enable auto-caching globally
|
|
50
|
+
UECA.globalSettings.modelCacheMode = "auto-cache";
|
|
51
|
+
|
|
52
|
+
type CounterStruct = UECA.ComponentStruct<{
|
|
53
|
+
props: {
|
|
54
|
+
count: number;
|
|
55
|
+
}
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
type CounterParams = UECA.ComponentParams<CounterStruct>;
|
|
59
|
+
type CounterModel = UECA.ComponentModel<CounterStruct>;
|
|
60
|
+
|
|
61
|
+
function useCounter(params?: CounterParams): CounterModel {
|
|
62
|
+
const struct: CounterStruct = {
|
|
63
|
+
props: {
|
|
64
|
+
id: useCounter.name,
|
|
65
|
+
count: 0
|
|
66
|
+
},
|
|
67
|
+
init: () => {
|
|
68
|
+
console.log("Model activated with count:", model.count);
|
|
69
|
+
},
|
|
70
|
+
deinit: () => {
|
|
71
|
+
console.log("Model deactivated");
|
|
72
|
+
},
|
|
73
|
+
View: () => (
|
|
74
|
+
<div id={model.htmlId()}>
|
|
75
|
+
<p>Count: {model.count}</p>
|
|
76
|
+
<button onClick={() => model.count++}>Increment</button>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
};
|
|
80
|
+
const model = UECA.useComponent(struct, params);
|
|
81
|
+
return model;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type DisplayStruct = UECA.ComponentStruct<{
|
|
85
|
+
children: {
|
|
86
|
+
staticCounter: CounterModel;
|
|
87
|
+
dynamicCounter: CounterModel; // Optional declaration for dynamic access
|
|
88
|
+
};
|
|
89
|
+
}>;
|
|
90
|
+
|
|
91
|
+
type DisplayStructParams = UECA.ComponentParams<DisplayStruct>;
|
|
92
|
+
type DisplayStructModel = UECA.ComponentModel<DisplayStruct>;
|
|
93
|
+
|
|
94
|
+
function useDisplay(params?: DisplayStructParams): DisplayStructModel {
|
|
95
|
+
const struct: DisplayStruct = {
|
|
96
|
+
props: {
|
|
97
|
+
id: useDisplay.name
|
|
98
|
+
},
|
|
99
|
+
children: {
|
|
100
|
+
staticCounter: useCounter({
|
|
101
|
+
onChangeCount: () => console.log(`Static Counter changed to: ${model.staticCounter.count}`)
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
View: () => (
|
|
105
|
+
<div id={model.htmlId()}>
|
|
106
|
+
{/* Static child */}
|
|
107
|
+
<model.staticCounter.View />
|
|
108
|
+
{/* Dynamic child */}
|
|
109
|
+
<Counter id={"dynamicCounter"}
|
|
110
|
+
onChangeCount={() => console.log(`Dynamic Counter changed to: ${model.dynamicCounter.count}`)}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
};
|
|
115
|
+
const model = UECA.useComponent(struct, params);
|
|
116
|
+
return model;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const Counter = UECA.getFC(useCounter);
|
|
120
|
+
const Display = UECA.getFC(useDisplay);
|
|
121
|
+
|
|
122
|
+
export { CounterModel, useCounter, Counter, useDisplay, Display };
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- **Explanation**:
|
|
126
|
+
- **Global Configuration**: `UECA.globalSettings.modelCacheMode = "auto-cache"` enables caching for models unless `cacheable` is explicitly `false`.
|
|
127
|
+
- **Counter Component**: The `Counter` component persists its `count` state across mounts/unmounts due to caching. The `init` and `deinit` hooks log model activation/deactivation.
|
|
128
|
+
- **Display Component**: The `staticCounter` is a cached child model, and `dynamicCounter` is a dynamically instantiated component. Both maintain state when toggled in/out of the DOM.
|
|
129
|
+
- **Behavior**: Incrementing the counter and toggling visibility (e.g., via conditional rendering) preserves the `count` value, demonstrating state continuity.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Best Practices
|
|
134
|
+
- **Choose the Right Mode**: Use `"auto-cache"` for most applications to balance performance and simplicity. Reserve `"cache"` for explicit control or `"no-cache"` for version 1.0 behavior.
|
|
135
|
+
- **Set `cacheable` Explicitly**: When needed, define `cacheable` in `props` to override the global mode (e.g., `cacheable: false` for transient components).
|
|
136
|
+
- **Optimize `init` and `deinit`**: Use these hooks for setup/cleanup tasks (e.g., subscriptions) to manage resources efficiently during caching cycles.
|
|
137
|
+
- **Monitor Performance**: Avoid caching large models unnecessarily to prevent memory overhead.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Notes
|
|
142
|
+
- Model caching is enabled by default in version 2.0 with `"auto-cache"`, but it requires careful configuration for optimal memory usage.
|
|
143
|
+
- The `id` property (e.g., `id={"dynamicCounter"}`) is critical for cache identification, ensuring models are correctly restored.
|
|
144
|
+
- Caching integrates with UECA’s reactivity, preserving state changes made via MobX observables.
|