Guide et bonnes pratiques
Ce guide décrit les conventions de base, les modèles de conception de code et les flux de travail de refactorisation courants pour écrire des applications réactives déclaratives, propres et hautes performances avec @beforesemicolon/markup.
Règles de base
- Préférez les aides déclaratives aux branchements : évitez d'écrire des opérations ternaires JavaScript en ligne ou des fonctions impératives dans les chaînes du modèle HTML. Privilégiez toujours les helpers Markup intégrés (
when,repeat,pick,is, etc.). - Gardez les effets secondaires hors des fonctions de rendu : gardez le rendu du modèle strictement sans effets secondaires. Les effets secondaires doivent résider dans les blocs
effect()ou les hooks de cycle de vie Web Component (tels queonMount). - Immuabilité de l'état : ne mute pas directement les objets utilisés comme état canonique. Suivez les modifications dérivées/en attente séparément et fusionnez-les uniquement au moment du rendu.
- Préserver les collections sources : conservez les tableaux sources intacts lors de la dérivation de vues filtrées ou triées. La suppression d'un filtre ou d'une entrée de recherche devrait restaurer la liste complète des sources sans nécessiter de réinitialisation manuelle des données.
- Liaison de valeur réactive : liez directement les props et l'état (par exemple,
disabled="${isDisabled}"au lieu dedisabled="${isDisabled()}") pour permettre à Markup de configurer les écouteurs de manière chirurgicale.
Refactoriser les flux de travail
Voici comment migrer les habitudes impératives traditionnelles vers un code Markup propre et déclaratif.
1. Interface utilisateur conditionnelle (si/sinon)
Évitez d'écrire des conditions JavaScript ou des portes logiques en ligne dans les modèles.
Impératif/Éviter :
html`
<div>
${() => (isLoading() ? html`<p>Loading...</p>` : html`<p>Loaded!</p>`)}
</div>
`Déclaratif/Préfère :
html`
<div>${when(isLoading, html`<p>Loading...</p>`, html`<p>Loaded!</p>`)}</div>
`Vous pouvez utiliser ternaire directement si vous avez l'intention d'effectuer un rendu une fois et/ou ne vous attendez pas à la mise à jour des données :
html` <div>${isLoading ? html`<p>Loading...</p>` : html`<p>Loaded!</p>`}</div> `2. Listes de rendu
Évitez d'utiliser .map() dans des modèles pour générer des nœuds de liste dynamiques. L'utilisation de .map() détruit et reconstruit les nœuds à chaque mise à jour, tandis que repeat() utilise la mémorisation chirurgicale sous le capot.
Impératif/Éviter :
html`
<ul>
${() => items().map((item) => html`<li>${item.name}</li>`)}
</ul>
`Déclaratif/Préfère :
html`
<ul>
${repeat(items, (item) => html`<li>${item.name}</li>`)}
</ul>
`Vous pouvez utiliser le map directement si vous avez l'intention d'effectuer un rendu une fois et/ou n'attendez pas de mises à jour :
html`
<ul>
${list.map((item) => html`<li>${item.name}</li>`)}
</ul>
`3. Lectures facultatives imbriquées
Évitez d'utiliser le chaînage facultatif imbriqué (?.) directement dans les interpolations de l'interface utilisateur. Cela peut entraîner des erreurs d'exécution si des parties de la chaîne deviennent indéfinies ou non résolues.
Impératif/Éviter :
html`
<div>
<h2>${() => user()?.profile?.details?.name || 'Guest'}</h2>
</div>
`Déclaratif/Préfère :
html`
<div>
<h2>
${pick(user, 'profile.details.name', (name) => name || 'Guest')}
</h2>
</div>
`L'option de sélection vous permet de définir des solutions de secours ou de gérer la valeur pour le formatage et/ou un traitement supplémentaire.
const over18 = (age) => (age > 18 ? 'Over 18' : 'Under 18')
html`
<div>
<h2>${pick(user, 'profile.details.age', over18)}</h2>
</div>
`4. Composition d'expression booléenne
Évitez d'écrire des fonctions personnalisées qui combinent simplement plusieurs états avec && ou ||. Composez-les à l'aide des combinateurs booléens Markup.
Impératif/Éviter :
const canPublish = () => !isSaving() && hasChanges() && hasPermission()
html` <button disabled="${() => !canPublish()}">Publish</button> `Déclaratif/Préfère :
const canPublish = and(isNot(isSaving), is(hasChanges), is(hasPermission))
html` <button disabled="${isNot(canPublish)}">Publish</button> `Markup invite à la composition de fonctions et à l'utilisation de fonctions avec état. Vous devriez examiner davantage comment créer des assistants personnalisés pour mieux comprendre.
Modèles canoniques
Liste de recherche et de filtrage avec état
Il s'agit du modèle standard pour le rendu des collections avec filtrage dynamique. L'état source (items) reste totalement immuable.
import { html, state, when, repeat, is, pick } from '@beforesemicolon/markup'
const [query, setQuery] = state('')
const [items] = state<Project[]>([])
// Derive filtered list reactively
const filtered = () =>
items().filter((p) => p.name.toLowerCase().includes(query().toLowerCase()))
const handleInput = (event: Event) => {
setQuery((event.target as HTMLInputElement).value)
}
const View = html`
<input value="${query}" oninput="${handleInput}" />
<ul>
${repeat(
filtered,
(item) => html`<li>${item.name}</li>`,
() => html`<p>No results found.</p>`
)}
</ul>
`Cela permet à l'état de rester immuable et vous permet de créer des états dérivés que vous utilisez pour le rendu qui combine différents états pour obtenir celui souhaité.
Machines à sous asynchrones (Suspense)
Utilisez suspense pour restituer proprement l'interface utilisateur asynchrone avec les gestionnaires de rendu d'erreur et de secours (pendant le chargement) :
import { html, suspense } from '@beforesemicolon/markup'
const resource = async () => {
const res = await fetch('/api/data')
const data = await res.json()
return html`<p>Resolved: ${data.message}</p>`
}
const ResourceView = html`
${suspense(
resource,
html`<p>Loading resource...</p>`,
(err) => html`<p class="error">Error: ${err.message}</p>`
)}
`Modèles plus courants
Voici des recettes plus typiques que vous pouvez copier-coller pour les exigences courantes de l'interface utilisateur :
Chèques d’adhésion et échange d’options
import { html, state, when, oneOf } from '@beforesemicolon/markup'
const [mode, setMode] = state<'view' | 'edit' | 'preview'>('view')
const View = html`
${when(
oneOf(mode, ['edit', 'preview']),
html`<button onclick="${() => setMode('view')}">Done</button>`,
html`<button onclick="${() => setMode('edit')}">Edit</button>`
)}
`Variables et styles réactifs CSS
import { html, state } from '@beforesemicolon/markup'
const [gap] = state(12)
// Reactive style bindings cleared and updated dynamically
const Box = html`
<div style="--gap: ${() => `${gap()}px`}; margin: ${gap}px">
Spacing Gap: ${gap}px
</div>
`Rendu de valeurs imbriquées
import { html, state, pick } from '@beforesemicolon/markup'
const [currentEntity] = state({ details: { author: { name: 'Ada Lovelace' } } })
// Safe nested navigation via pick
const AuthorHeader = html`
<h1>Written by: ${pick(currentEntity, 'details.author.name')}</h1>
`Magasin d'État partagé
import { state } from '@beforesemicolon/markup'
export const [todos, setTodos] = state<Todo[]>([])
export const [loadingState, setLoadingState] = state<
'idle' | 'loading' | 'error'
>('idle')
export const fetchTodos = async () => {
setLoadingState('loading')
try {
const response = await fetch('/api/todos')
const list = await response.json()
setTodos(list)
setLoadingState('idle')
} catch {
setLoadingState('error')
}
}Conventions et garde-corps
- Passez directement les getters/fonctions : n'exécutez pas de getters dans les attributs du modèle lorsque l'abonnement/la réactivité est prévu. Passez
disabled="${isDisabled}", pasdisabled="${isDisabled()}". - Nettoyer les liaisons d'événements : n'encapsulez pas les rappels dans des fermetures redondantes à moins de transmettre des arguments :
- Bon :
<button onclick="${logout}">Logout</button> - Bon :
<button onclick="${() => handleSelect(item)}">Select</button> - Éviter :
<button onclick="${() => logout()}">Logout</button>
- Bon :
- Liaisons de propriétés directes : ne pré-normalisez pas les attributs de modèle simples dans la configuration/les getters juste pour les restituer. Markup gère correctement les valeurs d'attribut
undefined, vides et fausses. - Attributs booléens : le noyau Markup déballe et évalue automatiquement les états booléens. N'ajoutez pas de wrappers de style
Boolean(val(this.props.someBoolean())); lier directement les propriétés. - Statique ou réactif : si une valeur est statique (ne change jamais après l'initialisation), affichez son état évalué :
html${state()}
. If it is reactive and should dynamically update, bind the getter:html<p>${state}</p>.