imean-service-engine-htmx-plugin 2.4.0 → 2.6.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/dist/index.d.mts +50 -106
- package/dist/index.d.ts +50 -106
- package/dist/index.js +287 -580
- package/dist/index.mjs +287 -577
- package/docs/README.md +0 -38
- package/package.json +1 -1
- package/docs/alpinejs-interactive-components.md +0 -653
- package/docs/custom-form-field-renderers.md +0 -541
- package/docs/field-editor-best-practices.md +0 -320
- package/docs/hono-html-best-practices.md +0 -509
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
# Hono HTML 模板字符串最佳实践
|
|
2
|
-
|
|
3
|
-
本文档介绍在 HtmxAdminPlugin 中使用 Hono `html` 模板字符串开发自定义表单字段渲染器的最佳实践。
|
|
4
|
-
|
|
5
|
-
## 为什么使用 Hono `html` 模板字符串?
|
|
6
|
-
|
|
7
|
-
### 优势
|
|
8
|
-
|
|
9
|
-
1. **自动转义**:Hono 会自动转义 HTML 属性值,避免 XSS 攻击
|
|
10
|
-
2. **类型安全**:TypeScript 提供类型检查和代码提示
|
|
11
|
-
3. **语法高亮**:编辑器支持 HTML 语法高亮和格式化
|
|
12
|
-
4. **避免字符串拼接错误**:不需要手动拼接字符串,减少错误
|
|
13
|
-
5. **更好的可维护性**:代码结构清晰,易于理解和修改
|
|
14
|
-
|
|
15
|
-
### 与字符串拼接的对比
|
|
16
|
-
|
|
17
|
-
**❌ 错误:使用字符串拼接**
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
const htmlContent = `
|
|
21
|
-
<div class="container">
|
|
22
|
-
<input name="${fieldName}" value="${value}" />
|
|
23
|
-
</div>
|
|
24
|
-
`;
|
|
25
|
-
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**问题**:
|
|
29
|
-
- 需要手动转义 HTML 特殊字符
|
|
30
|
-
- 容易出错,特别是处理引号和特殊字符时
|
|
31
|
-
- 失去类型检查和语法高亮
|
|
32
|
-
- 代码可读性差
|
|
33
|
-
|
|
34
|
-
**✅ 正确:使用 Hono `html` 模板字符串**
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { html } from "hono/html";
|
|
38
|
-
|
|
39
|
-
return html`
|
|
40
|
-
<div class="container">
|
|
41
|
-
<input name="${fieldName}" value="${value}" />
|
|
42
|
-
</div>
|
|
43
|
-
`;
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**优势**:
|
|
47
|
-
- 自动转义,安全可靠
|
|
48
|
-
- 类型检查和语法高亮
|
|
49
|
-
- 代码清晰易读
|
|
50
|
-
|
|
51
|
-
## 核心原则
|
|
52
|
-
|
|
53
|
-
### 1. 使用 `html` 模板字符串生成所有 HTML
|
|
54
|
-
|
|
55
|
-
所有 HTML 都应该使用 `html` 模板字符串生成,避免手动字符串拼接:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
import { html } from "hono/html";
|
|
59
|
-
|
|
60
|
-
export function MyComponent(props: MyComponentProps) {
|
|
61
|
-
return html`
|
|
62
|
-
<div class="container">
|
|
63
|
-
<input name="${props.fieldName}" value="${props.value}" />
|
|
64
|
-
</div>
|
|
65
|
-
`;
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 2. `x-data` 属性的处理
|
|
70
|
-
|
|
71
|
-
**重要**:不要使用 `raw()` 包装 `x-data` 属性值。
|
|
72
|
-
|
|
73
|
-
**❌ 错误**:
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { html, raw } from "hono/html";
|
|
77
|
-
|
|
78
|
-
return html`
|
|
79
|
-
<div x-data='${raw(xDataContent)}'>
|
|
80
|
-
<!-- ... -->
|
|
81
|
-
</div>
|
|
82
|
-
`;
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**✅ 正确**:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { html } from "hono/html";
|
|
89
|
-
|
|
90
|
-
const xDataContent = `{
|
|
91
|
-
items: [],
|
|
92
|
-
init() {
|
|
93
|
-
// ...
|
|
94
|
-
}
|
|
95
|
-
}`;
|
|
96
|
-
|
|
97
|
-
return html`
|
|
98
|
-
<div x-data="${xDataContent}">
|
|
99
|
-
<!-- ... -->
|
|
100
|
-
</div>
|
|
101
|
-
`;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
**原因**:Hono 会自动处理 `x-data` 属性值的转义,使用 `raw()` 会破坏转义机制。
|
|
105
|
-
|
|
106
|
-
### 3. JavaScript 字符串值的处理
|
|
107
|
-
|
|
108
|
-
在 `x-data` 中,使用 `JSON.stringify` 安全地插入字符串值:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
const xDataContent = `{
|
|
112
|
-
fieldName: ${JSON.stringify(fieldName)},
|
|
113
|
-
placeholder: ${JSON.stringify(placeholder)},
|
|
114
|
-
items: ${JSON.stringify(initialItems)},
|
|
115
|
-
init() {
|
|
116
|
-
// ...
|
|
117
|
-
}
|
|
118
|
-
}`;
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**为什么使用 `JSON.stringify`?**
|
|
122
|
-
- 自动处理特殊字符(引号、换行符等)
|
|
123
|
-
- 确保 JavaScript 语法正确
|
|
124
|
-
- 避免手动转义错误
|
|
125
|
-
|
|
126
|
-
### 4. HTML 属性值的自动转义
|
|
127
|
-
|
|
128
|
-
Hono 会自动转义 HTML 属性值,直接使用原始值即可:
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
return html`
|
|
132
|
-
<input
|
|
133
|
-
name="${fieldName}"
|
|
134
|
-
value="${value}"
|
|
135
|
-
data-testid="input-${fieldName}"
|
|
136
|
-
/>
|
|
137
|
-
`;
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**不需要手动转义**:Hono 会自动处理 `&`、`<`、`>`、`"`、`'` 等特殊字符。
|
|
141
|
-
|
|
142
|
-
### 5. 避免字符串拼接
|
|
143
|
-
|
|
144
|
-
**❌ 错误:在函数中拼接 HTML 字符串**
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
const generateFieldHTML = (field: ModelField): string => {
|
|
148
|
-
return `
|
|
149
|
-
<div class="field">
|
|
150
|
-
<label>${field.label}</label>
|
|
151
|
-
<input name="${field.name}" />
|
|
152
|
-
</div>
|
|
153
|
-
`;
|
|
154
|
-
};
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**✅ 正确:使用 `html` 模板字符串**
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
const generateField = (field: ModelField) => {
|
|
161
|
-
return html`
|
|
162
|
-
<div class="field">
|
|
163
|
-
<label>${field.label}</label>
|
|
164
|
-
<input name="${field.name}" />
|
|
165
|
-
</div>
|
|
166
|
-
`;
|
|
167
|
-
};
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### 6. 条件渲染
|
|
171
|
-
|
|
172
|
-
使用三元表达式进行条件渲染:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
return html`
|
|
176
|
-
<div>
|
|
177
|
-
${field.required
|
|
178
|
-
? html`<span class="text-red-500">*</span>`
|
|
179
|
-
: ""}
|
|
180
|
-
<input name="${field.name}" ${field.required ? "required" : ""} />
|
|
181
|
-
</div>
|
|
182
|
-
`;
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### 7. 数组渲染
|
|
186
|
-
|
|
187
|
-
使用 `map` 配合 `html` 模板字符串:
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
return html`
|
|
191
|
-
<select>
|
|
192
|
-
${options.map(
|
|
193
|
-
(option) => html`
|
|
194
|
-
<option value="${option.value}">${option.label}</option>
|
|
195
|
-
`
|
|
196
|
-
)}
|
|
197
|
-
</select>
|
|
198
|
-
`;
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### 8. 插入已生成的 HTML
|
|
202
|
-
|
|
203
|
-
如果必须插入已生成的 HTML 字符串,使用 `raw()`:
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
import { html, raw } from "hono/html";
|
|
207
|
-
|
|
208
|
-
const fieldsHTML = fields.map((field) => generateFieldHTML(field)).join("");
|
|
209
|
-
|
|
210
|
-
return html`
|
|
211
|
-
<div>
|
|
212
|
-
${raw(fieldsHTML)}
|
|
213
|
-
</div>
|
|
214
|
-
`;
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
**注意**:只有在确实需要插入已生成的 HTML 字符串时才使用 `raw()`,优先使用 `html` 模板字符串。
|
|
218
|
-
|
|
219
|
-
## 完整示例
|
|
220
|
-
|
|
221
|
-
### 示例 1:字符串数组编辑器
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
import { html } from "hono/html";
|
|
225
|
-
|
|
226
|
-
export function StringArrayEditor(props: StringArrayEditorProps) {
|
|
227
|
-
const { value, fieldName, placeholder = "请输入内容" } = props;
|
|
228
|
-
const initialItems: string[] = value || [];
|
|
229
|
-
const initialValueJson = JSON.stringify(initialItems);
|
|
230
|
-
|
|
231
|
-
// 构建 x-data 字符串
|
|
232
|
-
const xDataContent = `{
|
|
233
|
-
items: ${initialValueJson},
|
|
234
|
-
fieldName: ${JSON.stringify(fieldName)},
|
|
235
|
-
placeholder: ${JSON.stringify(placeholder)},
|
|
236
|
-
init() {
|
|
237
|
-
const dataAttr = this.$el.getAttribute('data-initial-value');
|
|
238
|
-
if (dataAttr) {
|
|
239
|
-
try {
|
|
240
|
-
const parsed = JSON.parse(dataAttr);
|
|
241
|
-
if (Array.isArray(parsed)) {
|
|
242
|
-
this.items = parsed;
|
|
243
|
-
} else {
|
|
244
|
-
this.items = [];
|
|
245
|
-
}
|
|
246
|
-
} catch (e) {
|
|
247
|
-
console.error('Failed to parse initial value:', e);
|
|
248
|
-
this.items = [];
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
this.updateHiddenField();
|
|
252
|
-
},
|
|
253
|
-
updateHiddenField() {
|
|
254
|
-
const hiddenInput = this.$el.querySelector('input[name="${fieldName}"][type="hidden"]');
|
|
255
|
-
if (hiddenInput) {
|
|
256
|
-
hiddenInput.value = JSON.stringify(this.items);
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
addItem() {
|
|
260
|
-
this.items.push('');
|
|
261
|
-
this.updateHiddenField();
|
|
262
|
-
},
|
|
263
|
-
removeItem(index) {
|
|
264
|
-
this.items.splice(index, 1);
|
|
265
|
-
this.updateHiddenField();
|
|
266
|
-
}
|
|
267
|
-
}`;
|
|
268
|
-
|
|
269
|
-
return html`
|
|
270
|
-
<div
|
|
271
|
-
x-data="${xDataContent}"
|
|
272
|
-
data-initial-value="${initialValueJson}"
|
|
273
|
-
x-init="init()"
|
|
274
|
-
class="space-y-3"
|
|
275
|
-
>
|
|
276
|
-
<input
|
|
277
|
-
type="hidden"
|
|
278
|
-
name="${fieldName}"
|
|
279
|
-
value=""
|
|
280
|
-
data-testid="hidden-${fieldName}"
|
|
281
|
-
/>
|
|
282
|
-
<div class="space-y-3">
|
|
283
|
-
<div class="flex items-center justify-between">
|
|
284
|
-
<span class="text-sm text-gray-600">
|
|
285
|
-
共 <span x-text="items.length">0</span> 项
|
|
286
|
-
</span>
|
|
287
|
-
<button
|
|
288
|
-
type="button"
|
|
289
|
-
x-on:click="addItem()"
|
|
290
|
-
class="px-4 py-2 bg-blue-600 text-white rounded-lg"
|
|
291
|
-
data-testid="${fieldName}-add-button"
|
|
292
|
-
>
|
|
293
|
-
添加项
|
|
294
|
-
</button>
|
|
295
|
-
</div>
|
|
296
|
-
<div class="space-y-2" x-show="items.length > 0">
|
|
297
|
-
<template x-for="(item, index) in items" x-bind:key="index">
|
|
298
|
-
<div class="flex items-center gap-2">
|
|
299
|
-
<input
|
|
300
|
-
type="text"
|
|
301
|
-
x-bind:value="items[index] || ''"
|
|
302
|
-
x-on:input="updateItem(index, $event.target.value)"
|
|
303
|
-
class="flex-1 px-3 py-2 border rounded-lg"
|
|
304
|
-
x-bind:data-testid="fieldName + '-input-' + index"
|
|
305
|
-
/>
|
|
306
|
-
<button
|
|
307
|
-
type="button"
|
|
308
|
-
x-on:click="removeItem(index)"
|
|
309
|
-
class="px-3 py-2 text-red-600"
|
|
310
|
-
x-bind:data-testid="fieldName + '-remove-button-' + index"
|
|
311
|
-
>
|
|
312
|
-
删除
|
|
313
|
-
</button>
|
|
314
|
-
</div>
|
|
315
|
-
</template>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
319
|
-
`;
|
|
320
|
-
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### 示例 2:对象编辑器(动态生成字段)
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
import { html } from "hono/html";
|
|
327
|
-
|
|
328
|
-
export function ObjectEditor(props: ObjectEditorProps) {
|
|
329
|
-
const { value, fieldName, objectSchema } = props;
|
|
330
|
-
const fields = parseSchemaToFields(objectSchema);
|
|
331
|
-
const initialValueJson = JSON.stringify(value || {});
|
|
332
|
-
|
|
333
|
-
const xDataContent = `{
|
|
334
|
-
obj: {},
|
|
335
|
-
init() {
|
|
336
|
-
const dataAttr = this.$el.getAttribute('data-initial-value');
|
|
337
|
-
if (dataAttr) {
|
|
338
|
-
try {
|
|
339
|
-
this.obj = JSON.parse(dataAttr);
|
|
340
|
-
} catch (e) {
|
|
341
|
-
console.error('Failed to parse initial value:', e);
|
|
342
|
-
this.obj = {};
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
this.updateHiddenField();
|
|
346
|
-
},
|
|
347
|
-
updateHiddenField() {
|
|
348
|
-
const hiddenInput = this.$el.querySelector('input[name="${fieldName}"][type="hidden"]');
|
|
349
|
-
if (hiddenInput) {
|
|
350
|
-
hiddenInput.value = JSON.stringify(this.obj);
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
updateField(fieldName, value, fieldType, required) {
|
|
354
|
-
// 更新逻辑
|
|
355
|
-
this.updateHiddenField();
|
|
356
|
-
}
|
|
357
|
-
}`;
|
|
358
|
-
|
|
359
|
-
// 生成字段组件
|
|
360
|
-
const generateField = (field: ModelField) => {
|
|
361
|
-
const fieldId = `${fieldName}-${field.name}`;
|
|
362
|
-
const fieldNameVar = `obj.${field.name}`;
|
|
363
|
-
const fieldNameForJs = JSON.stringify(field.name);
|
|
364
|
-
|
|
365
|
-
return html`
|
|
366
|
-
<div class="space-y-2" data-testid="${fieldName}-field-${field.name}">
|
|
367
|
-
<label
|
|
368
|
-
for="${fieldId}"
|
|
369
|
-
class="block text-sm font-semibold text-gray-700"
|
|
370
|
-
>
|
|
371
|
-
${field.label}
|
|
372
|
-
${field.required ? html`<span class="text-red-500 ml-1">*</span>` : ""}
|
|
373
|
-
</label>
|
|
374
|
-
<input
|
|
375
|
-
type="text"
|
|
376
|
-
id="${fieldId}"
|
|
377
|
-
x-bind:value="${fieldNameVar} || ''"
|
|
378
|
-
x-on:input="updateField(${fieldNameForJs}, $event.target.value, 'text', ${field.required})"
|
|
379
|
-
class="w-full px-3 py-2 border rounded-lg"
|
|
380
|
-
data-testid="${fieldName}-input-${field.name}"
|
|
381
|
-
${field.required ? "required" : ""}
|
|
382
|
-
/>
|
|
383
|
-
</div>
|
|
384
|
-
`;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
return html`
|
|
388
|
-
<div
|
|
389
|
-
x-data="${xDataContent}"
|
|
390
|
-
data-initial-value="${initialValueJson}"
|
|
391
|
-
x-init="init()"
|
|
392
|
-
class="space-y-4"
|
|
393
|
-
>
|
|
394
|
-
<input
|
|
395
|
-
type="hidden"
|
|
396
|
-
name="${fieldName}"
|
|
397
|
-
value=""
|
|
398
|
-
data-testid="hidden-${fieldName}"
|
|
399
|
-
/>
|
|
400
|
-
<div class="space-y-4">
|
|
401
|
-
${fields.map((field) => generateField(field))}
|
|
402
|
-
</div>
|
|
403
|
-
</div>
|
|
404
|
-
`;
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
## Alpine.js 表达式中的字符串拼接
|
|
409
|
-
|
|
410
|
-
在 Alpine.js 表达式中(如 `x-bind:data-testid`),使用字符串拼接而不是模板字符串:
|
|
411
|
-
|
|
412
|
-
**❌ 错误**:
|
|
413
|
-
|
|
414
|
-
```typescript
|
|
415
|
-
x-bind:data-testid="`${fieldName}-item-${index}`"
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
**✅ 正确**:
|
|
419
|
-
|
|
420
|
-
```typescript
|
|
421
|
-
x-bind:data-testid="fieldName + '-item-' + index"
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
**原因**:Alpine.js 表达式是 JavaScript 代码,模板字符串在运行时可能无法正确解析。
|
|
425
|
-
|
|
426
|
-
## 常见错误和解决方案
|
|
427
|
-
|
|
428
|
-
### 错误 1:在 `x-data` 中使用 `raw()`
|
|
429
|
-
|
|
430
|
-
**错误**:
|
|
431
|
-
```typescript
|
|
432
|
-
return html`
|
|
433
|
-
<div x-data='${raw(xDataContent)}'>
|
|
434
|
-
<!-- ... -->
|
|
435
|
-
</div>
|
|
436
|
-
`;
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
**解决方案**:直接使用,不要包装 `raw()`:
|
|
440
|
-
```typescript
|
|
441
|
-
return html`
|
|
442
|
-
<div x-data="${xDataContent}">
|
|
443
|
-
<!-- ... -->
|
|
444
|
-
</div>
|
|
445
|
-
`;
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### 错误 2:手动转义 HTML
|
|
449
|
-
|
|
450
|
-
**错误**:
|
|
451
|
-
```typescript
|
|
452
|
-
const escapedValue = value.replace(/&/g, "&").replace(/</g, "<");
|
|
453
|
-
return html`<input value="${escapedValue}" />`;
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
**解决方案**:让 Hono 自动转义:
|
|
457
|
-
```typescript
|
|
458
|
-
return html`<input value="${value}" />`;
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### 错误 3:字符串拼接生成 HTML
|
|
462
|
-
|
|
463
|
-
**错误**:
|
|
464
|
-
```typescript
|
|
465
|
-
const htmlContent = `<div class="${className}">${content}</div>`;
|
|
466
|
-
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
**解决方案**:使用 `html` 模板字符串:
|
|
470
|
-
```typescript
|
|
471
|
-
return html`<div class="${className}">${content}</div>`;
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### 错误 4:在 Alpine.js 表达式中使用模板字符串
|
|
475
|
-
|
|
476
|
-
**错误**:
|
|
477
|
-
```typescript
|
|
478
|
-
x-bind:data-testid="`${fieldName}-item-${index}`"
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
**解决方案**:使用字符串拼接:
|
|
482
|
-
```typescript
|
|
483
|
-
x-bind:data-testid="fieldName + '-item-' + index"
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
## 检查清单
|
|
487
|
-
|
|
488
|
-
在开发自定义表单字段渲染器时,请确保:
|
|
489
|
-
|
|
490
|
-
- [ ] 使用 `html` 模板字符串生成所有 HTML
|
|
491
|
-
- [ ] 不在 `x-data` 属性上使用 `raw()`
|
|
492
|
-
- [ ] 在 `x-data` 中使用 `JSON.stringify` 插入字符串值
|
|
493
|
-
- [ ] 不在 HTML 属性值上手动转义
|
|
494
|
-
- [ ] 避免字符串拼接,使用 `html` 模板字符串
|
|
495
|
-
- [ ] 在 Alpine.js 表达式中使用字符串拼接而不是模板字符串
|
|
496
|
-
- [ ] 条件渲染使用三元表达式
|
|
497
|
-
- [ ] 数组渲染使用 `map` 配合 `html` 模板字符串
|
|
498
|
-
|
|
499
|
-
## 总结
|
|
500
|
-
|
|
501
|
-
使用 Hono `html` 模板字符串的最佳实践:
|
|
502
|
-
|
|
503
|
-
1. **始终使用 `html` 模板字符串**:避免字符串拼接
|
|
504
|
-
2. **信任自动转义**:让 Hono 处理 HTML 转义
|
|
505
|
-
3. **使用 `JSON.stringify`**:在 JavaScript 代码中安全插入字符串值
|
|
506
|
-
4. **不要使用 `raw()`**:除非确实需要插入已生成的 HTML
|
|
507
|
-
5. **保持代码清晰**:使用条件表达式和数组方法,而不是字符串拼接
|
|
508
|
-
|
|
509
|
-
遵循这些最佳实践,可以编写出更安全、更易维护、更少错误的代码。
|