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ãoWebView / H5Mini-Programa
Modelo de threadsThread principal único (JS+layout+pintura+input)Duplo: motor lógico + render WebView
Impacto de tarefa longaBloqueia pintura e input (toque congelado)Cálculo isolado; render segue pintando
TBT móvel (mediana)1.916 ms; 7.555 ms no p90Thread de render fora da rota JS crítica
DOM na camada lógica?Sim (pode bloquear direto)Não, só via setData
Custo a vigiarJS sem limiteTamanho/frequência do setData
Melhor paraConteúdo web abertoUI 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.

Fontes