0%


Developing frontend applications without frameworks means building web projects without using any frontend frameworks such as Vue or React, and instead relying directly on JavaScript, HTML, and CSS. This approach allows developers to gain a deeper understanding of the underlying principles and mechanisms of frontend development, while avoiding over-reliance on frameworks.

For static projects or low-complexity applications with limited functionality, developing without a framework is often more than sufficient. Code written with frameworks must be compiled before it can be rendered in the browser, which is why tools like Webpack exist. Frameworks such as Vue were created to solve specific pain points—like two-way data binding and automatic reactive updates—but they also require developers to follow framework-specific syntax and conventions. As frameworks evolve, developers must continuously learn and adapt to new versions.

Frameworks are undoubtedly beneficial for development efficiency, but for simple requirements, avoiding a framework can actually be the wiser choice. Whether or not to use a framework should always be decided based on the actual project requirements.

When developing multiple similar websites, unbundled code can be uploaded directly to the server and modified in place. In contrast, when using a framework, only the compiled output is deployed, and any changes require local modification and rebuilding, which can be more time-consuming. After becoming proficient in pure JavaScript development, developers can even go on to build their own tools or frameworks.

How to Properly Insert <script> Code in Vue (Including Ad Code Examples)

In real-world development, we often need to integrate third-party services such as advertising platforms, analytics tools, or customer support widgets. These services usually provide a block of code that includes <script> tags.

However, if you try to place <script> tags directly inside a Vue template (<template>), they will not work—and may even cause errors.

This article explains why <script> tags cannot be directly used inside Vue templates, and how to correctly inject scripts dynamically, with a ready-to-use example (using ad code as a case study).


1. Why You Can’t Write <script> Directly in a Vue Template

Vue Single File Components (SFC) have a clear structure:

  • <template>: markup / structure
  • <script>: logic
  • <style>: styles

Placing <script> tags inside <template> causes several problems:

  1. Vue treats <script> tags as plain text or strips them out
  2. It breaks Vue’s compilation process and may throw errors
  3. Third-party scripts (ads, analytics, trackers) will not execute at all

Therefore, to make external scripts work properly, they must be dynamically injected using DOM APIs during the component lifecycle.


2. Incorrect Example: Pasting Ad Code Directly (This Will NOT Work)

Suppose an ad network provides the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
atOptions = {
'key' : '1523b7ddb936e560aadef64a851d500c',
'format' : 'iframe',
'height' : 250,
'width' : 300,
'params' : {}
};
</script>
<script
type="text/javascript"
src="//www.highperformanceformat.com/1523b7ddb936e560aadef64a851d500c/invoke.js">
</script>

If you paste this code directly into a Vue <template>, the advertisement will never render.


3. The Correct Approach: Dynamically Inject Scripts in Vue

The core idea is simple:

  1. Place a container element in the template (e.g. <div id="de_v"></div>)
  2. Use the onMounted (or mounted) lifecycle hook
  3. Create <script> elements via JavaScript
  4. Append them to the DOM

4. Complete Example (Vue 3 + <script setup>)

Assume the ad should be rendered inside a container with the ID "de_v".

Template Section

1
2
3
<template>
<div id="de_v"></div>
</template>

Script Section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script setup>
import { onMounted } from "vue";

onMounted(() => {
const targetDiv = document.getElementById("de_v");
if (!targetDiv) return;

// Create the first script (ad configuration)
const script1 = document.createElement("script");
script1.type = "text/javascript";
script1.innerHTML = `
atOptions = {
'key' : '1523b7ddb936e560aadef64a851d500c',
'format' : 'iframe',
'height' : 250,
'width' : 300,
'params' : {}
};
`;

// Create the second script (external ad script)
const script2 = document.createElement("script");
script2.type = "text/javascript";
script2.src =
"//www.highperformanceformat.com/1523b7ddb936e560aadef64a851d500c/invoke.js";

// Append scripts to the container
targetDiv.appendChild(script1);
targetDiv.appendChild(script2);
});
</script>

5. Common Issues and Notes

1. Will the script be injected multiple times?

If the component is mounted and unmounted repeatedly, the scripts may be injected multiple times.

Solutions:

  • Check whether the script already exists before inserting
  • Or remove the scripts in onUnmounted

2. How to Handle Multiple Ad Slots?

Use different container IDs for each ad slot, for example:

1
2
<div id="ad1"></div>
<div id="ad2"></div>

Then inject scripts into the corresponding container.


3. What About SSR Frameworks (Nuxt)?

On the server side, document is not available.

You must run the script injection code on the client side only:

1
onMounted(() => { ... })

Otherwise, you will encounter:

1
document is not defined

6. Summary

  • You cannot directly place <script> tags inside Vue templates
  • The correct solution is to dynamically create and inject scripts during the component lifecycle
  • Using appendChild() ensures third-party scripts execute properly
  • This approach works for ads, analytics, tracking pixels, and external SDKs

By following the method described above, third-party scripts will run correctly in Vue-based applications.

Saving CSV Data from a Third-Party API into a Database

When data retrieved from a third-party API is returned in CSV format, a common and reliable approach is:

  1. First, save the CSV file to a local path
  2. Then use fs.createReadStream to read the file
  3. Finally, parse the CSV content and store it in a local database

Below is an example implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const processConversations = async (dateObj) => {
const username = 'your_account_name';
const password = 'password';

const year = dateObj.format('YYYY');
const month = dateObj.format('MM');
const day = dateObj.format('DD');
const hour = dateObj.format('HH');
const dateStr = `${year}-${month}-${day}-${hour}`;
const fileName = `partial_path`;
const url = `https://api/${fileName}`;
const outputPath = `/tmp/conversations`;
const tableName = 'test';

try {
// Download CSV
const response = await axios.get(url, {
responseType: 'stream',
headers: {
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`
}
});

const writer = fs.createWriteStream(outputPath);
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});

console.log('CSV file downloaded successfully:', fileName);

const rows = [];

await new Promise((resolve, reject) => {
fs.createReadStream(outputPath)
.pipe(csv())
.on('data', (row) => {
rows.push([
row['DATE_TIME']
? dayjs(row['DATE_TIME']).format('YYYY-MM-DD HH:mm:ss')
: null,
row['CLID'] || null,
row['CLID_SOURCE'] || null
]);
})
.on('end', resolve)
.on('error', reject);
});

if (rows.length === 0) {
console.warn(`No data found in CSV: ${fileName}`);
return;
}

const insertSQL = `
INSERT INTO \`${tableName}\` (
date_time, clid, clid_source
) VALUES ?
`;

await query(insertSQL, [rows]);
console.log(`Successfully inserted ${rows.length} records: ${fileName}`);
} catch (error) {
console.error(`Error (${fileName}):`, error.message);
}
};

The Role of Indexes

When you want to quickly find the data you need, you can use indexes.
With indexes, the database can directly locate the required data during retrieval, saving time—much like the table of contents at the front of a book, which helps you quickly find what you want to read.

If an index contains multiple columns, it follows the leftmost prefix rule, meaning it is used from left to right, starting with the leftmost column.

Types of Indexes

1. Unique Index

A unique index can be added to certain columns (such as an ID number) to ensure that no two records have the same value. By using the UNIQUE keyword, we can create a unique index.

1
ADD UNIQUE INDEX uni_sf (sf);

You can also add a unique constraint to a column without explicitly creating a unique index. In this case, the column is still required to have unique values.

1
ADD CONSTRAINT uni_name UNIQUE (name);

2. Primary Key Index

Once a primary key is created, an index is automatically created for it. This is why searching data by the primary key is faster.

A table can have only one primary key, but a primary key can consist of multiple columns, which is known as a composite (or joint) primary key.

1
ALTER TABLE table_name ADD PRIMARY KEY (column);

3. Normal (Non-unique) Index

Any column can have an index created on it. It is recommended to add indexes only to columns that are frequently queried. Avoid adding indexes to every column, as this can negatively impact performance.

1
ALTER TABLE table_name ADD INDEX index_name (column);


For testing and high UI requirements (for example, replacing the thumb with a custom image), the traditional @react-native-community/slider can no longer meet the needs. So I switched to a custom approach. After searching for a long time, I finally found a great library:
https://github.com/miblanchard/react-native-slider

Component Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
import React, { PureComponent } from 'react';
import {
Animated,
Easing,
I18nManager,
Image,
ImageSourcePropType,
LayoutChangeEvent,
PanResponder,
PanResponderInstance,
View,
ViewStyle,
} from 'react-native';
// styles
import { defaultStyles as styles } from './styles';
import type { Dimensions, SliderProps, SliderState } from './types';

type RectReturn = {
containsPoint: (nativeX: number, nativeY: number) => boolean;
height: number;
trackDistanceToPoint: (nativeX: number) => number;
width: number;
x: number;
y: number;
};

const Rect = ({
height,
width,
x,
y,
}: {
height: number;
width: number;
x: number;
y: number;
}) => ({
containsPoint: (nativeX: number, nativeY: number) =>
nativeX >= x &&
nativeY >= y &&
nativeX <= x + width &&
nativeY <= y + height,
height,
trackDistanceToPoint: (nativeX: number) => {
if (nativeX < x) {
return x - nativeX;
}

if (nativeX > x + width) {
return nativeX - (x + width);
}

return 0;
},
width,
x,
y,
});

const DEFAULT_ANIMATION_CONFIGS = {
spring: {
friction: 7,
tension: 100,
},
timing: {
duration: 150,
easing: Easing.inOut(Easing.ease),
delay: 0,
},
};

const normalizeValue = (
props: SliderProps,
value?: number | Array<number>,
): Array<number> => {
if (!value || (Array.isArray(value) && value.length === 0)) {
return [0];
}

const { maximumValue, minimumValue } = props;

const getBetweenValue = (inputValue: number) =>
Math.max(Math.min(inputValue, maximumValue), minimumValue);

if (!Array.isArray(value)) {
return [getBetweenValue(value)];
}

return value.map(getBetweenValue).sort((a, b) => a - b);
};

const updateValues = ({
values,
newValues = values,
}: {
values: number | Array<number> | Animated.Value | Array<Animated.Value>;
newValues?: number | Array<number> | Animated.Value | Array<Animated.Value>;
}): Animated.Value[] => {
if (
Array.isArray(newValues) &&
Array.isArray(values) &&
newValues.length !== values.length
) {
return updateValues({ values: newValues });
}

if (Array.isArray(values) && Array.isArray(newValues)) {
return values?.map((value: number | Animated.Value, index: number) => {
let valueToSet = newValues[index];
if (value instanceof Animated.Value) {
if (valueToSet instanceof Animated.Value) {
valueToSet = valueToSet.__getValue();
}
value.setValue(valueToSet);
return value;
}

if (valueToSet instanceof Animated.Value) {
return valueToSet;
}

return new Animated.Value(valueToSet);
});
}

return [new Animated.Value(0)];
};

const indexOfLowest = (values: Array<number>): number => {
let lowestIndex = 0;
values.forEach((value, index, array) => {
if (value < array[lowestIndex]) {
lowestIndex = index;
}
});
return lowestIndex;
};

export class Slider extends PureComponent<SliderProps, SliderState> {
constructor(props: SliderProps) {
super(props);
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder:
this._handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminationRequest:
this._handlePanResponderRequestEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
});
this.state = {
allMeasured: false,
containerSize: {
width: 0,
height: 0,
},
thumbSize: {
width: 0,
height: 0,
},
trackMarksValues: updateValues({
values: normalizeValue(this.props, this.props.trackMarks),
}),
values: updateValues({
values: normalizeValue(
this.props,
this.props.value instanceof Animated.Value
? this.props.value.__getValue()
: this.props.value,
),
}),
};
}

static defaultProps = {
animationType: 'timing',
debugTouchArea: false,
trackMarks: [],
maximumTrackTintColor: '#b3b3b3',
maximumValue: 1,
minimumTrackTintColor: '#3f3f3f',
minimumValue: 0,
step: 0,
thumbTintColor: '#343434',
trackClickable: true,
value: 0,
vertical: false,
};

static getDerivedStateFromProps(props: SliderProps, state: SliderState) {
if (
props.trackMarks &&
!!state.trackMarksValues &&
state.trackMarksValues.length > 0
) {
const newTrackMarkValues = normalizeValue(props, props.trackMarks);
const statePatch = {} as SliderState;

if (state.trackMarksValues) {
statePatch.trackMarksValues = updateValues({
values: state.trackMarksValues,
newValues: newTrackMarkValues,
});
}

return statePatch;
}
}

componentDidUpdate() {
const newValues = normalizeValue(
this.props,
this.props.value instanceof Animated.Value
? this.props.value.__getValue()
: this.props.value,
);
newValues.forEach((value, i) => {
if (!this.state.values[i]) {
this._setCurrentValue(value, i);
} else if (value !== this.state.values[i].__getValue()) {
if (this.props.animateTransitions) {
this._setCurrentValueAnimated(value, i);
} else {
this._setCurrentValue(value, i);
}
}
});
}

_getRawValues(
values: Array<Animated.Value> | Array<Animated.AnimatedInterpolation>,
) {
return values.map((value) => value.__getValue());
}

_handleStartShouldSetPanResponder = (e: any): boolean =>
this._thumbHitTest(e);

_handleMoveShouldSetPanResponder(): boolean {
return false;
}

_handlePanResponderGrant = (e: { nativeEvent: any }) => {
const { thumbSize } = this.state;
const { nativeEvent } = e;
this._previousLeft = this.props.trackClickable
? nativeEvent.locationX - thumbSize.width
: this._getThumbLeft(this._getCurrentValue(this._activeThumbIndex));

this.props?.onSlidingStart?.(this._getRawValues(this.state.values));
};

_handlePanResponderMove = (_e: any, gestureState: any) => {
if (this.props.disabled) {
return;
}

this._setCurrentValue(
this._getValue(gestureState),
this._activeThumbIndex,
() => {
this.props?.onValueChange?.(
this._getRawValues(this.state.values),
);
},
);
};

_handlePanResponderRequestEnd = () => {
return false;
};

_handlePanResponderEnd = (_e: any, gestureState: any) => {
if (this.props.disabled) {
return;
}

this._setCurrentValue(
this._getValue(gestureState),
this._activeThumbIndex,
() => {
if (this.props.trackClickable) {
this.props?.onValueChange?.(
this._getRawValues(this.state.values),
);
}

this.props?.onSlidingComplete?.(
this._getRawValues(this.state.values),
);
},
);

this._activeThumbIndex = 0;
};

_measureContainer = (e: LayoutChangeEvent) => {
this._handleMeasure('_containerSize', e);
};

_measureTrack = (e: LayoutChangeEvent) => {
this._handleMeasure('_trackSize', e);
};

_measureThumb = (e: LayoutChangeEvent) => {
this._handleMeasure('_thumbSize', e);
};

_handleMeasure = (
name: '_containerSize' | '_trackSize' | '_thumbSize',
e: LayoutChangeEvent,
) => {
const { width, height } = e.nativeEvent.layout;
const size = { width, height };

const currentSize = this[name];

if (
currentSize &&
width === currentSize.width &&
height === currentSize.height
) {
return;
}

this[name] = size;

if (this._containerSize && this._thumbSize) {
this.setState({
containerSize: this._containerSize,
thumbSize: this._thumbSize,
allMeasured: true,
});
}
};

_getRatio = (value: number) => {
const { maximumValue, minimumValue } = this.props;
return (value - minimumValue) / (maximumValue - minimumValue);
};

_getThumbLeft = (value: number) => {
const { containerSize, thumbSize } = this.state;
const { vertical } = this.props;

const standardRatio = this._getRatio(value);
const ratio = I18nManager.isRTL ? 1 - standardRatio : standardRatio;

return (
ratio *
((vertical ? containerSize.height : containerSize.width) -
thumbSize.width)
);
};

_getValue = (gestureState: { dx: number; dy: number }) => {
const { containerSize, thumbSize, values } = this.state;
const { maximumValue, minimumValue, step, vertical } = this.props;

const length = containerSize.width - thumbSize.width;
const thumbLeft = vertical
? this._previousLeft + gestureState.dy * -1
: this._previousLeft + gestureState.dx;

const nonRtlRatio = thumbLeft / length;
const ratio = I18nManager.isRTL ? 1 - nonRtlRatio : nonRtlRatio;

let minValue = minimumValue;
let maxValue = maximumValue;

const rawValues = this._getRawValues(values);
const buffer = step ? step : 0.1;

if (values.length === 2) {
if (this._activeThumbIndex === 1) {
minValue = rawValues[0] + buffer;
} else {
maxValue = rawValues[1] - buffer;
}
}

if (step) {
return Math.max(
minValue,
Math.min(
maxValue,
minimumValue +
Math.round(
(ratio * (maximumValue - minimumValue)) / step,
) *
step,
),
);
}

return Math.max(
minValue,
Math.min(
maxValue,
ratio * (maximumValue - minimumValue) + minimumValue,
),
);
};

_getCurrentValue = (thumbIndex: number = 0) =>
this.state.values[thumbIndex].__getValue();

_setCurrentValue = (
value: number,
thumbIndex: number | null | undefined,
callback?: () => void,
) => {
const safeIndex = thumbIndex ?? 0;
const animatedValue = this.state.values[safeIndex];

if (animatedValue) {
animatedValue.setValue(value);
callback?.();
} else {
this.setState((prevState: SliderState) => {
const newValues = [...prevState.values];
newValues[safeIndex] = new Animated.Value(value);
return { values: newValues };
}, callback);
}
};

_setCurrentValueAnimated = (value: number, thumbIndex: number = 0) => {
const { animationType } = this.props;
const animationConfig = {
...DEFAULT_ANIMATION_CONFIGS[animationType],
...this.props.animationConfig,
toValue: value,
useNativeDriver: false,
};
Animated[animationType](this.state.values[thumbIndex], animationConfig).start();
};

_getTouchOverflowSize = (): { width: number; height: number } => {
const { allMeasured, containerSize, thumbSize } = this.state;
const { thumbTouchSize } = this.props;

const size = { width: 40, height: 40 };

if (allMeasured) {
size.width = Math.max(0, (thumbTouchSize?.width || 0) - thumbSize.width);
size.height = Math.max(0, (thumbTouchSize?.height || 0) - containerSize.height);
}

return size;
};

_getTouchOverflowStyle = () => {
const { width, height } = this._getTouchOverflowSize();
const touchOverflowStyle = {} as ViewStyle;

if (width !== undefined && height !== undefined) {
const verticalMargin = -height / 2;
touchOverflowStyle.marginTop = verticalMargin;
touchOverflowStyle.marginBottom = verticalMargin;

const horizontalMargin = -width / 2;
touchOverflowStyle.marginLeft = horizontalMargin;
touchOverflowStyle.marginRight = horizontalMargin;
}

if (this.props.debugTouchArea === true) {
touchOverflowStyle.backgroundColor = 'orange';
touchOverflowStyle.opacity = 0.5;
}

return touchOverflowStyle;
};

_thumbHitTest = (e: { nativeEvent: any }) => {
const { nativeEvent } = e;
const { trackClickable } = this.props;
const { values } = this.state;

const hitThumb = values.find((_, i) => {
const thumbTouchRect = this._getThumbTouchRect(i);
const containsPoint = thumbTouchRect.containsPoint(
nativeEvent.locationX,
nativeEvent.locationY,
);

if (containsPoint) {
this._activeThumbIndex = i;
}

return containsPoint;
});

if (hitThumb) {
return true;
}

if (trackClickable) {
if (values.length === 1) {
this._activeThumbIndex = 0;
} else {
const thumbDistances = values.map((_value, index) => {
const thumbTouchRect = this._getThumbTouchRect(index);
return thumbTouchRect.trackDistanceToPoint(nativeEvent.locationX);
});
this._activeThumbIndex = indexOfLowest(thumbDistances);
}

return true;
}

return false;
};

_getThumbTouchRect = (thumbIndex: number = 0): RectReturn => {
const { containerSize, thumbSize } = this.state;
const { thumbTouchSize } = this.props;
const { height, width } = thumbTouchSize || { height: 40, width: 40 };

const touchOverflowSize = this._getTouchOverflowSize();

return Rect({
height,
width,
x:
touchOverflowSize.width / 2 +
this._getThumbLeft(this._getCurrentValue(thumbIndex)) +
(thumbSize.width - width) / 2,
y:
touchOverflowSize.height / 2 +
(containerSize.height - height) / 2,
});
};

_activeThumbIndex: number = 0;
_containerSize: Dimensions | null | undefined;
_panResponder: PanResponderInstance;
_previousLeft: number = 0;
_thumbSize: Dimensions | null | undefined;
_trackSize: Dimensions | null | undefined;

_renderDebugThumbTouchRect = (
thumbLeft: Animated.AnimatedInterpolation,
index: number,
) => {
const { height, y, width } = this._getThumbTouchRect() || {};
const positionStyle = { height, left: thumbLeft, top: y, width };
return (
<Animated.View
key={`debug-thumb-${index}`}
pointerEvents="none"
style={[styles.debugThumbTouchArea, positionStyle]}
/>
);
};

_renderThumbImage = (thumbIndex: number = 0) => {
const { thumbImage } = this.props;
if (!thumbImage) return null;

return (
<Image
source={
(Array.isArray(thumbImage)
? thumbImage[thumbIndex]
: thumbImage) as ImageSourcePropType
}
/>
);
};

render() {
const {
containerStyle,
debugTouchArea,
maximumTrackTintColor,
maximumValue,
minimumTrackTintColor,
minimumValue,
renderAboveThumbComponent,
renderTrackMarkComponent,
renderThumbComponent,
thumbStyle,
thumbTintColor,
trackStyle,
vertical,
...other
} = this.props;

const {
allMeasured,
containerSize,
thumbSize,
trackMarksValues,
values,
} = this.state;

const interpolatedThumbValues = values.map((value) =>
value.interpolate({
inputRange: [minimumValue, maximumValue],
outputRange: I18nManager.isRTL
? [0, -(containerSize.width - thumbSize.width)]
: [0, containerSize.width - thumbSize.width],
}),
);

const interpolatedTrackValues = values.map((value) =>
value.interpolate({
inputRange: [minimumValue, maximumValue],
outputRange: [0, containerSize.width - thumbSize.width],
}),
);

const interpolatedTrackMarksValues =
trackMarksValues &&
trackMarksValues.map((v) =>
v.interpolate({
inputRange: [minimumValue, maximumValue],
outputRange: I18nManager.isRTL
? [0, -(containerSize.width - thumbSize.width)]
: [0, containerSize.width - thumbSize.width],
}),
);

const valueVisibleStyle = {} as ViewStyle;
if (!allMeasured) valueVisibleStyle.opacity = 0;

const interpolatedRawValues = this._getRawValues(interpolatedTrackValues);
const minThumbValue = new Animated.Value(Math.min(...interpolatedRawValues));
const maxThumbValue = new Animated.Value(Math.max(...interpolatedRawValues));

const minimumTrackStyle = {
position: 'absolute',
left:
interpolatedTrackValues.length === 1
? new Animated.Value(0)
: Animated.add(minThumbValue, thumbSize.width / 2),
width:
interpolatedTrackValues.length === 1
? Animated.add(interpolatedTrackValues[0], thumbSize.width / 2)
: Animated.add(Animated.multiply(minThumbValue, -1), maxThumbValue),
backgroundColor: minimumTrackTintColor,
...valueVisibleStyle,
} as ViewStyle;

const touchOverflowStyle = this._getTouchOverflowStyle();

return (
<>
{renderAboveThumbComponent && (
<View style={styles.aboveThumbComponentsContainer}>
{interpolatedThumbValues.map((value, i) => (
<Animated.View
key={`slider-above-thumb-${i}`}
style={[
styles.renderThumbComponent,
{
bottom: 0,
transform: [
{ translateX: value },
{ translateY: 0 },
],
...valueVisibleStyle,
},
]}>
{renderAboveThumbComponent(i)}
</Animated.View>
))}
</View>
)}

<View
{...other}
style={[
styles.container,
vertical ? { transform: [{ rotate: '-90deg' }] } : {},
containerStyle,
]}
onLayout={this._measureContainer}>

<View
renderToHardwareTextureAndroid
style={[
styles.track,
{ backgroundColor: maximumTrackTintColor },
trackStyle,
]}
onLayout={this._measureTrack}
/>

<Animated.View
renderToHardwareTextureAndroid
style={[styles.track, trackStyle, minimumTrackStyle]}
/>

{renderTrackMarkComponent &&
interpolatedTrackMarksValues &&
interpolatedTrackMarksValues.map((value, i) => (
<Animated.View
key={`track-mark-${i}`}
style={[
styles.renderThumbComponent,
{
transform: [
{ translateX: value },
{ translateY: 0 },
],
...valueVisibleStyle,
},
]}>
{renderTrackMarkComponent(i)}
</Animated.View>
))}

{interpolatedThumbValues.map((value, i) => (
<Animated.View
key={`slider-thumb-${i}`}
style={[
renderThumbComponent
? styles.renderThumbComponent
: styles.thumb,
renderThumbComponent
? {}
: { backgroundColor: thumbTintColor, ...thumbStyle },
{
transform: [
{ translateX: value },
{ translateY: 0 },
],
...valueVisibleStyle,
},
]}
onLayout={this._measureThumb}>
{renderThumbComponent ? renderThumbComponent() : this._renderThumbImage(i)}
</Animated.View>
))}

<View
style={[styles.touchArea, touchOverflowStyle]}
{...this._panResponder.panHandlers}>
{!!debugTouchArea &&
interpolatedThumbValues.map((value, i) =>
this._renderDebugThumbTouchRect(value, i),
)}
</View>
</View>
</>
);
}
}

How to Use (Import)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Slider } from 'path-to-the-component-above';

<Slider
minimumValue={1}
maximumValue={7} // sliding range: 1 to 7
minimumTrackTintColor="#0A79C3"
maximumTrackTintColor="#F3F3F3"
step={1} // step size
value={targetDay} // default value, e.g. 4
onValueChange={targetDays =>
dispatch({
type: 'SetupWeekTarget/updateState',
payload: { targetDay: targetDays }
})
} // triggered when sliding
trackStyle={{ height: 10, borderRadius: 12 }}
containerStyle={{ width: SCREEN_WIDTH - 90 }}
thumbTintColor="transparent"
thumbImage={require('path-to-your-image')}
/>

More Parameters

For the full list of available props and detailed documentation, please refer to:
https://github.com/miblanchard/react-native-slider

At first, I thought that if I needed six verification code boxes, I should simply create six <input> elements.

However, after implementing it, I ran into various issues:

  • The cursor didn’t smoothly jump to the next input box
  • The keyboard would suddenly disappear
  • The overall typing experience felt clunky and not smooth

The correct approach is actually to use only one real input field, and simulate multiple input boxes through styling (spacing, highlighting, etc.).

Visually it looks like six input boxes, but in reality there is only one <input> element handling the input.


Correct Solution

1
2
3
4
5
6
7
8
9
<view class="w-80 h-100 rad-10 b-a-9 tc f-50" v-for="(item,index) in 6">
<input
class="w-80 h-100"
maxlength="1"
type="text"
@input="inputListener(index)"
:focus="focus && (focusIndex == index)"
/>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data() {
return {
focus: true,
code: ['', '', '', '', '', ''], // Stores the verification code digits
focusIndex: 0 // Index of the input that should receive focus
}
},
methods: {
// Triggered when input changes
inputListener(index) {
if (this.focusIndex !== index) {
this.focusIndex = index
}

if (index < 6) {
this.focus = true
this.focusIndex = index + 1
} else {
this.focus = false
}
}
}

Complete Version

You can refer to the full implementation here:

https://blog.csdn.net/u011423258/article/details/106683068?spm=1001.2014.3001.5506


What Is Serialization

Data transmitted over a TCP channel can only be in binary form. So converting a data structure or an object into binary form is called serialization. The reverse process is called deserialization. The rules that define how they convert back and forth are called a protocol.


What Is RPC

RPC stands for Remote Procedure Call. Most RPC systems are based on TCP. The general implementation flow is as follows:

  1. The frontend calls the corresponding interface through a Proxy. The Proxy converts information such as the RPC service name, method name, and parameters into an RPC Request object and passes it to the RPC framework.
  2. The RPC framework serializes the RPC Request object into binary form according to the RPC protocol.
  3. The RPC framework sends the binary data to the backend through a TCP channel.
  4. The backend deserializes the binary data back into an RPC Request object.
  5. The backend maps the deserialized data to the actual method it implements, passes in the parameters, obtains the result, and then packages it into an RPC Response object for the RPC framework.
  6. The RPC framework serializes the RPC Response object into binary form according to the RPC protocol and sends it back to the frontend through the TCP channel.
  7. After receiving the binary data, the frontend deserializes it into an RPC Response object, and the Proxy returns the result to the business code.


When do you need to use a frontend skeleton screen? It is mainly used when the homepage has not fully loaded yet. You can display a skeleton screen first to avoid a blank screen, which would otherwise create a poor user experience. So how do you implement it? You need to configure Dps.config.js as shown below.

In the configuration, the url field is the address of the page for which you want to generate the skeleton screen. You can even use a website like Baidu (https://baidu.com) for testing. The output field defines where the skeleton screen will be generated. The filepath specifies the page where the skeleton screen will be saved, usually the entry page of your project. The injectSelector specifies the DOM node where the skeleton screen will be injected.

The includeElement option allows you to customize or exclude certain nodes during rendering. You can target nodes by id, or check the tagName and define how they should be rendered. The init function runs before generating the skeleton screen and can be used to remove interfering elements.

Basic Configuration Field Description

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const dpsConfig = {
url: 'file:///Users/myname/Desktop/project/pro/scaleTips/dist/index.html#/',
// The page URL for which the skeleton screen will be generated.
// You can also try using https://baidu.com

output: {
filepath: '/Users/myname/Desktop/project/pro',
// The file where the generated skeleton screen will be stored,
// usually the entry page of the project

injectSelector: '#app'
// The DOM node where the skeleton screen will be injected
},

// header: {
// height: 40,
// background: '#1b9af4'
// },

// background: '#eee',
// animation: 'opacity 1s linear infinite;',

// Used to customize or exclude certain nodes during rendering
includeElement: function(node, draw) {
// Customize how a specific node is drawn, then return false
if (node.id == 'ui-alert') {
// Skip this node and its children
return false;
}

if (node.tagName.toLowerCase() === 'img') {
// Replace the image with a red block (width 100%, height 8%)
draw({
width: 100,
height: 8,
left: 0,
top: 0,
zIndex: 99999999,
background: 'red'
});
return false;
}
},

// writePageStructure: function(html) {
// // Customize how the skeleton screen HTML is handled
// // fs.writeFileSync(filepath, html);
// // console.log(html)
// },

init: function() {
// Operations before generating the skeleton screen,
// such as removing interfering elements
}
};

module.exports = dpsConfig;

When interacting with native applications, we usually cannot see the data returned or printed by the native side in our local environment. In this case, we need to use a debugging tool. A very useful tool is vConsole, which allows us to clearly view network requests, console logs, and other information. It is similar to the browser’s DevTools (F12 console). Below is how to integrate it:

H5 Mobile Debugging Tool (vConsole for Mobile)

1
2
3
4
5
6
<script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js"></script>
<script>
// Initialize vConsole
var vConsole = new VConsole();
console.log('vConsole is ready.');
</script>

If you want to interact with the native side, you also need to use window. There are two scenarios: sending data and receiving data. Different approaches are used for different cases.

Case 1: Sending Data to the Native App

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (window.webkit != undefined) {
// Defined on iOS side
window.webkit.messageHandlers.getUserInfo.postMessage(null);
} else {
// Defined on Android side
feedback.getUserInfo();
}

if (window.webkit != undefined) {
window.webkit.messageHandlers.feedback.postMessage({
title: "Test share title",
content: "Test share content",
url: "https://github.com/maying1992",
});
} else {
// try {
// result = feedback.getUserInfo();
// } catch(err){
// }
feedback.postMessage();
}

Case 2: Receiving Data from the Native App

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
created() {
// Attach method to window
window.setUserInfo = this.setUserInfo;
},

setUserInfo(param) {
const u = navigator.userAgent;
const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // iOS detection
const result = JSON.parse(param);

if (isiOS) {

this.queryParams = result.userInfo.token;

// Handle inconsistent values from native side
if (
result.userInfo.userId == undefined ||
result.userInfo.userId == 0
) {
this.usrIdd = 0;
} else {
this.usrIdd = result.userInfo.userId;
}

window.localStorage.setItem("lang", result.userInfo.language);

} else {

this.queryParams = result;

if (result.userId == undefined || result.userId == 0) {
this.usrIdd = 0;
} else {
this.usrIdd = result.userId;
}

window.localStorage.setItem("lang", result.language);
}

this.$i18n.locale = localStorage.getItem("lang");

return localStorage.getItem("lang");
}

When You Need to Redirect to the App via a Button

You can directly use an <a> tag with the href attribute to trigger the app, provided that the native application has defined a custom scheme such as:

1
myapp://***

This works similarly to how Instagram handles H5-to-app redirection using a scheme like:

1
instagram://**

If the redirect does not work, it usually means the native app does not support the corresponding URL scheme.

Notes on Encountering the Unknown Self

What is the “Unknown”?

The “self” refers to the “true self.” From the question “Who am I?” to the concept of “from existence to non-existence,” the entire book is structured around dialogues between Ruoling and a wise elder, revealing many profound life concepts.


Core Concepts

  • There is no one outside—only yourself.
    All external situations are actually projections of your inner world.

  • Golden Moments to Talk with the Subconscious
    The best times to imagine you’ve already achieved your goals are the moments between waking and sleeping—early morning just before fully waking up, or late at night before falling asleep. These times are when we are closest to our subconscious.

  • Key Practices: Awareness, Surrender, Overcoming the Peptide Self (Ego)

    • Awareness: Focus on your present self. Observe your emotions and reactions without judgment.
    • Surrender: Accept reality instead of resisting it.
    • Peptide Self (Ego): Represents inner voices of fear, comparison, and anxiety. Learn to recognize and transcend them.
  • Meditation, Gratitude, Giving

    • During meditation, visualize yourself already living your desired life.
    • Be grateful even before your goals are achieved.
    • Maintain a selfless, giving mindset.
  • Care Less About Others, Care More About Yourself

    • Bring your attention back to yourself and your inner world.
    • When emotions arise, be aware of which part of your body feels uncomfortable and understand its message.

Conclusion

This book is not only about discovering “who you are,” but also about reminding you to “become that true self.” Awakening inner strength requires continuous awareness, practice, and transcending the ego.