@je-es/client 0.0.1
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 +21 -0
- package/README.md +770 -0
- package/dist/main.cjs +173 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.d.cts +825 -0
- package/dist/main.d.ts +825 -0
- package/dist/main.js +173 -0
- package/dist/main.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
<!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
<div align="center">
|
|
5
|
+
<p>
|
|
6
|
+
<img src="./assets/img/logo.png" alt="logo" style="" height="80" />
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.1-black"/>
|
|
12
|
+
<img src="https://img.shields.io/badge/🔥-@je--es-black"/>
|
|
13
|
+
<br>
|
|
14
|
+
<img src="https://github.com/je-es/client/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
|
15
|
+
<img src="https://img.shields.io/badge/coverage-90%25-brightgreen" alt="Test Coverage" />
|
|
16
|
+
<img src="https://img.shields.io/github/issues/je-es/client?style=flat" alt="Github Repo Issues" />
|
|
17
|
+
<img src="https://img.shields.io/github/stars/je-es/client?style=social" alt="GitHub Repo stars" />
|
|
18
|
+
</div>
|
|
19
|
+
<br>
|
|
20
|
+
|
|
21
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
|
|
27
|
+
|
|
28
|
+
- ## Quick Start 🔥
|
|
29
|
+
|
|
30
|
+
> _**The simplest, fastest and most organized way to manage the front-end of web applications.**_
|
|
31
|
+
|
|
32
|
+
> _**This lib must run with [@je-es/server](https://github.com/je-es/server)**_.
|
|
33
|
+
|
|
34
|
+
> _We prefer to use [`space`](https://github.com//solution-lib/space) with [`@solution-dist/web`](https://github.com/solution-dist/web) for a better experience._
|
|
35
|
+
|
|
36
|
+
- ### Setup
|
|
37
|
+
|
|
38
|
+
> install [`space`](https://github.com/solution-lib/space) first.
|
|
39
|
+
|
|
40
|
+
- #### Create
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
> space init <name> -t web # This will clone a ready-to-use repo and make some changes to suit your app.
|
|
44
|
+
> cd <name> # Go to the project directory
|
|
45
|
+
> space install # Install the dependencies
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- #### Manage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
> space lint
|
|
52
|
+
> space build
|
|
53
|
+
> space test
|
|
54
|
+
> space start
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- #### Usage
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { Component, html, state, router } from '@je-es/client';
|
|
61
|
+
|
|
62
|
+
class MyComponent extends Component {
|
|
63
|
+
@state count = 0;
|
|
64
|
+
|
|
65
|
+
render() {
|
|
66
|
+
return html`
|
|
67
|
+
<div>
|
|
68
|
+
<h1>Count: ${this.count}</h1>
|
|
69
|
+
<button onclick=${() => this.count++}>
|
|
70
|
+
Increment
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
> space start
|
|
80
|
+
|
|
81
|
+
Building @je-es/client application...
|
|
82
|
+
Build completed successfully!
|
|
83
|
+
Output: ./src/frontend/static/js/client.js
|
|
84
|
+
|
|
85
|
+
Server started at http://localhost:3000
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
89
|
+
|
|
90
|
+
- ## Examples
|
|
91
|
+
|
|
92
|
+
- ### Component System
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { Component, html, css, state, computed } from '@je-es/client';
|
|
96
|
+
|
|
97
|
+
class TodoComponent extends Component {
|
|
98
|
+
@state todos = [];
|
|
99
|
+
@state filter = 'all';
|
|
100
|
+
|
|
101
|
+
@computed
|
|
102
|
+
get filteredTodos() {
|
|
103
|
+
return this.filter === 'all'
|
|
104
|
+
? this.todos
|
|
105
|
+
: this.todos.filter(t => t.status === this.filter);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render() {
|
|
109
|
+
return html`
|
|
110
|
+
<div class="todo-app">
|
|
111
|
+
<h1>My Todos</h1>
|
|
112
|
+
${this.filteredTodos.map(todo => html`
|
|
113
|
+
<div class="todo-item">
|
|
114
|
+
${todo.title}
|
|
115
|
+
</div>
|
|
116
|
+
`)}
|
|
117
|
+
</div>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
styles() {
|
|
122
|
+
return css`
|
|
123
|
+
.todo-app {
|
|
124
|
+
padding: 2rem;
|
|
125
|
+
max-width: 600px;
|
|
126
|
+
margin: 0 auto;
|
|
127
|
+
}
|
|
128
|
+
.todo-item {
|
|
129
|
+
padding: 1rem;
|
|
130
|
+
border-bottom: 1px solid #eee;
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
138
|
+
|
|
139
|
+
- ### Router Configuration
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { client, router } from '@je-es/client';
|
|
143
|
+
|
|
144
|
+
const clientApp = client({
|
|
145
|
+
app: {
|
|
146
|
+
root: '#app',
|
|
147
|
+
routes: [
|
|
148
|
+
{
|
|
149
|
+
path: '/',
|
|
150
|
+
component: () => import('./pages/HomePage'),
|
|
151
|
+
meta: { title: 'Home' }
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
path: '/users/:id',
|
|
155
|
+
component: () => import('./pages/UserPage'),
|
|
156
|
+
meta: { title: 'User Profile' },
|
|
157
|
+
beforeEnter: (to, from, next) => {
|
|
158
|
+
// Route guard
|
|
159
|
+
if (isAuthenticated()) {
|
|
160
|
+
next();
|
|
161
|
+
} else {
|
|
162
|
+
next('/login');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
router: {
|
|
169
|
+
mode: 'history',
|
|
170
|
+
base: '/',
|
|
171
|
+
scrollBehavior: 'smooth'
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Navigate programmatically
|
|
176
|
+
router.push('/users/123');
|
|
177
|
+
router.replace('/home');
|
|
178
|
+
router.back();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
182
|
+
|
|
183
|
+
- ### State Management (Store)
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { createStore, connect } from '@je-es/client';
|
|
187
|
+
|
|
188
|
+
// Create global store
|
|
189
|
+
const userStore = createStore({
|
|
190
|
+
state: {
|
|
191
|
+
user: null,
|
|
192
|
+
isAuthenticated: false,
|
|
193
|
+
preferences: {}
|
|
194
|
+
},
|
|
195
|
+
persist: true,
|
|
196
|
+
storage: 'localStorage',
|
|
197
|
+
storageKey: 'app-user-store'
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Subscribe to changes
|
|
201
|
+
userStore.subscribe((state) => {
|
|
202
|
+
console.log('User state changed:', state);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Update state
|
|
206
|
+
userStore.setState({
|
|
207
|
+
user: { name: 'John', email: 'john@example.com' },
|
|
208
|
+
isAuthenticated: true
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Connect to component
|
|
212
|
+
class UserProfile extends Component {
|
|
213
|
+
render() {
|
|
214
|
+
return html`<div>User: ${userStore.state.user?.name}</div>`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
connect(userStore, component, (state) => ({
|
|
219
|
+
user: state.user
|
|
220
|
+
}));
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
224
|
+
|
|
225
|
+
- ### React-Like Hooks
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import {
|
|
229
|
+
createFunctionalComponent,
|
|
230
|
+
useState,
|
|
231
|
+
useEffect,
|
|
232
|
+
useMemo
|
|
233
|
+
} from '@je-es/client';
|
|
234
|
+
|
|
235
|
+
const Counter = createFunctionalComponent((props) => {
|
|
236
|
+
const [count, setCount] = useState(0);
|
|
237
|
+
const [multiplier, setMultiplier] = useState(2);
|
|
238
|
+
|
|
239
|
+
// Computed value
|
|
240
|
+
const result = useMemo(() => {
|
|
241
|
+
return count * multiplier;
|
|
242
|
+
}, [count, multiplier]);
|
|
243
|
+
|
|
244
|
+
// Side effect
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
document.title = `Count: ${count}`;
|
|
247
|
+
|
|
248
|
+
return () => {
|
|
249
|
+
// Cleanup
|
|
250
|
+
document.title = 'App';
|
|
251
|
+
};
|
|
252
|
+
}, [count]);
|
|
253
|
+
|
|
254
|
+
return html`
|
|
255
|
+
<div>
|
|
256
|
+
<h2>Result: ${result}</h2>
|
|
257
|
+
<button onclick=${() => setCount(count + 1)}>
|
|
258
|
+
Increment
|
|
259
|
+
</button>
|
|
260
|
+
<button onclick=${() => setMultiplier(multiplier + 1)}>
|
|
261
|
+
Increase Multiplier
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
`;
|
|
265
|
+
}, 'Counter');
|
|
266
|
+
|
|
267
|
+
const component = new Counter({ initial: 5 });
|
|
268
|
+
await component.mount(container);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
272
|
+
|
|
273
|
+
- ### Smart Forms
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { SmartFormComponent } from '@je-es/client';
|
|
277
|
+
|
|
278
|
+
const loginForm = new SmartFormComponent({
|
|
279
|
+
fields: [
|
|
280
|
+
{
|
|
281
|
+
name: 'email',
|
|
282
|
+
type: 'email',
|
|
283
|
+
label: 'Email Address',
|
|
284
|
+
placeholder: 'Enter your email',
|
|
285
|
+
validation: {
|
|
286
|
+
required: true,
|
|
287
|
+
email: true,
|
|
288
|
+
message: 'Please enter a valid email'
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: 'password',
|
|
293
|
+
type: 'password',
|
|
294
|
+
label: 'Password',
|
|
295
|
+
validation: {
|
|
296
|
+
required: true,
|
|
297
|
+
minLength: 8,
|
|
298
|
+
message: 'Password must be at least 8 characters'
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'remember',
|
|
303
|
+
type: 'checkbox',
|
|
304
|
+
label: 'Remember me'
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
endpoint: '/api/auth/login',
|
|
308
|
+
method: 'POST',
|
|
309
|
+
autoValidate: true,
|
|
310
|
+
submitButton: {
|
|
311
|
+
label: 'Sign In',
|
|
312
|
+
loadingLabel: 'Signing in...',
|
|
313
|
+
className: 'btn-primary'
|
|
314
|
+
},
|
|
315
|
+
onSuccess: (data) => {
|
|
316
|
+
localStorage.setItem('token', data.token);
|
|
317
|
+
router.push('/dashboard');
|
|
318
|
+
},
|
|
319
|
+
onError: (error) => {
|
|
320
|
+
console.error('Login failed:', error);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await loginForm.mount(container);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
328
|
+
|
|
329
|
+
- ### Context API
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { createContext, Provider, useContext } from '@je-es/client';
|
|
333
|
+
|
|
334
|
+
// Create contexts
|
|
335
|
+
const ThemeContext = createContext({ theme: 'light' });
|
|
336
|
+
const UserContext = createContext({ user: null });
|
|
337
|
+
|
|
338
|
+
// Provider component
|
|
339
|
+
class App extends Component {
|
|
340
|
+
@state theme = 'dark';
|
|
341
|
+
@state user = { name: 'John' };
|
|
342
|
+
|
|
343
|
+
render() {
|
|
344
|
+
return html`
|
|
345
|
+
<${Provider}
|
|
346
|
+
context=${ThemeContext}
|
|
347
|
+
value=${{ theme: this.theme }}
|
|
348
|
+
>
|
|
349
|
+
<${Provider}
|
|
350
|
+
context=${UserContext}
|
|
351
|
+
value=${{ user: this.user }}
|
|
352
|
+
>
|
|
353
|
+
<${ConsumerComponent} />
|
|
354
|
+
</${Provider}>
|
|
355
|
+
</${Provider}>
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Consumer component
|
|
361
|
+
class ConsumerComponent extends Component {
|
|
362
|
+
render() {
|
|
363
|
+
const theme = useContext(ThemeContext, this);
|
|
364
|
+
const user = useContext(UserContext, this);
|
|
365
|
+
|
|
366
|
+
return html`
|
|
367
|
+
<div class="${theme.theme}">
|
|
368
|
+
Welcome, ${user.user?.name}!
|
|
369
|
+
</div>
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
376
|
+
|
|
377
|
+
- ### API Integration
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { api, http, configureApi } from '@je-es/client';
|
|
381
|
+
|
|
382
|
+
// Global configuration
|
|
383
|
+
configureApi({
|
|
384
|
+
baseURL: 'https://api.example.com',
|
|
385
|
+
timeout: 30000,
|
|
386
|
+
headers: {
|
|
387
|
+
'Content-Type': 'application/json'
|
|
388
|
+
},
|
|
389
|
+
interceptors: {
|
|
390
|
+
request: (config) => {
|
|
391
|
+
const token = localStorage.getItem('token');
|
|
392
|
+
if (token) {
|
|
393
|
+
config.headers['Authorization'] = `Bearer ${token}`;
|
|
394
|
+
}
|
|
395
|
+
return config;
|
|
396
|
+
},
|
|
397
|
+
response: (response) => {
|
|
398
|
+
console.log('Response:', response);
|
|
399
|
+
return response;
|
|
400
|
+
},
|
|
401
|
+
error: (error) => {
|
|
402
|
+
if (error.status === 401) {
|
|
403
|
+
router.push('/login');
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Make requests
|
|
411
|
+
const users = await http.get('/users');
|
|
412
|
+
const user = await http.get('/users/123');
|
|
413
|
+
const created = await http.post('/users', { name: 'Jane' });
|
|
414
|
+
const updated = await http.put('/users/123', { name: 'Jane Doe' });
|
|
415
|
+
await http.delete('/users/123');
|
|
416
|
+
|
|
417
|
+
// Advanced usage
|
|
418
|
+
const response = await api({
|
|
419
|
+
method: 'POST',
|
|
420
|
+
url: '/upload',
|
|
421
|
+
data: formData,
|
|
422
|
+
params: { folder: 'avatars' },
|
|
423
|
+
timeout: 60000
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
<br>
|
|
428
|
+
|
|
429
|
+
- ## API
|
|
430
|
+
|
|
431
|
+
- ### Component Lifecycle
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
class MyComponent extends Component {
|
|
435
|
+
// Called before component is mounted to DOM
|
|
436
|
+
async onBeforeMount(): void {
|
|
437
|
+
// Initialize data, fetch resources
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Called after component is mounted to DOM
|
|
441
|
+
async onMount(): void {
|
|
442
|
+
// Setup event listeners, start timers
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Called before component updates
|
|
446
|
+
async onBeforeUpdate(prevProps, prevState): void {
|
|
447
|
+
// Prepare for update
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Called after component updates
|
|
451
|
+
onUpdate(prevProps, prevState): void {
|
|
452
|
+
// React to changes
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Called before component unmounts
|
|
456
|
+
onBeforeUnmount(): void {
|
|
457
|
+
// Cleanup preparation
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Called after component unmounts
|
|
461
|
+
onUnmount(): void {
|
|
462
|
+
// Remove listeners, clear timers
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Called when error occurs
|
|
466
|
+
onError(error: Error, errorInfo): void {
|
|
467
|
+
// Handle errors
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Control whether component should update
|
|
471
|
+
shouldUpdate(prevProps, prevState): boolean {
|
|
472
|
+
return true; // or custom logic
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
478
|
+
|
|
479
|
+
- ### Decorators
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { state, computed, watch } from '@je-es/client';
|
|
483
|
+
|
|
484
|
+
class ReactiveComponent extends Component {
|
|
485
|
+
// Reactive state - triggers re-render on change
|
|
486
|
+
@state count = 0;
|
|
487
|
+
@state items = [];
|
|
488
|
+
@state user = { name: 'John' };
|
|
489
|
+
|
|
490
|
+
// Computed property - cached until dependencies change
|
|
491
|
+
@computed
|
|
492
|
+
get doubleCount() {
|
|
493
|
+
return this.count * 2;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@computed
|
|
497
|
+
get itemCount() {
|
|
498
|
+
return this.items.length;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Watch for property changes
|
|
502
|
+
@watch('count')
|
|
503
|
+
onCountChange(newValue, oldValue) {
|
|
504
|
+
console.log(`Count changed from ${oldValue} to ${newValue}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@watch('user')
|
|
508
|
+
onUserChange(newUser, oldUser) {
|
|
509
|
+
console.log('User updated:', newUser);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
render() {
|
|
513
|
+
return html`
|
|
514
|
+
<div>
|
|
515
|
+
Count: ${this.count}
|
|
516
|
+
Double: ${this.doubleCount}
|
|
517
|
+
</div>
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
524
|
+
|
|
525
|
+
- ### React-Like Hooks
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
import {
|
|
529
|
+
useState,
|
|
530
|
+
useEffect,
|
|
531
|
+
useMemo,
|
|
532
|
+
useCallback,
|
|
533
|
+
useRef,
|
|
534
|
+
useReducer,
|
|
535
|
+
useLocalStorage,
|
|
536
|
+
useDebounce,
|
|
537
|
+
usePrevious,
|
|
538
|
+
useToggle,
|
|
539
|
+
useInterval,
|
|
540
|
+
useFetch,
|
|
541
|
+
useWindowSize,
|
|
542
|
+
useEventListener
|
|
543
|
+
} from '@je-es/client';
|
|
544
|
+
|
|
545
|
+
// State management
|
|
546
|
+
const [count, setCount] = useState(0);
|
|
547
|
+
const [user, setUser] = useState({ name: 'John' });
|
|
548
|
+
|
|
549
|
+
// Side effects
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
console.log('Component mounted');
|
|
552
|
+
return () => console.log('Component unmounted');
|
|
553
|
+
}, []);
|
|
554
|
+
|
|
555
|
+
// Memoization
|
|
556
|
+
const expensiveValue = useMemo(() => {
|
|
557
|
+
return count * 2;
|
|
558
|
+
}, [count]);
|
|
559
|
+
|
|
560
|
+
// Callback memoization
|
|
561
|
+
const handleClick = useCallback(() => {
|
|
562
|
+
setCount(count + 1);
|
|
563
|
+
}, [count]);
|
|
564
|
+
|
|
565
|
+
// Persistent reference
|
|
566
|
+
const inputRef = useRef(null);
|
|
567
|
+
|
|
568
|
+
// Complex state
|
|
569
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
570
|
+
|
|
571
|
+
// LocalStorage sync
|
|
572
|
+
const [value, setValue] = useLocalStorage('key', defaultValue);
|
|
573
|
+
|
|
574
|
+
// Debounced value
|
|
575
|
+
const debouncedSearch = useDebounce(searchTerm, 500);
|
|
576
|
+
|
|
577
|
+
// Previous value
|
|
578
|
+
const prevCount = usePrevious(count);
|
|
579
|
+
|
|
580
|
+
// Boolean toggle
|
|
581
|
+
const [isOn, toggle] = useToggle(false);
|
|
582
|
+
|
|
583
|
+
// Interval
|
|
584
|
+
useInterval(() => {
|
|
585
|
+
console.log('Tick');
|
|
586
|
+
}, 1000);
|
|
587
|
+
|
|
588
|
+
// Data fetching
|
|
589
|
+
const { data, loading, error, refetch } = useFetch('/api/users');
|
|
590
|
+
|
|
591
|
+
// Window size
|
|
592
|
+
const { width, height } = useWindowSize();
|
|
593
|
+
|
|
594
|
+
// Event listener
|
|
595
|
+
useEventListener('click', handleClick, element);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
599
|
+
|
|
600
|
+
- ### Router API
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
import { router, Router } from '@je-es/client';
|
|
604
|
+
|
|
605
|
+
// Navigation
|
|
606
|
+
router.push('/users/123');
|
|
607
|
+
router.push('/search?q=test&page=2');
|
|
608
|
+
router.replace('/home');
|
|
609
|
+
router.back();
|
|
610
|
+
router.forward();
|
|
611
|
+
router.go(-2);
|
|
612
|
+
|
|
613
|
+
// Named routes
|
|
614
|
+
router.pushNamed('user', { id: '123' });
|
|
615
|
+
|
|
616
|
+
// Route guards
|
|
617
|
+
router.beforeEach((to, from, next) => {
|
|
618
|
+
if (to.meta.requiresAuth && !isAuthenticated()) {
|
|
619
|
+
next('/login');
|
|
620
|
+
} else {
|
|
621
|
+
next();
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
router.afterEach((to, from) => {
|
|
626
|
+
console.log(`Navigated from ${from.path} to ${to.path}`);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// Route info
|
|
630
|
+
const current = router.getCurrentRoute();
|
|
631
|
+
const isActive = router.isActive('/users');
|
|
632
|
+
const route = router.resolve('/users/123');
|
|
633
|
+
|
|
634
|
+
// Route outlet (in component)
|
|
635
|
+
render() {
|
|
636
|
+
return html`
|
|
637
|
+
<div>
|
|
638
|
+
<nav>...</nav>
|
|
639
|
+
${router.outlet()}
|
|
640
|
+
</div>
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
646
|
+
|
|
647
|
+
- ### Store API
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
import { Store, createStore, createComputedStore, connect } from '@je-es/client';
|
|
651
|
+
|
|
652
|
+
// Create store
|
|
653
|
+
const store = createStore({
|
|
654
|
+
state: { count: 0, user: null },
|
|
655
|
+
persist: true,
|
|
656
|
+
storage: 'localStorage',
|
|
657
|
+
storageKey: 'my-store',
|
|
658
|
+
middleware: [
|
|
659
|
+
(state, action) => {
|
|
660
|
+
console.log('State changed:', action, state);
|
|
661
|
+
}
|
|
662
|
+
]
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Get state
|
|
666
|
+
const state = store.state;
|
|
667
|
+
const count = store.get('count');
|
|
668
|
+
|
|
669
|
+
// Update state
|
|
670
|
+
store.setState({ count: 5 });
|
|
671
|
+
store.set('count', 10);
|
|
672
|
+
store.setState(prev => ({ count: prev.count + 1 }));
|
|
673
|
+
|
|
674
|
+
// Subscribe
|
|
675
|
+
const unsubscribe = store.subscribe((state) => {
|
|
676
|
+
console.log('State:', state);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
store.subscribeToKey('count', (value) => {
|
|
680
|
+
console.log('Count:', value);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Batch updates
|
|
684
|
+
store.batch(() => {
|
|
685
|
+
store.set('count', 1);
|
|
686
|
+
store.set('user', { name: 'Jane' });
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Computed store
|
|
690
|
+
const doubleStore = createComputedStore(
|
|
691
|
+
[store],
|
|
692
|
+
(state) => state.count * 2
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
// Clear/Reset
|
|
696
|
+
store.clear();
|
|
697
|
+
store.reset({ count: 0, user: null });
|
|
698
|
+
|
|
699
|
+
// Destroy
|
|
700
|
+
store.destroy();
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
704
|
+
|
|
705
|
+
- ### Utility Functions
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
import {
|
|
709
|
+
debounce,
|
|
710
|
+
throttle,
|
|
711
|
+
classNames,
|
|
712
|
+
formatDate,
|
|
713
|
+
deepClone,
|
|
714
|
+
deepMerge,
|
|
715
|
+
uniqueId,
|
|
716
|
+
sleep,
|
|
717
|
+
isEmpty,
|
|
718
|
+
capitalize,
|
|
719
|
+
kebabCase,
|
|
720
|
+
camelCase,
|
|
721
|
+
pascalCase,
|
|
722
|
+
truncate,
|
|
723
|
+
parseQuery,
|
|
724
|
+
stringifyQuery,
|
|
725
|
+
clamp
|
|
726
|
+
} from '@je-es/client';
|
|
727
|
+
|
|
728
|
+
// Function utilities
|
|
729
|
+
const debouncedFn = debounce(() => console.log('Called'), 300);
|
|
730
|
+
const throttledFn = throttle(() => console.log('Called'), 1000);
|
|
731
|
+
|
|
732
|
+
// String utilities
|
|
733
|
+
const classes = classNames('btn', { active: true, disabled: false });
|
|
734
|
+
const date = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
|
|
735
|
+
const cap = capitalize('hello'); // 'Hello'
|
|
736
|
+
const kebab = kebabCase('helloWorld'); // 'hello-world'
|
|
737
|
+
const camel = camelCase('hello-world'); // 'helloWorld'
|
|
738
|
+
const pascal = pascalCase('hello-world'); // 'HelloWorld'
|
|
739
|
+
const short = truncate('Long text here', 10); // 'Long te...'
|
|
740
|
+
|
|
741
|
+
// Object utilities
|
|
742
|
+
const cloned = deepClone(obj);
|
|
743
|
+
const merged = deepMerge(obj1, obj2, obj3);
|
|
744
|
+
|
|
745
|
+
// Other utilities
|
|
746
|
+
const id = uniqueId('prefix');
|
|
747
|
+
await sleep(1000);
|
|
748
|
+
const empty = isEmpty(value);
|
|
749
|
+
const clamped = clamp(150, 0, 100); // 100
|
|
750
|
+
|
|
751
|
+
// Query string
|
|
752
|
+
const params = parseQuery('?page=1&limit=10');
|
|
753
|
+
const query = stringifyQuery({ page: 1, limit: 10 });
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
<!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
|
|
761
|
+
|
|
762
|
+
<br>
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
<div align="center">
|
|
767
|
+
<a href="https://github.com/solution-lib/space"><img src="https://img.shields.io/badge/by-Space-black"/></a>
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|