-
Notifications
You must be signed in to change notification settings - Fork 1
DeclarativeSurface pt BR
🌐 Esta página em: English · Português
Uma tela é C# puro em expressões: sem linguagem de marcação, sem cerimônia de builder, e sem new:
public override VisualNode Build(ComponentContext context) =>
Column(gap: Space.S4, children: [
Text($"Contagem: {_count}", TypeRole.Display, context.Theme.TextPrimary),
Row(gap: Space.S3, children: [
Button("Somar", onPressed: () => SetState(() => _count++)),
Button("Zerar", Variant.Outline, onPressed: () => SetState(() => _count = 0)),
]),
]);Todo nome ali é um método fábrica chamado exatamente como o tipo que ele retorna. Não há import para escrever: o SDK coloca a superfície do framework em escopo em cada arquivo do seu projeto, e os seus próprios componentes entram nela automaticamente (veja abaixo).
Como os estilos são valores tipados em vez de strings CSS, o compilador confere a interface inteira, layout e estilo incluídos, e a mesma classe renderiza no web e no nativo pelo Photon.
- Uma fábrica é chamada exatamente como o tipo dela.
Column,Text,Button. - Ela espelha um construtor parâmetro por parâmetro (mesmos nomes, mesma ordem, mesmos padrões),
então argumentos nomeados atravessam de
new X(…)paraX(…)sem mudança. - Nós contêineres recebem um parâmetro
childrenao final, escrito como expressão de coleção. - Não há sobrecargas. A superfície transpila para um gêmeo JavaScript, e métodos JS não podem sobrecarregar, então cada tipo tem UMA fábrica canônica.
Desde 0.2.0-preview.13
Row(gap: Space.S3, main: MainAlign.SpaceBetween, cross: CrossAlign.Start, children: [ … ])
Column(gap: Space.S2, wrap: true, runGap: Space.S4, children: [ … ])
Text("42", TypeRole.Display, align: TextAlignment.Center, tabular: true)Row e Column recebem main, cross, wrap, runGap e padding; Text recebe align,
mono, tabular e styleOverride. Essas eram propriedades init-only, então definir uma exigia um
inicializador de objeto, que exige new, e new é exatamente o que essa superfície remove. Uma
linha que precisava centralizar o conteúdo caía fora da superfície inteira e tinha que ser escrita
do jeito antigo.
Largura, altura, fundo e raio de canto deliberadamente não são parâmetros de um flex: um flex
carregando isso é um Box embrulhando um flex, e as propriedades já dizem isso.
O styleOverride do Text é a saída de uma escala de tipo fechada. Os degraus são o padrão certo, e
um design que passa por cima deles em todo lugar deixou de ter escala, mas uma escala fechada sem
saída acaba contornada aninhando um HtmlElement cru, o que é pior, porque só funciona num alvo.
Quebra na 0.2.0-preview.13.
childrenvem ao final (o contrato de contêiner), então os botões ficam entre ele e ogap:Column(Space.S3, [ … ])viraColumn(Space.S3, children: [ … ]). Uma palavra, e já é a forma normal da superfície.
Propriedades init mais raras mantêm a forma construtor + inicializador; as fábricas são açúcar
sobre os mesmos tipos, nunca uma segunda API:
Box(new BoxStyle { Padding = EdgeInsets.All(Space.S4), Background = theme.Surface },
Text("Continua o mesmo Box", TypeRole.BodyM))Records de valor (GridTrack, DialogAction, NavItem) deliberadamente não têm fábricas: eles
são dado, e o new(…) com tipo alvo já lê bem.
Defina um componente; o build gera a fábrica dele. Nada para registrar, nada para importar:
// Components/StatTile.cs
public sealed class StatTile : StatelessComponent
{
public StatTile(string label, string value) { Label = label; Value = value; }
public string Label { get; init; }
public string Value { get; init; }
public override VisualNode Build(ComponentContext context) => /* … */;
}// Pages/HomePage.cs, sem using, sem new
Row(gap: Space.S3, children: [
StatTile("Contagem", $"{_count}"),
StatTile("Dobro", $"{_count * 2}"),
])Um gerador de código lê a compilação, encontra os componentes, e escreve uma classe estática AppUI
mais o global using static que a põe em escopo. Ele fica em passo com os seus componentes porque é
gerado a partir deles.
Páginas não ganham fábrica. Uma [Page] é alcançada pela rota dela, nunca composta na mão.
O mais largo, a mesma regra que o transpilador aplica quando colapsa sobrecargas de construtor, para que a fábrica e o construtor emitido nunca discordem. Quando o mais largo não é o que você quer oferecer, eleja outro:
public sealed class Badge : StatelessComponent
{
[UiFactory]
public Badge(string label) { … } // ← este ganha a fábrica
public Badge(string label, int count, bool dot) { … }
}| Código | Severidade | Significado |
|---|---|---|
EQ3101 |
Erro | Dois construtores de um componente estão marcados com [UiFactory]. Uma eleição precisa de um único vencedor. |
EQ3102 |
Aviso | Dois componentes compartilham um nome, então só um pode ser dono daquela fábrica. Renomeie um, ou construa o outro com new. |
<PropertyGroup>
<EQuanticGenerateFactorySurface>false</EQuanticGenerateFactorySurface>
</PropertyGroup>Os seus componentes passam a compor com new, exatamente como antes.
Um método chamado como um tipo sombreia aquele tipo em qualquer arquivo onde a superfície esteja
em escopo. Isso só morde tipos que chegam por um using, ou seja, os do framework, não os seus:
Spacer.Fixed(34) // ✗ CS0119: `Spacer` liga ao método fábrica
Panel.Empty("x") // ✓ seu próprio tipo, declarado no seu namespace, venceO C# resolve nomes declarados no namespace atual antes dos imports using static, então os seus
componentes nunca são sombreados pelas próprias fábricas, e você nunca precisa do contorno abaixo.
Os statics do próprio framework que ficam atrás de um nome de fábrica ganham uma fábrica com nome próprio:
| Em vez de | Escreva |
|---|---|
Spacer.Fixed(34) |
Gap(34) |
Badge.AsDot(variant) |
DotBadge(variant) |
new Icon(packGlyph) |
Glyph(packGlyph) |
Desde 0.2.0-preview.7
(a linha do Glyph; as outras duas desde 0.2.0-preview.2)
Um teste de conformidade percorre cada fábrica, procura statics no tipo que ela sombreia, e falha nomeando-os até que cada um tenha uma fábrica com nome próprio, para que uma quarta não possa aparecer despercebida.
O Glyph está lá por uma razão um pouco diferente das outras duas, e vale saber qual: o Icon tem
dois construtores, um recebendo o enum Icons curado do framework, outro recebendo o IconGlyph
que um pacote de ícones entrega. O Icon(...) espelhado só pode ser um deles, e não há sobrecargas
aqui, então um glifo de pacote não tinha caminho nenhum para dentro de um arquivo que importa essa
superfície: só podia ser desenhado com new, no único lugar onde o framework promete que você nunca
precisa dele.
using eQuantic.UI.MaterialSymbols;
Glyph(MaterialSymbolsIcons.PlayArrowRounded) // o catálogo de qualquer pacote de ícones
Icon(Icons.Check) // o conjunto curadoA superfície gerada é escrita em disco (EmitCompilerGeneratedFiles) porque o eqc lê arquivos, não
a compilação C#: uma fábrica que a sua página chama tem que fazer parte do que o transpilador
enxerga, ou a chamada resolve para nada e o JavaScript emitido degrada em silêncio. Depois ela é
transpilada num módulo como qualquer outra classe, então AppUI.statTile(…) existe no bundle ao lado
de UI.column(…).
Se você renomear ou remover um gerador, rode
dotnet clean. Arquivos gerados pelo compilador não são removidos quando o gerador que os escreveu some, e o eqc continuaria lendo o resto: um tipo fantasma, ou uma segunda cópia de um vivo.
- Componentes write-once: a arquitetura sobre a qual a superfície repousa
- Componentes: o catálogo a que cada fábrica corresponde
- Design System: os tokens tipados de que os argumentos são feitos
🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento