O thread principal é uma só faixa
Tocar um botão de compra e nada acontecer por um segundo não é um bug isolado: é o teto do modelo. Em um navegador ou WebView, o motor de JS e a renderização compartilham um único thread principal: execução de scripts, layout, pintura e resposta a input competem pela mesma faixa. Assim que surge uma tarefa longa (cálculo pesado, parse de JSON grande, hidratação de framework, tags de terceiros), a pintura e o input bloqueiam. O web.dev define o TBT direto: uma tarefa acima de 50 ms é "tarefa longa" e bloqueia o thread principal; o Web Almanac 2025 mediu uma TBT móvel mediana de 1.916 ms e 7.555 ms no percentil 90. O mesmo script que roda liso em um laptop pode congelar um telefone de médio porte por mais de um segundo.
WebView: um thread faz tudo
No navegador e no WebView, o motor de JS e a renderização vivem no mesmo thread principal. Uma tarefa longa —cálculo pesado, parse de JSON gigante, hidratação de framework, tags de terceiros— bloqueia ao mesmo tempo a pintura e o input. O toque do usuário já disparou, mas o navegador precisa esperar a tarefa terminar para responder. Este é o teto estrutural do H5 no mobile: sua própria lógica e "fazer a tela se mover" brigam pela mesma faixa.
Mini-programa: lógica e render seguem separados
O modelo de dois threads do WeChat separa os dois: a camada lógica roda em um motor JS isolado (JavaScriptCore no iOS, V8/X5 no Android) sem DOM; a camada de render é um WebView que só pinta WXML/WXSS. Eles não compartilham memória: se comunicam só pela ponte Native via setData —os dados são serializados, diffados e aplicados à view. Assim, um cálculo pesado na camada lógica não congela a pintura da camada de render; como o trabalho desta última é leve, ela continua respondendo ao toque do usuário.
(Com honestidade: o setData tem seu custo —serialização + diff + transferência entre threads. Um setData grande ou muito frequente traz seu próprio jank. O ganho real é "o cálculo não bloqueia mais a pintura", mas é preciso manter o setData enxuto. O Skyline do WeChat é um motor de render mais novo que chega ainda mais perto do nativo.)
De onde vem a fluidez
- Separação de responsabilidades. Cálculo pesado e pintura isolados, sem esperas cruzadas.
- Thread de render leve. Só faz diff e pinta, não roda lógica de negócio.
- Mediação Native. A ponte batcha e encaminha; o render não espera seu loop.
- Previsível no mobile. O jank que persegue o H5 em médio porte é reduzido estruturalmente.
Lado a lado
| Dimensão | WebView / H5 | Mini-Programa |
|---|---|---|
| Modelo de threads | Thread principal único (JS+layout+pintura+input) | Duplo: motor lógico + render WebView |
| Impacto de tarefa longa | Bloqueia pintura e input (toque congelado) | Cálculo isolado; render segue pintando |
| TBT móvel (mediana) | 1.916 ms; 7.555 ms no p90 | Thread de render fora da rota JS crítica |
| DOM na camada lógica? | Sim (pode bloquear direto) | Não, só via setData |
| Custo a vigiar | JS sem limite | Tamanho/frequência do setData |
| Melhor para | Conteúdo web aberto | UI transacional dentro do host |
O que isso significa para o seu fluxo
Para uma UI de trading, um chat ou um formulário, a resposta é o produto. Mover o núcleo transacional para um mini-programa remove por estrutura o teto de "um thread faz tudo" que o H5 atinge em médio porte. O H5 é correto para a web aberta; o núcleo dentro do host não deveria compartilhar seu thread principal com sua própria lógica pesada.
Onde o Cross Mini App se encaixa
O Cross Mini App é o runtime e o catálogo aberto do modelo de dois threads. Você constrói uma vez e roda em qualquer host com lógica e render separados por uma ponte governada, assim o cálculo pesado nunca rouba o frame de um toque. O H5 continua sendo a porta; o mini-programa é a sala onde o botão sempre responde.