- Arquitetura do Núcleo
1.1 Visão Geral da Arquitetura
// Definição da interface principal do mecanismo de renderização
interface MotorDeRenderizacao {
schema: SchemaPagina; // Schema da página
componentes: MapaDeComponentes; // Mapa de componentes
fonteDados: FonteDados; // Fonte de dados
eventos: SistemaEventos; // Sistema de eventos
renderizar(): VNode; // Método de renderização
}
// Estrutura do Schema da Página
interface SchemaPagina {
id: string;
nome: string;
componentes: SchemaComponente[];
fonteDados: ConfigFonteDados;
eventos: ConfigEventos;
estilo: EstiloPagina;
}
// Schema do Componente
interface SchemaComponente {
id: string;
tipo: string; // Tipo do componente
nomeComponente: string; // Nome do componente
props: Record<string, any>; // Propriedades do componente
eventos?: EventoComponente[]; // Eventos do componente
filhos?: SchemaComponente[]; // Componentes filhos
estilo?: PropriedadesCSS; // Estilo
vinculacaoDados?: VinculacaoDados; // Vinculação de dados
}
- Design do Schema
2.1 Definição do Schema da Página
{
"pagina": {
"id": "pag_001",
"nome": "Página de Gestão de Usuários",
"layout": "flex",
"estilo": {
"espacamento": "20px",
"corFundo": "#f5f5f5"
},
"fonteDados": {
"listaUsuarios": {
"tipo": "api",
"url": "/api/usuarios",
"metodo": "GET",
"autoCarregar": true
}
},
"componentes": [
{
"id": "form_busca",
"tipo": "container",
"nomeComponente": "ElForm",
"props": {
"inline": true,
"modelo": "{{formularioBusca}}"
},
"filhos": [
{
"id": "input_busca",
"tipo": "input",
"nomeComponente": "ElInput",
"props": {
"textoPlaceholder": "Digite o nome do usuário",
},
"eventos": [
{
"nome": "change",
"acao": "BUSCAR_USUARIO"
}
]
}
]
}
]
}
}
2.2 Extensão do Schema do Componente
// Definição detalhada do Schema do Componente
interface SchemaComponente {
id: string;
tipo: TipoComponente;
nomeComponente: string;
props: {
// Propriedades estáticas
[chave: string]: any;
// Vinculação dinâmica de propriedades
valorModelo?: string; // {{dados.campo}}
// Propriedades de estilo
classe?: string;
estilo?: PropriedadesCSS;
};
eventos?: Array<{
nome: string; // Nome do evento
acao: string; // Tipo da ação
cargaUtil?: any; // Parâmetros da ação
manipulador?: string; // Função de manipulação personalizada
}>;
filhos?: SchemaComponente[];
// Configuração de vinculação de dados
vinculacaoDados?: {
tipo: 'api' | 'local' | 'computado';
origem: string;
campo?: string;
transformar?: string; // Função de transformação de dados
};
// Renderização condicional
condicoes?: Condicao[];
// Renderização em loop
loop?: {
fonteDados: string;
nomeItem: string;
nomeIndice?: string;
};
// Configuração de slots
slots?: {
[nomeSlot: string]: SchemaComponente[];
};
}
// Configuração de renderização condicional
interface Condicao {
expressao: string; // Expressão JS
operador: '==' | '!=' | '>' | '<' | 'includes' | string;
valor: any;
}
- Implementação do Mecanismo de Renderização
3.1 Renderizador Principal
<template>
<div class="renderizador-baixo-codigo">
<template v-for="componente in componentesNormalizados" :key="componente.id">
<renderizador-componente
:schema="componente"
:dados="dadosPagina"
@acao="tratarAcao"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, calculado, aoMontar } from 'vue';
import RenderizadorComponente from './RenderizadorComponente.vue';
// Props
interface Props {
schema: SchemaPagina;
componentes: MapaDeComponentes;
}
const props = definirProps<Props>();
// Dados da página
const dadosPagina = ref<Record<string, any>>({});
// Componentes normalizados
const componentesNormalizados = calculado(() => {
return processarComponentes(props.schema.componentes, dadosPagina.value);
});
// Processar componentes (tratar loop, condições, etc.)
function processarComponentes(componentes: SchemaComponente[], dados: any) {
return componentes.flatMap(componente => {
// Verificação de renderização condicional
if (componente.condicoes && !verificarCondicoes(componente.condicoes, dados)) {
return [];
}
// Processamento de renderização em loop
if (componente.loop) {
const lista = obterValorPorCaminho(dados, componente.loop.fonteDados) || [];
return lista.map((item: any, indice: number) => ({
...componente,
id: `${componente.id}_${indice}`,
props: processarProps(componente.props, {
...dados,
[componente.loop.nomeItem]: item,
[componente.loop.nomeIndice || 'indice']: indice
})
}));
}
// Componente comum
return [{
...componente,
props: processarProps(componente.props, dados)
}];
});
}
// Processar propriedades com vinculação dinâmica
function processarProps(props: Record<string, any>, dados: any) {
const processado: Record<string, any> = {};
Object.keys(props).forEach(chave => {
const valor = props[chave];
if (typeof valor === 'string' && valor.startsWith('{{') && valor.endsWith('}}')) {
// Processar vinculação dinâmica {{dados.campo}}
const expressao = valor.slice(2, -2).trim();
processado[chave] = obterValorPorCaminho(dados, expressao);
} else {
processado[chave] = valor;
}
});
return processado;
}
// Tratamento de ações
function tratarAcao(acao: string, cargaUtil: any) {
switch (acao) {
case 'BUSCAR_USUARIO':
tratarBusca(cargaUtil);
break;
case 'ENVIAR_FORMULARIO':
tratarEnvio(cargaUtil);
break;
// Outras ações...
}
}
// Inicializar dados
aoMontar(async () => {
await carregarFontesDados();
});
// Carregar fontes de dados
async function carregarFontesDados() {
if (props.schema.fonteDados) {
for (const [chave, config] of Object.entries(props.schema.fonteDados)) {
if (config.autoCarregar) {
dadosPagina.value[chave] = await buscarFonteDados(config);
}
}
}
}
</script>
3.2 Renderizador de Componente
<template>
<componente
:is="resolverComponente(schema.nomeComponente)"
v-bind="propsFundidos"
v-on="manipuladoresEventos"
>
<!-- Renderizar componentes filhos -->
<template v-if="schema.filhos && schema.filhos.length">
<renderizador-componente
v-for="filho in filhosNormalizados"
:key="filho.id"
:schema="filho"
:dados="dados"
@acao="$emit('acao', $event)"
/>
</template>
<!-- Renderizar slots -->
<template v-for="(componentesSlot, nomeSlot) in schema.slots" :key="nomeSlot">
<template #[nomeSlot]>
<renderizador-componente
v-for="componente in componentesSlot"
:key="componente.id"
:schema="componente"
:dados="dados"
@acao="$emit('acao', $event)"
/>
</template>
</template>
</componente>
</template>
<script setup lang="ts">
import { calculado } from 'vue';
interface Props {
schema: SchemaComponente;
dados: Record<string, any>;
}
const props = definirProps<Props>();
const emitir = definirEmit<{
acao: [acao: string, cargaUtil: any];
}>();
// Mapeamento de componentes
const mapaComponentes = {
// Componentes de layout
'ElRow': () => import('element-plus').then(mod => mod.ElRow),
'ElCol': () => import('element-plus').then(mod => mod.ElCol),
'ElForm': () => import('element-plus').then(mod => mod.ElForm),
// Componentes de formulário
'ElInput': () => import('element-plus').then(mod => mod.ElInput),
'ElButton': () => import('element-plus').then(mod => mod.ElButton),
'ElTable': () => import('element-plus').then(mod => mod.ElTable),
// Componentes de negócio personalizados
'AvatarUsuario': () => import('@/componentes/AvatarUsuario.vue'),
// ...
};
// Resolver componente
async function resolverComponente(nome: string) {
const resolvedor = mapaComponentes[nome];
if (!resolvedor) {
console.warn(`Componente ${nome} não encontrado`);
return null;
}
return await resolvedor();
}
// Propriedades processadas
const propsFundidos = calculado(() => {
const propsBase = { ...props.schema.props };
// Processar estilo
if (props.schema.estilo) {
propsBase.estilo = props.schema.estilo;
}
return propsBase;
});
// Manipuladores de eventos
const manipuladoresEventos = calculado(() => {
const manipuladores: Record<string, Funcao> = {};
props.schema.eventos?.forEach(evento => {
manipuladores[evento.nome] = (...args: any[]) => {
emitir('acao', evento.acao, {
...evento.cargaUtil,
$evento: args[0],
$dados: props.dados
});
};
});
return manipuladores;
});
// Processar componentes filhos
const filhosNormalizados = calculado(() => {
return processarComponentes(props.schema.filhos || [], props.dados);
});
</script>
- Gerenciamento de Fontes de Dados
4.1 Gerenciador de Fontes de Dados
class GerenciadorFontesDados {
private dados: Map<string, any> = new Map();
private assinantes: Map<string, Funcao[]> = new Map();
// Registrar fonte de dados
async registrarFonteDados(config: ConfigFonteDados) {
const { chave, tipo, opcoes } = config;
switch (tipo) {
case 'api':
return await this.configurarFonteApi(chave, opcoes);
case 'local':
return this.configurarFonteLocal(chave, opcoes);
case 'computado':
return this.configurarFonteComputada(chave, opcoes);
}
}
// Fonte de dados API
private async configurarFonteApi(chave: string, opcoes: OpcoesFonteApi) {
const { url, metodo = 'GET', parametros, autoCarregar = true } = opcoes;
if (autoCarregar) {
try {
const resposta = await this.buscarDados(url, metodo, parametros);
this.definirDados(chave, resposta);
} catch (erro) {
console.error(`Falha ao carregar fonte de dados ${chave}:`, erro);
}
}
// Retornar função de obtenção de dados
return {
recarregar: () => this.buscarDados(url, metodo, parametros).then(dados => {
this.definirDados(chave, dados);
return dados;
})
};
}
// Definir dados
definirDados(chave: string, valor: any) {
this.dados.set(chave, valor);
this.notificarAssinantes(chave, valor);
}
// Obter dados
obterDados(chave: string) {
return this.dados.get(chave);
}
// Assinar alterações de dados
assinar(chave: string, callback: Funcao) {
if (!this.assinantes.has(chave)) {
this.assinantes.set(chave, []);
}
this.assinantes.get(chave)!.push(callback);
// Retornar função para cancelar assinatura
return () => {
const callbacks = this.assinantes.get(chave) || [];
const indice = callbacks.indexOf(callback);
if (indice > -1) {
callbacks.splice(indice, 1);
}
};
}
private notificarAssinantes(chave: string, valor: any) {
const callbacks = this.assinantes.get(chave) || [];
callbacks.forEach(callback => callback(valor));
}
}
- Sistema de Eventos
5.1 Processador de Ações
class ProcessadorAcoes {
private gerenciadorDados: GerenciadorFontesDados;
private roteador: Roteador;
constructor(deps: { gerenciadorDados: GerenciadorFontesDados; roteador: Roteador }) {
this.gerenciadorDados = deps.gerenciadorDados;
this.roteador = deps.roteador;
}
async processar(acao: string, cargaUtil: any) {
const [tipoAcao, ...resto] = acao.split('.');
switch (tipoAcao) {
case 'NAVEGAR':
return this.processarNavegacao(cargaUtil);
case 'API':
return this.processarChamadaApi(cargaUtil);
case 'DADOS':
return this.processarOperacaoDados(cargaUtil);
case 'MODAL':
return this.processarModal(cargaUtil);
default:
console.warn(`Ação desconhecida: ${acao}`);
}
}
// Navegação de rotas
private processarNavegacao(cargaUtil: any) {
const { caminho, consulta, parametros } = cargaUtil;
this.roteador.push({ path: caminho, query: consulta, params: parametros });
}
// Chamada à API
private async processarChamadaApi(cargaUtil: any) {
const { chave, url, metodo, dados, aoSucesso, aoErro } = cargaUtil;
try {
const resposta = await fetch(url, {
method: metodo,
headers: { 'Content-Type': 'application/json' },
body: dados ? JSON.stringify(dados) : undefined
});
const resultado = await resposta.json();
if (aoSucesso) {
await this.processar(aoSucesso, { $resposta: resultado });
}
return resultado;
} catch (erro) {
if (aoErro) {
await this.processar(aoErro, { $erro: erro });
}
throw erro;
}
}
// Operações com dados
private processarOperacaoDados(cargaUtil: any) {
const { operacao, chave, valor } = cargaUtil;
switch (operacao) {
case 'DEFINIR':
this.gerenciadorDados.definirDados(chave, valor);
break;
case 'ATUALIZAR':
const atual = this.gerenciadorDados.obterDados(chave);
this.gerenciadorDados.definirDados(chave, { ...atual, ...valor });
break;
case 'EXCLUIR':
this.gerenciadorDados.definirDados(chave, undefined);
break;
}
}
}
- Registro Dinâmico de Componentes
6.1 Gerenciador de Biblioteca de Componentes
class BibliotecaComponentes {
private componentes: Map<string, DefinicaoComponente> = new Map();
private metadados: Map<string, MetadadosComponente> = new Map();
// Registrar componente
registrarComponente(nome: string, definicao: DefinicaoComponente) {
this.componentes.set(nome, definicao);
}
// Registrar metadados do componente
registrarMetadados(nome: string, metadados: MetadadosComponente) {
this.metadados.set(nome, metadados);
}
// Obter componente
obterComponente(nome: string): DefinicaoComponente | undefined {
return this.componentes.get(nome);
}
// Obter metadados do componente
obterMetadados(nome: string): MetadadosComponente | undefined {
return this.metadados.get(nome);
}
// Obter todos os componentes
obterTodosComponentes(): MetadadosComponente[] {
return Array.from(this.metadados.values());
}
}
// Definição de metadados do componente
interface MetadadosComponente {
nome: string;
titulo: string;
categoria: string; // Categoria: formulário, layout, negócio, etc.
props: MetadadosProp[];
eventos?: MetadadosEvento[];
slots?: MetadadosSlot[];
}
interface MetadadosProp {
nome: string;
tipo: 'string' | 'number' | 'boolean' | 'array' | 'object';
padrao?: any;
obrigatorio?: boolean;
rotulo: string;
componente?: string; // Componente para edição da propriedade
opcoes?: any[]; // Valores possíveis
}
// Inicializar biblioteca de componentes
export const bibliotecaComponentes = new BibliotecaComponentes();
// Registrar componentes Element Plus
bibliotecaComponentes.registrarMetadados('ElInput', {
nome: 'ElInput',
titulo: 'Campo de Entrada',
categoria: 'formulário',
props: [
{
nome: 'valorModelo',
tipo: 'string',
rotulo: 'Valor',
obrigatorio: true
},
{
nome: 'textoPlaceholder',
tipo: 'string',
rotulo: 'Texto Placeholder'
},
{
nome: 'desabilitado',
tipo: 'boolean',
rotulo: 'Desabilitado',
padrao: false
}
],
eventos: [
{
nome: 'change',
rotulo: 'Valor Alterado'
},
{
nome: 'input',
rotulo: 'Entrada'
}
]
});
- Otimização de Renderização
7.1 Cache e Otimização de Componentes
// Gerenciador de cache de componentes
class CacheComponentes {
private cache = new Map<string, any>();
private componentesEstaticos = new Set<string>();
// Pré-compilar componentes estáticos
preCompilar(schema: SchemaPagina) {
schema.componentes.forEach(componente => {
if (this.isComponenteEstatico(componente)) {
this.compilarComponente(componente);
}
});
}
// Verificar se é componente estático
private isComponenteEstatico(componente: SchemaComponente): boolean {
return !componente.vinculacaoDados &&
!componente.condicoes &&
!componente.loop &&
!temPropsDinamicas(componente.props);
}
// Compilar componente
private compilarComponente(componente: SchemaComponente) {
const chave = this.gerarChave(componente);
// Aqui pode-se pré-compilar para função de renderização
this.cache.set(chave, this.criarFuncaoRenderizacao(componente));
}
// Obter componente do cache
obter(chave: string) {
return this.cache.get(chave);
}
}
// Otimização de lista virtual (para listas longas)
const RenderizadorListaVirtual = definirComponente({
props: {
componentes: Array as PropType<SchemaComponente[]>,
dados: Object
},
configuracao(props) {
const refContainer = ref<HTMLElement>();
const alcanceVisivel = ref({ inicio: 0, fim: 50 });
// Monitorar rolagem, calcular área visível
const tratarRolagem = usarThrottleFn(() => {
if (!refContainer.value) return;
const posicaoRolagem = refContainer.value.scrollTop;
const alturaCliente = refContainer.value.clientHeight;
const inicio = Math.floor(posicaoRolagem / 50);
const fim = inicio + Math.ceil(alturaCliente / 50) + 5; // Buffer de 5 itens
alcanceVisivel.value = {
inicio: Math.max(0, inicio),
fim: Math.min(props.componentes.length, fim)
};
}, 16);
const componentesVisiveis = calculado(() => {
const { inicio, fim } = alcanceVisivel.value;
return props.componentes.slice(inicio, fim);
});
return {
refContainer,
componentesVisiveis,
tratarRolagem
};
},
template: `
<div ref="refContainer" class="container-virtual" @scroll="tratarRolagem">
<div class="conteudo-virtual" :estilo="{ height: alturaTotal + 'px' }">
<div
v-for="componente in componentesVisiveis"
:key="componente.id"
class="item-virtual"
:estilo="{ transform: \`translateY(${componente.indice * 50}px)\` }"
>
<renderizador-componente
:schema="componente"
:dados="dados"
/>
</div>
</div>
</div>
`
});
- Integração com o Designer
8.1 Comunicação entre Designer e Renderizador
// Contexto do designer
interface ContextoDesigner {
componenteSelecionado: string | null;
componenteSobCursor: string | null;
isModoDesign: boolean;
atualizarComponente: (id: string, atualizacoes: Partial<SchemaComponente>) => void;
excluirComponente: (id: string) => void;
adicionarComponente: (idPai: string, componente: SchemaComponente) => void;
}
// Fornecer contexto do designer
export const usarDesigner = () => {
return injetar<ContextoDesigner>('designer');
};
// Wrapper de componente editável
const WrapperEditavel = definirComponente({
props: {
schema: Object as PropType<SchemaComponente>,
dados: Object
},
configuracao(props, { slots }) {
const designer = usarDesigner();
const estaSelecionado = calculado(() =>
designer?.componenteSelecionado === props.schema.id
);
const tratarClique = (e: MouseEvent) => {
e.stopPropagation();
designer?.selecionarComponente(props.schema.id);
};
return () => h('div', {
classe: [
'componente-editavel',
{ 'selecionado': estaSelecionado.value }
],
onClick: tratarClique
}, slots.padrao?.());
}
});
Esta arquitetura fornece uma estrutura de renderização de baixo código altamente extensível, suportando cenários de negócios complexos, mantendo ao mesmo tempo um bom desempenho e experiência de desenvolvimento.