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.
- 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,275 @@
|
|
|
1
|
+
# Component Extension in UECA-React
|
|
2
|
+
#components #extension #inheritance #modularity #typescript
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
Component extension in UECA-React allows developers to create a hierarchy of components by inheriting functionality from a base component while adding or customizing features in derived components. This promotes code reuse, modularity, and maintainability, enabling the creation of reusable base library components (e.g., with validation or common props) that extended components inherit. The mechanism uses TypeScript generics for type safety and supports unlimited levels of inheritance, making it ideal for building scalable component libraries.
|
|
6
|
+
|
|
7
|
+
Key features:
|
|
8
|
+
- **Inheritance**: Derived components inherit props, methods, and events from the base component.
|
|
9
|
+
- **Type-Safe**: Leverages TypeScript generics for robust typing across the hierarchy.
|
|
10
|
+
- **Modular**: Facilitates reusable base components for common functionality.
|
|
11
|
+
- **Flexible**: Supports multi-level component hierarchies without restrictions.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## API/Methods
|
|
16
|
+
|
|
17
|
+
### Base Component Structure
|
|
18
|
+
- **BaseStruct**
|
|
19
|
+
Defines the common properties, methods, and events for the base component.
|
|
20
|
+
- **Example**: A base structure with validation logic, such as `EditControl` with props like `disabled` and methods like `validate`.
|
|
21
|
+
|
|
22
|
+
### Extending Components
|
|
23
|
+
- **UECA.ComponentStruct<T>**
|
|
24
|
+
A generic type that extends a base structure with additional properties, methods, or events.
|
|
25
|
+
- **Parameters**:
|
|
26
|
+
- `T`: The extended structure to merge with the base.
|
|
27
|
+
- **Usage**: Combines base and extended structures for type safety.
|
|
28
|
+
|
|
29
|
+
- **UECA.useExtendedComponent(baseStruct, extStruct, params, baseHook?)**
|
|
30
|
+
Creates a model that merges the base component’s structure with the extended structure.
|
|
31
|
+
- **Parameters**:
|
|
32
|
+
- `baseStruct`: The base component’s structure (e.g., validation logic).
|
|
33
|
+
- `extStruct`: The extended component’s structure (e.g., input-specific props).
|
|
34
|
+
- `params`: Optional parameters for initialization.
|
|
35
|
+
- `baseHook`: Optional base hook (defaults to `UECA.useComponent`).
|
|
36
|
+
- **Returns**: A model combining base and extended functionality.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Code Examples
|
|
41
|
+
|
|
42
|
+
### Example 1: Base EditControl Component
|
|
43
|
+
This example defines a generic `EditControl<T>` base component that provides validation functionality and common properties.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import * as UECA from "ueca-react";
|
|
47
|
+
|
|
48
|
+
type BaseStruct = UECA.ComponentStruct<{
|
|
49
|
+
props: {
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
readOnly: boolean;
|
|
52
|
+
mandatory: boolean;
|
|
53
|
+
style: React.CSSProperties;
|
|
54
|
+
modelsToValidate: EditControlModel[];
|
|
55
|
+
_internalValidationError: string;
|
|
56
|
+
};
|
|
57
|
+
methods: {
|
|
58
|
+
getValidationError: () => string;
|
|
59
|
+
validate: (errorText?: string) => Promise<void>;
|
|
60
|
+
isValid: () => boolean;
|
|
61
|
+
resetValidationErrors: () => void;
|
|
62
|
+
};
|
|
63
|
+
events: {
|
|
64
|
+
onInternalValidate: () => Promise<string>;
|
|
65
|
+
onValidate: () => Promise<string>;
|
|
66
|
+
};
|
|
67
|
+
}>;
|
|
68
|
+
|
|
69
|
+
type EditControlStruct<T extends UECA.GeneralComponentStruct> = BaseStruct & UECA.ComponentStruct<T>;
|
|
70
|
+
type EditControlParams<T extends BaseStruct = EditControlStruct<{}>> = UECA.ComponentParams<T>;
|
|
71
|
+
type EditControlModel<T extends BaseStruct = BaseStruct> = UECA.ComponentModel<T>;
|
|
72
|
+
|
|
73
|
+
function useEditControl<T extends BaseStruct>(extStruct: T, params?: EditControlParams<T>): EditControlModel<T> {
|
|
74
|
+
const struct: BaseStruct = {
|
|
75
|
+
props: {
|
|
76
|
+
disabled: false,
|
|
77
|
+
readOnly: false,
|
|
78
|
+
mandatory: false,
|
|
79
|
+
style: undefined,
|
|
80
|
+
modelsToValidate: [],
|
|
81
|
+
_internalValidationError: undefined
|
|
82
|
+
},
|
|
83
|
+
methods: {
|
|
84
|
+
getValidationError: () => {
|
|
85
|
+
const errors: string[] = [];
|
|
86
|
+
model.modelsToValidate?.forEach(x => {
|
|
87
|
+
const err = x.getValidationError();
|
|
88
|
+
err && errors.push(err);
|
|
89
|
+
});
|
|
90
|
+
model._internalValidationError && errors.push(model._internalValidationError);
|
|
91
|
+
return errors.length && errors.join("\r\n") || undefined;
|
|
92
|
+
},
|
|
93
|
+
validate: async (errorText?: string) => {
|
|
94
|
+
await Promise.all(model.modelsToValidate?.map(x => x.validate()));
|
|
95
|
+
model._internalValidationError = await model.onInternalValidate?.();
|
|
96
|
+
if (model._internalValidationError) return;
|
|
97
|
+
model._internalValidationError = await model.onValidate?.();
|
|
98
|
+
if (model._internalValidationError) return;
|
|
99
|
+
model._internalValidationError = errorText;
|
|
100
|
+
},
|
|
101
|
+
isValid: () => !model.getValidationError(),
|
|
102
|
+
resetValidationErrors: () => {
|
|
103
|
+
model.modelsToValidate?.map(async x => x.resetValidationErrors());
|
|
104
|
+
model._internalValidationError = undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const model = UECA.useExtendedComponent(struct, extStruct, params);
|
|
109
|
+
return model;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { useEditControl, EditControlModel, EditControlStruct, EditControlParams };
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Explanation**:
|
|
116
|
+
- Defines a base `EditControl` with validation-related props (`disabled`, `mandatory`), methods (`validate`, `isValid`), and events (`onInternalValidate`, `onValidate`).
|
|
117
|
+
- Uses generics (`T`) to allow extension with additional functionality.
|
|
118
|
+
- `UECA.useExtendedComponent` merges the base and extended structures.
|
|
119
|
+
|
|
120
|
+
### Example 2: Extended Input Component
|
|
121
|
+
This example extends `EditControl` to create an `Input` component with text input functionality and inherited validation.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { getFC } from "ueca-react";
|
|
125
|
+
import { Div, Input as RawInput } from "@components";
|
|
126
|
+
import { EditControlModel, EditControlParams, EditControlStruct, useEditControl } from "./EditControl";
|
|
127
|
+
|
|
128
|
+
type Struct = EditControlStruct<{
|
|
129
|
+
props: {
|
|
130
|
+
label: string;
|
|
131
|
+
value: string;
|
|
132
|
+
placeholder: string;
|
|
133
|
+
};
|
|
134
|
+
events: {
|
|
135
|
+
onClick: () => void;
|
|
136
|
+
onChange: (value: string, e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
137
|
+
onEnterKey: () => void;
|
|
138
|
+
};
|
|
139
|
+
}>;
|
|
140
|
+
|
|
141
|
+
type InputParams = EditControlParams<Struct>;
|
|
142
|
+
type InputModel = EditControlModel<Struct>;
|
|
143
|
+
|
|
144
|
+
function useInput(params?: InputParams): InputModel {
|
|
145
|
+
const struct: Struct = {
|
|
146
|
+
props: {
|
|
147
|
+
id: useInput.name,
|
|
148
|
+
value: undefined,
|
|
149
|
+
label: undefined,
|
|
150
|
+
placeholder: undefined,
|
|
151
|
+
},
|
|
152
|
+
events: {
|
|
153
|
+
onInternalValidate: async () => {
|
|
154
|
+
if (model.mandatory && !model.value) {
|
|
155
|
+
return `${model.label || "This field"} cannot be empty`;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
onChangeValue: () => model.resetValidationErrors()
|
|
159
|
+
},
|
|
160
|
+
View: () => (
|
|
161
|
+
<Div id={model.htmlId()}>
|
|
162
|
+
<RawInput
|
|
163
|
+
value={model.value}
|
|
164
|
+
placeholder={model.placeholder}
|
|
165
|
+
label={model.label}
|
|
166
|
+
mandatory={model.mandatory}
|
|
167
|
+
disabled={model.disabled}
|
|
168
|
+
readOnly={model.readOnly}
|
|
169
|
+
validationError={model.getValidationError()}
|
|
170
|
+
style={model.style}
|
|
171
|
+
onChange={(value: string, e: React.ChangeEvent<HTMLInputElement>) => {
|
|
172
|
+
model.value = value;
|
|
173
|
+
model.onChange?.(value, e);
|
|
174
|
+
}}
|
|
175
|
+
onClick={() => model.onClick?.()}
|
|
176
|
+
onEnterKey={() => model.onEnterKey?.()}
|
|
177
|
+
/>
|
|
178
|
+
</Div>
|
|
179
|
+
)
|
|
180
|
+
};
|
|
181
|
+
const model = useEditControl(struct, params);
|
|
182
|
+
return model;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const Input = getFC(useInput);
|
|
186
|
+
export { InputModel, useInput, Input };
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **Explanation**:
|
|
190
|
+
- Extends `EditControl` to inherit validation props, methods, and events.
|
|
191
|
+
- Adds input-specific props (`value`, `label`, `placeholder`) and events (`onClick`, `onChange`, `onEnterKey`).
|
|
192
|
+
- Implements `onInternalValidate` for mandatory field validation.
|
|
193
|
+
- The `View` uses a `RawInput` component, leveraging inherited props.
|
|
194
|
+
|
|
195
|
+
### Example 3: Using the Input Component in a Form
|
|
196
|
+
This example shows the `Input` component in a form with a button to trigger validation.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import * as UECA from "ueca-react";
|
|
200
|
+
import { useInput, InputModel } from "./Input";
|
|
201
|
+
import { ButtonModel, useButton } from "./Button";
|
|
202
|
+
|
|
203
|
+
type FormStruct = UECA.ComponentStruct<{
|
|
204
|
+
props: {
|
|
205
|
+
formValid: boolean
|
|
206
|
+
};
|
|
207
|
+
children: {
|
|
208
|
+
nameInput: InputModel,
|
|
209
|
+
okButton: ButtonModel,
|
|
210
|
+
};
|
|
211
|
+
}>;
|
|
212
|
+
|
|
213
|
+
function useForm(): UECA.ComponentModel<FormStruct> {
|
|
214
|
+
const struct: FormStruct = {
|
|
215
|
+
props: {
|
|
216
|
+
formValid: false
|
|
217
|
+
},
|
|
218
|
+
children: {
|
|
219
|
+
nameInput: useInput({
|
|
220
|
+
mandatory: true,
|
|
221
|
+
label: "Name",
|
|
222
|
+
onValidate: async () => {
|
|
223
|
+
return model.nameInput.value ? undefined : "Name is required";
|
|
224
|
+
},
|
|
225
|
+
onChange: () => {
|
|
226
|
+
model.nameInput.resetValidationErrors();
|
|
227
|
+
model.formValid = true;
|
|
228
|
+
}
|
|
229
|
+
}),
|
|
230
|
+
okButton: useButton({
|
|
231
|
+
text: "OK",
|
|
232
|
+
onClick: async () => {
|
|
233
|
+
await model.nameInput.validate();
|
|
234
|
+
model.formValid = model.nameInput.isValid();
|
|
235
|
+
}
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
View: () => (
|
|
239
|
+
<div>
|
|
240
|
+
<model.nameInput.View />
|
|
241
|
+
<model.okButton.View />
|
|
242
|
+
<p>Form Valid: {model.formValid.toString()}</p>
|
|
243
|
+
</div>
|
|
244
|
+
),
|
|
245
|
+
};
|
|
246
|
+
const model = UECA.useComponent(struct);
|
|
247
|
+
return model;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const Form = UECA.getFC(useForm);
|
|
251
|
+
export { Form };
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- **Explanation**:
|
|
255
|
+
- The `nameInput` inherits `EditControl`’s validation logic, with `onValidate` checking for empty input.
|
|
256
|
+
- The `onChange` event resets validation errors and sets `formValid` to `true`.
|
|
257
|
+
- The `okButton` triggers validation and updates `formValid`.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Best Practices
|
|
262
|
+
- **Centralize Common Logic**: Place shared functionality (e.g., validation) in base components for reuse.
|
|
263
|
+
- **Use TypeScript Generics**: Ensure type safety with `UECA.ComponentStruct<T>` for extensions.
|
|
264
|
+
- **Keep Extensions Minimal**: Add only necessary props or methods to derived components.
|
|
265
|
+
- **Leverage Inherited Methods**: Use base methods like `validate` for consistent behavior.
|
|
266
|
+
- **Chain Event Handlers**: Combine inherited events (e.g., `onChangeValue`) with custom handlers for flexibility.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Notes
|
|
271
|
+
- Component extension supports unlimited hierarchy levels for complex libraries.
|
|
272
|
+
- The `model` returned by `useExtendedComponent` includes both base and extended props/methods.
|
|
273
|
+
- Set the `id` prop (e.g., `useInput.name`) for proper model caching and identification.
|
|
274
|
+
- Errors in extended components are caught by `UECA.globalSettings.errorHandler`.
|
|
275
|
+
- Event handlers like `onChange` chain with inherited handlers (e.g., `onChangeValue` from `EditControl`).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Component IDs in UECA-React
|
|
2
|
+
#component-ids #basics #api #example #best_practices
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
In UECA-React, every component is assigned a unique identifier, or **ID**, which plays a critical role in managing the component's lifecycle, tracing, and integration with the DOM. The ID system ensures that each component can be uniquely identified within the application, which is essential for features like caching, logging, and automated testing. This article explains how component IDs are generated, how to use them, and their significance in the UECA-React ecosystem.
|
|
6
|
+
|
|
7
|
+
### Key Concepts
|
|
8
|
+
- **Component ID**: A unique string that identifies a component instance within the application.
|
|
9
|
+
- **Full ID**: A hierarchical path that represents the component's position in the parent-child structure (e.g., `app.ui.mainForm.button`).
|
|
10
|
+
- **HTML ID**: A DOM-compatible ID generated from the full ID, used to identify the component's root element in the DOM.
|
|
11
|
+
- **ID Initialization**: The ID is typically initialized using the hook's name for consistency and tracing purposes.
|
|
12
|
+
|
|
13
|
+
### Importance of Component IDs
|
|
14
|
+
- **Uniqueness**: Ensures each component can be distinctly identified, preventing conflicts in large applications.
|
|
15
|
+
- **Tracing**: Component IDs are used in logs to trace component creation, updates, and lifecycle events.
|
|
16
|
+
- **DOM Integration**: The HTML ID allows for easy selection and manipulation of the component's root element.
|
|
17
|
+
- **Testing**: Automated testing tools (e.g., UI Vision) can use the HTML ID to interact with specific components.
|
|
18
|
+
|
|
19
|
+
## How Component IDs Work
|
|
20
|
+
|
|
21
|
+
### 1. Initializing the Component ID
|
|
22
|
+
When defining a UECA component, the `id` property is typically set in the `props` section of the component structure. By convention, it is initialized to the name of the custom hook (e.g., `useMyComponent.name`), which provides a default unique identifier.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
props: {
|
|
26
|
+
id: useMyComponent.name, // e.g., "myComponent"
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **Note**: The `id` is a built-in property provided by `UECA.ComponentStruct` and is automatically included in the model. It can be overridden if needed, but using the hook's name is a best practice for consistency.
|
|
31
|
+
|
|
32
|
+
### 2. Full ID
|
|
33
|
+
The **full ID** is a hierarchical string that represents the component's path in the parent-child structure. It is constructed by concatenating the IDs of all parent components, separated by dots.
|
|
34
|
+
|
|
35
|
+
For example, if a `Button` component is nested inside a `Form` component, which is inside the `App` component, the full ID of the button would be:
|
|
36
|
+
```
|
|
37
|
+
app.form.button
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Accessing the Full ID**: The full ID can be retrieved using the `model.fullId()` method.
|
|
41
|
+
```typescript
|
|
42
|
+
const fullId = model.fullId(); // e.g., "app.form.button"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. HTML ID
|
|
46
|
+
The **HTML ID** is derived from the full ID and is used as the `id` attribute for the component's root element in the DOM. In development, it typically matches the full ID, but in production, it can be hashed for optimization.
|
|
47
|
+
|
|
48
|
+
- **Accessing the HTML ID**: The HTML ID can be retrieved using the `model.htmlId()` method.
|
|
49
|
+
```typescript
|
|
50
|
+
const htmlId = model.htmlId(); // e.g., "app.form.button" or a hashed version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- **Usage in JSX**: The HTML ID should be applied to the top-level element in the component's `View`.
|
|
54
|
+
```typescript
|
|
55
|
+
View: () => (
|
|
56
|
+
<div id={model.htmlId()}>
|
|
57
|
+
{/* Component UI */}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. ID in Tracing
|
|
63
|
+
Component IDs are essential for tracing. When `globalSettings.traceLog` is enabled, the framework logs events such as model creation, property changes, and lifecycle hooks, using the component's ID for identification.
|
|
64
|
+
|
|
65
|
+
- **Example Log Entry**:
|
|
66
|
+
```
|
|
67
|
+
create model=#123456 path=useMyComponent
|
|
68
|
+
change prop model=#123456 path=app.ui.myComponent[count] 0➝1
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- The `path` in the log corresponds to the component's full ID, making it easy to trace events back to specific components.
|
|
72
|
+
|
|
73
|
+
## API/Methods
|
|
74
|
+
- **`model.fullId(): string`**: Returns the full hierarchical ID of the component.
|
|
75
|
+
- **`model.htmlId(): string`**: Returns the DOM-compatible ID for the component's root element.
|
|
76
|
+
- **`model.id: string`**: The unique identifier of the component instance.
|
|
77
|
+
|
|
78
|
+
## Code Example
|
|
79
|
+
|
|
80
|
+
### Simple Button Component with ID
|
|
81
|
+
This example demonstrates how to initialize and use the component ID in a simple button component.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import * as UECA from "ueca-react";
|
|
85
|
+
|
|
86
|
+
// Declare the component structure
|
|
87
|
+
type ButtonStruct = UECA.ComponentStruct<{
|
|
88
|
+
props: {
|
|
89
|
+
caption: string;
|
|
90
|
+
};
|
|
91
|
+
methods: {
|
|
92
|
+
click: () => void;
|
|
93
|
+
};
|
|
94
|
+
}>;
|
|
95
|
+
|
|
96
|
+
// Declare the parameters type
|
|
97
|
+
type ButtonParams = UECA.ComponentParams<ButtonStruct>;
|
|
98
|
+
|
|
99
|
+
// Declare the model type
|
|
100
|
+
type ButtonModel = UECA.ComponentModel<ButtonStruct>;
|
|
101
|
+
|
|
102
|
+
// Custom hook
|
|
103
|
+
function useButton(params?: ButtonParams): ButtonModel {
|
|
104
|
+
const struct: ButtonStruct = {
|
|
105
|
+
props: {
|
|
106
|
+
id: useButton.name,
|
|
107
|
+
caption: "Click Me", // Default caption
|
|
108
|
+
},
|
|
109
|
+
methods: {
|
|
110
|
+
click: () => {
|
|
111
|
+
console.log(`Button ${model.id} clicked`);
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
View: () => (
|
|
115
|
+
<button id={model.htmlId()} onClick={model.click}>
|
|
116
|
+
{model.caption}
|
|
117
|
+
</button>
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
const model = UECA.useComponent(struct, params);
|
|
121
|
+
return model;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Functional component
|
|
125
|
+
const Button = UECA.getFC(useButton);
|
|
126
|
+
|
|
127
|
+
// Export for use in parent components
|
|
128
|
+
export { ButtonModel, useButton, Button };
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Using the Button in a Parent Component
|
|
132
|
+
This example shows how to integrate the `Button` component into a parent component and access its ID.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import * as UECA from "ueca-react";
|
|
136
|
+
import { useButton, ButtonModel } from "./Button";
|
|
137
|
+
|
|
138
|
+
type AppStruct = UECA.ComponentStruct<{
|
|
139
|
+
children: {
|
|
140
|
+
button: ButtonModel;
|
|
141
|
+
};
|
|
142
|
+
}>;
|
|
143
|
+
|
|
144
|
+
function useApp(): UECA.ComponentModel<AppStruct> {
|
|
145
|
+
const struct: AppStruct = {
|
|
146
|
+
props: {
|
|
147
|
+
id: "App"
|
|
148
|
+
},
|
|
149
|
+
children: {
|
|
150
|
+
button: useButton({ caption: "Submit" }), // Initialize Button with custom caption
|
|
151
|
+
},
|
|
152
|
+
View: () => (
|
|
153
|
+
<div id={model.htmlId()}>
|
|
154
|
+
<h1>App</h1>
|
|
155
|
+
{model.button.View}
|
|
156
|
+
<p>Button ID: {model.button.id}</p>
|
|
157
|
+
<p>Button Full ID: {model.button.fullId()}</p>
|
|
158
|
+
</div>
|
|
159
|
+
),
|
|
160
|
+
};
|
|
161
|
+
const model = UECA.useComponent(struct);
|
|
162
|
+
return model;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const App = UECA.getFC(useApp);
|
|
166
|
+
export default App;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- The parent component accesses the button's ID and full ID via `model.button.id` and `model.button.fullId()`.
|
|
170
|
+
|
|
171
|
+
## Best Practices
|
|
172
|
+
- **Initialize ID with Hook Name**: Always set `id: useMyComponent.name` in the `props` section for consistency and easier tracing.
|
|
173
|
+
- **Use Full ID for Logging**: When logging custom messages, include the full ID to trace the component's location in the hierarchy.
|
|
174
|
+
- **Apply HTML ID to Root Element**: Always set `id={model.htmlId()}` on the top-level JSX element to ensure proper DOM integration.
|
|
175
|
+
- **Avoid Manual ID Overrides**: Unless necessary, avoid overriding the ID after model creation to maintain uniqueness.
|
|
176
|
+
- **Use IDs in Tests**: Leverage the HTML ID in automated tests to target specific components (e.g., with UI Vision).
|
|
177
|
+
|
|
178
|
+
## Notes
|
|
179
|
+
- The `id` is automatically included in the model as a built-in property and does not need to be redefined in the component's `props` unless overriding the default value.
|
|
180
|
+
- In production, the HTML ID can be hashed for optimization by setting `globalSettings.hashHtmlId = true`.
|
|
181
|
+
- The full ID is constructed dynamically based on the component hierarchy, ensuring uniqueness even for components with the same base ID.
|
package/docs/{component-intergation-model.md → Component Integration Model in UECA-React.md }
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
# Component Integration Model
|
|
1
|
+
# Component Integration Model in UECA-React
|
|
2
|
+
#components #integration #composition #message_bus
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
UECA-React employs two primary methods for component interaction: direct model calls and communication through a common message bus. Each method serves distinct purposes and maintains different aspects of component interaction.
|
|
4
5
|
|
|
5
6
|
<p align="center"><img src="component-integration.png" /></p>
|
|
6
7
|
|
|
@@ -41,4 +42,4 @@ The diagram visually represents these interactions, emphasizing the dual approac
|
|
|
41
42
|
|
|
42
43
|
## Conclusion
|
|
43
44
|
|
|
44
|
-
By utilizing these two methods, the UECA ensures clear, maintainable, and scalable component interactions. This approach not only improves code readability and modularity but also facilitates testing and reduces the likelihood of bugs.
|
|
45
|
+
By utilizing these two methods, the UECA-React ensures clear, maintainable, and scalable component interactions. This approach not only improves code readability and modularity but also facilitates testing and reduces the likelihood of bugs.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# Component Mental Model
|
|
1
|
+
# Component Mental Model in UECA-React
|
|
2
|
+
#components #model #structure #integration #composition
|
|
2
3
|
|
|
3
4
|
## Rationale of creating UECA
|
|
4
5
|
|
|
5
6
|
In UI application development, the component approach is widely used, where the user interface is constructed from various unified parts called components. React, as a framework, is based on this model. However, standard React patterns often require developers to focus heavily on the interactions between these components, leading to redundant code and inconsistencies, especially in larger teams. This redundancy increases support costs and the likelihood of bugs over time.
|
|
6
7
|
|
|
7
|
-
## What is UECA component?
|
|
8
|
-
The UECA addresses the issue by encapsulating routine code and allowing developers to focus on the core principles of components. Here are the fundamental principles, visualized in the provided diagram.
|
|
8
|
+
## What is UECA-React component?
|
|
9
|
+
The UECA-React addresses the issue by encapsulating routine code and allowing developers to focus on the core principles of components. Here are the fundamental principles, visualized in the provided diagram.
|
|
9
10
|
|
|
10
11
|
<p align="center"><img src="component-mental-model.svg" /></p>
|
|
11
12
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Introduction to UECA-React Components
|
|
2
|
+
#components #basics #api #example #best_practices
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
UECA-React (Unified Encapsulated Component Architecture) provides a structured approach to building React applications by encapsulating component logic, state, and UI within a consistent template. Components in UECA-React are defined using a custom hook that creates a reactive model, leveraging MobX for state management. This article introduces the anatomy of a UECA component, explaining how to create and use components based on the standard template. All sections of the template are optional, allowing flexibility while maintaining uniformity.
|
|
6
|
+
|
|
7
|
+
UECA components offer:
|
|
8
|
+
- **Modularity**: Clear separation of concerns within a single structure.
|
|
9
|
+
- **Type Safety**: Full TypeScript support for robust development.
|
|
10
|
+
- **Reactivity**: Automatic UI updates via MobX integration.
|
|
11
|
+
- **Decoupled Logic**: Support for lifecycle hooks, events, and messaging.
|
|
12
|
+
|
|
13
|
+
## Component Template
|
|
14
|
+
|
|
15
|
+
The standard UECA component template consists of a TypeScript structure, a custom hook, and a functional component. Below is a breakdown of the template’s key elements.
|
|
16
|
+
|
|
17
|
+
### Structure Declaration
|
|
18
|
+
The component structure is defined using a TypeScript interface, specifying optional sections:
|
|
19
|
+
- **props**: Properties for state and configuration.
|
|
20
|
+
- **events**: Event handlers for component interactions.
|
|
21
|
+
- **children**: Nested UECA component models.
|
|
22
|
+
- **methods**: Functions available on the model.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
type MyCompStruct = UECA.ComponentStruct<{
|
|
26
|
+
props: { /* properties */ };
|
|
27
|
+
events: { /* event handlers */ };
|
|
28
|
+
children: { /* child components */ };
|
|
29
|
+
methods: { /* methods */ };
|
|
30
|
+
}>;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Properties and methods derived from UECA.ComponentStruct
|
|
34
|
+
- **props**: `id: string`, `cacheable: boolean`, `bus: MessageBus`
|
|
35
|
+
- **methods**: `disableOnChange()`, `enableOnChange()`, `changeNotifyDisabled(): boolean`, `fullId(): string`, `htmlId(): string`, `birthMark(): string`, `clearModelCache()`, `getChildrenModels(): AnyComponentModel[]`, `invalidateView()`
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Type Definitions
|
|
39
|
+
- **Parameters**: `UECA.ComponentParams<MyCompStruct>` defines the input parameters (props, events, hooks).
|
|
40
|
+
- **Model**: `UECA.ComponentModel<MyCompStruct>` defines the component’s reactive model.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
type MyCompParams = UECA.ComponentParams<MyCompStruct>;
|
|
44
|
+
type MyCompModel = UECA.ComponentModel<MyCompStruct>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Custom Hook
|
|
48
|
+
The custom hook (`useMyComp`) creates the component model using `UECA.useComponent`. It initializes the structure with optional sections:
|
|
49
|
+
- **props**: Initial state or configuration.
|
|
50
|
+
- **children**: Child component models.
|
|
51
|
+
- **methods**: Model methods.
|
|
52
|
+
- **events**: Event handlers.
|
|
53
|
+
- **messages**: Message bus handlers for listenning messages (for inter-component communication).
|
|
54
|
+
- **Lifecycle Hooks**: `constr`, `init`, `deinit`, `mount`, `unmount`, `draw`, `erase`.
|
|
55
|
+
- **View**: The component’s UI, returned as JSX.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
function useMyComp(params?: MyCompParams): MyCompModel {
|
|
59
|
+
const struct: MyCompStruct = {
|
|
60
|
+
View: () => <div>Hello World!</div>, // UI (otional)
|
|
61
|
+
// Include only needed sections and hooks
|
|
62
|
+
};
|
|
63
|
+
const model = UECA.useComponent(struct, params);
|
|
64
|
+
return model; // members of struct sections (props, events, methods and children) are accessible directly from model (e.g. model.id).
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Functional Component
|
|
69
|
+
The functional component is generated using `UECA.getFC` to wrap the custom hook.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const MyComp = UECA.getFC(useMyComp);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Exports
|
|
76
|
+
Export the model type, hook, and component for use in parent components.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
export { MyCompModel, useMyComp, MyComp };
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API/Methods
|
|
83
|
+
- **UECA.useComponent(struct, params)**: Creates or retrieves a reactive component model.
|
|
84
|
+
- **Example**: `const model = UECA.useComponent(struct, params);`
|
|
85
|
+
- **UECA.getFC(hook)**: Converts a custom hook into a React functional component.
|
|
86
|
+
- **Example**: `const MyComp = UECA.getFC(useMyComp);`
|
|
87
|
+
|
|
88
|
+
## Code Example
|
|
89
|
+
### Simple Counter Component
|
|
90
|
+
This example creates a `Counter` component with a single state property (`count`) and a method to increment it.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import * as UECA from "ueca-react";
|
|
94
|
+
|
|
95
|
+
// Declare the component structure
|
|
96
|
+
type CounterStruct = UECA.ComponentStruct<{
|
|
97
|
+
props: {
|
|
98
|
+
count: number
|
|
99
|
+
};
|
|
100
|
+
methods: {
|
|
101
|
+
increment: () => void
|
|
102
|
+
};
|
|
103
|
+
}>;
|
|
104
|
+
|
|
105
|
+
// Declare the parameters type
|
|
106
|
+
type CounterParams = UECA.ComponentParams<CounterStruct>;
|
|
107
|
+
|
|
108
|
+
// Declare the model type
|
|
109
|
+
type CounterModel = UECA.ComponentModel<CounterStruct>;
|
|
110
|
+
|
|
111
|
+
// Custom hook
|
|
112
|
+
function useCounter(params?: CounterParams): CounterModel {
|
|
113
|
+
const struct: CounterStruct = {
|
|
114
|
+
props: {
|
|
115
|
+
id: useCounter.name, // Initial ID set to the hook name for tracing log
|
|
116
|
+
count: 0 // Initial state
|
|
117
|
+
},
|
|
118
|
+
methods: {
|
|
119
|
+
increment: () => {
|
|
120
|
+
model.count++; // Update state
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
View: () => (
|
|
124
|
+
<div id={model.htmlId()}> {/* DOM element ID */}
|
|
125
|
+
<p>Count: {model.count}</p>
|
|
126
|
+
<button onClick={model.increment}>Increment</button>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
};
|
|
130
|
+
const model = UECA.useComponent(struct, params);
|
|
131
|
+
return model;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Functional component
|
|
135
|
+
const Counter = UECA.getFC(useCounter);
|
|
136
|
+
|
|
137
|
+
// Export for use in parent components
|
|
138
|
+
export { CounterModel, useCounter, Counter };
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Using the Counter in a Parent Component
|
|
142
|
+
This shows how to integrate the `Counter` component into a parent.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import * as UECA from "ueca-react";
|
|
146
|
+
import { useCounter, CounterModel } from "./Counter";
|
|
147
|
+
|
|
148
|
+
type AppStruct = UECA.ComponentStruct<{
|
|
149
|
+
children: {
|
|
150
|
+
counter: CounterModel
|
|
151
|
+
};
|
|
152
|
+
}>;
|
|
153
|
+
|
|
154
|
+
function useApp(): UECA.ComponentModel<AppStruct> {
|
|
155
|
+
const struct: AppStruct = {
|
|
156
|
+
props: {
|
|
157
|
+
id: "App", // ID of the application container
|
|
158
|
+
},
|
|
159
|
+
children: {
|
|
160
|
+
counter: useCounter({ count: 10 }) // Initialize Counter and set the count property to 10
|
|
161
|
+
},
|
|
162
|
+
View: () => (
|
|
163
|
+
<div id={model.htmlId()}>
|
|
164
|
+
<h1>My App</h1>
|
|
165
|
+
{model.counter.View} {/* Render Counter */}
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
};
|
|
169
|
+
const model = UECA.useComponent(struct);
|
|
170
|
+
return model;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const App = UECA.getFC(useApp);
|
|
174
|
+
export default App;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Best Practices
|
|
178
|
+
- **Use Minimal Sections**: Only include necessary sections (e.g., `props`, `methods`, `View`) to keep components lightweight.
|
|
179
|
+
- **Leverage Type Safety**: Always define `props` and other sections with TypeScript for robust integration.
|
|
180
|
+
- **Keep View Simple**: Delegate complex logic to methods, private methods or lifecycle hooks.
|
|
181
|
+
- **Export Model Type**: Ensure `MyCompModel` is exported for parent components to use in their `children` section.
|
|
182
|
+
- **Organize Private Methods**: Place helper functions after the `return model` statement for clarity.
|
|
183
|
+
- **Private Members**: Prefix names of private properties and methods with `_` for better organizing.
|
|
184
|
+
|
|
185
|
+
## Notes
|
|
186
|
+
- All template sections (`props`, `events`, `methods`, etc.) including `View` and hooks (`constr`, `init`, `deinit`, etc.) are optional.
|
|
187
|
+
- The model is reactive by default, powered by MobX, ensuring automatic UI updates when properties in `props` change.
|
|
188
|
+
- Only initialized properties in `props` are reactive (MobX observable), but properties starting with two underscores (e.g., `__itemList`) are non-reactive and considered private.
|
|
189
|
+
- Model properties and methods which names end with `View` are MobX observers (e.g. `TableHeaderView`).
|
|
190
|
+
- Components are cached (controlled by `cacheable` and `globalSettings.modelCacheMode`), preserving state across mounts.
|