A ponte entre Python e ReactUma coruja é uma nano estrutura que pode ser integrada a outras estruturas.
Imagem de sova.online, na qual três servidores http estão em execução:
http://sova.online/ - apenas Falcon
http://sova.online:8000/ - apenas Django
http://sova.online:8001/ - apenas Python (login: 1, senha: 1)
Existem códigos fonte e instruções de instalação. Não há publicidade lá.

A idéia de criar sites em Python com renderização no React não é nova. Existe uma estrutura maravilhosa
https://plot.ly/products/dash/ , por que fazer outra coisa?
Explique: O Owl não foi desenvolvido para o desenvolvimento de sites. Esta é uma ferramenta para substituir clientes espessos por aplicativos em execução no navegador (aplicativos de desktop).
- O que é um cliente web?
- não. Este não é um cliente da web. Este é um aplicativo de navegador.
"Eu não entendo."
Infelizmente, muitos desenvolvedores não entendem.
Em geral, trabalhei ativamente com vários aplicativos da Internet.
Cliente online do banco Ugra (o banco está fechado).Era uma boa aplicação, mas era um applet Java, ou seja, cliente grosso lançado a partir do navegador. E o banco de Ugra, e applets no passado.
Cliente online do VTB-24 Bank (o banco está fechado).Eu sou humanista, mas depois de trabalhar com esse milagre, pensamentos cruéis começaram a aparecer como: "Forçar o desenvolvedor a registrar 1000 contracheques nele".
Além disso, como um cliente web, ele é bonito. A animação é aberta em um telefone celular. Uau! Legal!
Perguntei a um amigo contador: como você trabalha com ele?
Ela diz: ótimo! Carrego os dados em 1s, em 1s trabalho, carrego os resultados de volta.
Cliente online SberbankCliente satisfatório, você pode trabalhar. Quando me pediram para avaliá-lo, dei-lhe 3 pontos em 5 e dei-lhe uma lista de comentários. Isso é com meus 10 pagamentos por mês. Aqueles que faturam 100 contas por dia provavelmente enviarão informações.
Preenchendo um pagamento.

A área verde, que ocupa 20% da tela, é o menu. Não interfere apenas (posição: fixa), diz que o desenvolvedor não é um profissional. Se eu comecei a criar um pagamento, a tela deve ser. 3 botões: "Criar", "Salvar como modelo", "Cancelar". Esses botões são (por algum motivo abaixo). Este não é um SPA de várias páginas: se você clicar no item de menu, os dados no formulário serão perdidos.
Quem fez isso nem vai entender o que é o golpe: “Normalmente feito, todo mundo faz, como uma biblioteca, as pessoas trabalham ...”. E ele está certo. Você precisa perguntar ao gerente de projetos e, para o gerente, o principal é os bancos de dados e as camadas no modelo conceitual. E formas - vamos contratar meninos, eles vão desenhar. E eles provavelmente estão orgulhosos desse hack.
Pregões (5 peças, 44 leis federais)Estes são verdadeiramente aplicativos (não clientes da Web). Mas eu não gosto dos controladores de campo.
Exemplos:

É estranho alinhado, a largura do campo é claramente insuficiente, não há altura automática no campo de entrada.
Outro exemplo Não há modelo dd.mm.aaaa no campo "Data da publicação", o calendário está com erro, o ícone do calendário é assustador:

Lista no rts-tender: há a entrada atual destacada em cores, as setas podem se mover pela lista, mas não há rolagem automática (você pode fugir da borda da tela), nem Enter nem a barra de espaço abrem o link, a guia não está anexada ao registro atual. Embora você só possa abrir o link com o mouse, avalio o controle com um sinal de adição. Essa funcionalidade (lembre-se e destaque o documento atual) não é suficiente para mim no mail.ru
Parece pequenas coisas. Mas a aplicação profissional difere da semiprofissional em detalhes. O usuário final não dá a mínima para o banco de dados que você possui e quantas camadas no modelo conceitual. Funciona com formas de tela e possui 3 requisitos: funcional, conveniente, rápido.
Infelizmente, a escolha do sistema é determinada por especialistas e chefes de TI que não trabalharam com o sistema e não funcionarão. Apreciarão a velocidade, apreciarão a funcionalidade como a entendem e não se importam com a conveniência, mais importante, de serem bonitas.
Pavel Valeryevich Durov não inventou nem uma rede social nem um mensageiro. Ele fez o que os usuários precisavam, de forma conveniente e bonita. E as pessoas gostaram disso, inclusive financeiramente.
Owl é uma ferramenta para a construção de uma interface profissional.
O que isso significa, por exemplo, EDMS.
Existe um EDMS, possui 3 grupos de usuários:
Superiores
Especialistas em preparação de documentos
Funcionários de escritório.
Chefes, eles são como crianças. Eles precisam ser simples e bonitos. Idealmente 1 botão e 1 campo. E para mostrar um pouco mais. Um cliente da Web e, é claro, um cliente móvel para se exibir.
Especialistas. Cliente Web, rede social cruzada / mala direta. Não esqueça que existem muitos especialistas e eles precisam ser treinados. Quanto mais familiar o ambiente for para eles, melhor. O cliente móvel também será útil se o serviço de segurança permitir.
Funcionários de escritório. É aqui que a Coruja é útil. Os funcionários do escritório são um grupo de usuários formadores de sistema. Todo mundo pode ficar doente / sair de férias / parar de usá-lo - o EDMS funcionará. Se o registro parar, tudo estará funcionando.
A papelada é um transportador, e aqui tudo é importante, qualquer ninharia: fontes, meios-tons, preenchimento automático, verificação de valores, facilidade de entrada, etc.
EDS "Case". Os escritórios dos artistas são feitos no cliente web, o escritório é um cliente gordo. Está tudo bem, mas funcionará até o governo proibir o Windows de agências governamentais. Eu amo o Win 7, mas se eu fosse o governante, o mercado de TI era revigorado com novos pedidos, e a MS permanecia em uma memória brilhante. A propósito, em 6 de dezembro, Anton Siluanov assinou uma
diretiva sobre a transição para o software doméstico.
Sova.onlineComo uma coruja abre um formulário.
Sem várias páginas.
O elemento central da coruja é o componente do documento. Pressionando ctrl-U na página inicial, você verá tudo o que precisa para criar um objeto da classe Document:
- campos de dados do banco de dados;
- URL do formulário a ser exibido;
- dbAlias, unid - para trabalhar com o banco de dados;
- algo mais lá.
Até certo ponto, o Document é um análogo da forma Redux.
O formulário é carregado como uma string JSON e, em seguida, o dicionário anterior se torna um objeto que possui estilo, className e uma matriz (lista) de elementos. A matriz será inserida no elemento com id = root no formulário
<div style className>……</div>
Elementos de matriz são objetos que descrevem tags.
<div>, <a>, <img>, <button>
, ou uma matriz ou componentes.
A função de boxe é responsável por analisar a matriz. Se um elemento que contém uma matriz for encontrado, ele se chamará recursivamente.
O umbigo da terra é, obviamente, um div.
No caso mais simples, esta é uma linha: dict (div = 'Hello', className = 'h2')
Mas pode haver uma matriz (matriz de matrizes):
def style(**par): return {'style': {**par}} dict(
Existem 3 painéis (cada um em um arquivo separado: subFormTop.py, etc.).
subFormTop.panel () retorna uma matriz para criar o painel superior.
subFormLeft.panel () e subFormRight.panel () são combinados em uma sequência ('className': 'row') e descrevem os painéis esquerdo e direito.
subFormDown.panel () está comentado (não é útil).
Pode parecer complicado. Mas isso é Python: tudo pode ser simplificado.
Um exemplo de formulário da revista "Reports". A função labField (label, DB_field_name) retorna uma matriz de dois dicionários (linha da tabela): o primeiro dicionário é {'div': label}, o segundo {'field': [DB_field_name, 'tx']}.
div = [ docTitle(''), dict ( wl='40mm', className='cellbg-green', div=_table( labField('', 'nodafd'), labField(' ', '_STARTINGTIME'), labField('', '_ENDTIME'), labField('', 'CREATOR'), labField('', 'REPORTCAT'), labField('', 'REPORTNAME'), labField('', 'REPORTTITLE'), labField(' ', 'dt1'), labField(' ', 'dt2'), labField(' 2', 'dt3'), labField(' 2', 'dt4'), labField('', 'LBYEARS'), labField('', 'GRGROUP'), labField(' ', 'QUERYMAIN'), labField('', 'NOTES'), )), sent(), ]

Exemplos de sova / api / forms / home / top.py (começando em sova.online):
dicionário python
{'a': 'Reagir v16', 'href': 'https://reactjs.org'}
Gera um componente React claro
<a href={'https://reactjs.org'}>React v16</a>
Img é mais inteligente que o padrão - em adereços, você pode especificar href e target:
Python:
dict (img = 'imagem? react.ico', estilo = {'largura': 16}, href = 'https: //reactjs.org')
Um fragmento de um analisador que converte uma matriz de objetos em componentes (boxing.js):
if ( td.img ) {
Digite no mecanismo de pesquisa "reagir biblioteca de componentes". O resultado é previsível - muito. Mas toda essa abundância é para sites, não aplicativos:
Talvez a área de texto inteligente seja o único controle que me convém.
Seleção de reação - simplificou e refez a lista suspensa
Seletor de dados / calendário - não encontrou nada adequado. Ele escreveu o seu, tomando como exemplo o G.Chrome embutido.
Upload / Download - nada adequado, escreveu o meu.
Imho: os sites têm um futuro triste. A grande maioria dos usuários em um futuro próximo deixará de usar navegadores (ou já pararam). O telefone crescerá junto com o tablet, e 1 pitada de 10 aplicativos atenderá totalmente às necessidades.
Já encontrei programadores duas vezes que não sabem escrever um endereço de email corretamente. Por que eles deveriam se lembrar do que não usam? O mundo está mudando.
No Owl, os controladores não são perfeitos, mas foram projetados para o operador, não para o usuário da web.
Como exemplo, o formulário "Marca de Transferência". Uma forma bastante universal, usada onde há um chefe. Os campos que controlam a ocultação são circulados em vermelho na captura de tela. As resoluções adicionais são abertas automaticamente à medida que são preenchidas, se na parte operacional houver várias instruções para diferentes grupos de artistas com termos diferentes. Dois termos por grupo: 1º termo ao 1º intérprete, 2º termo aos co-executores.

Você pode tocar no formulário
AQUIUm controlador é um componente React associado a um campo do banco de dados.
Uma descrição detalhada dos controladores com a capacidade de verificar sua operação está em Sova.online.
Preste atenção aos tipos rtf e json. Rtf é exibido como texto, mas se houver uma construção {_ {object} _} no texto, o Owl executará json.parse para essa construção e adicionará o resultado ao formulário. Um campo do tipo json deve armazenar uma descrição da matriz de elementos de marcação: [{ele1}, {ele2}, ...]. json.parse é executado antes da renderização.
Os campos com esses tipos permitem armazenar marcações no banco de dados ou em arquivos. Útil para relatar e escrever documentação.
A lista de controladores para todos os tipos de campos (controllers.js):
export const controller = prop => { switch (prop.type) {
Um aplicativo requer um mecanismo para manipular controladores.
Em uma coruja, todos os controladores de documentos são armazenados em uma variável de documento
this.register
Não ousei usar árbitros por causa de rumores de que a equipe editorial o cancelaria.
O controlador pode ter as seguintes interfaces:
getValue (param)
setValue (valor, param)
setFocus ()
changeDropList ()
Para acessar o campo desejado, existem métodos de documento
getField (fieldName, param)
setField (fieldName, value, param)
changeDropList (fieldName, param)
setFocus (fieldName)
Para campos do tipo FileShow, existe um método fileShow ['FILES1 _']. HasAtt (), em que FILES1_ é o nome da área do arquivo. Retorna true se houver anexos. Na marca de transferência de tais áreas 2.
Os controladores podem gerar um evento recalc. Se um manipulador estiver registrado para este campo, ele será executado. Os manipuladores estão localizados em arquivos js carregáveis.
Um exemplo e uma descrição um pouco simplificada:
Há um formulário "Marca de transferência" (o.py). Ele contém o arquivo o.js carregado
No o.js, os manipuladores são registrados
recalc: { PROJECTO: doc => doc.forceUpdate(), WHOPRJ2: doc => doc.forceUpdate(), WHOPRJ3: doc => doc.forceUpdate(), … }
, e também as condições de ocultação são especificadas (projeto, op, prj1, prj2 ... prj5 é a propriedade "name" na descrição de divs):
hide: { project: doc => !doc.getField('projectO'),
Como funciona: o campo PROJECTO é uma caixa de seleção, quando o valor é alterado, o controlador gera um evento recalc, o documento chama o manipulador recalc.PROJECTO (this).
O manipulador simplesmente chama forceUpdate () para redesenhar o documento.
Ao redesenhar, é verificado se o componente nos adereços tem um nome, se existe uma função hide [props.name] para esse nome e se ele retornará verdadeiro.
prj3: doc =>! doc.getField ('projectO') || (! doc.getField ('whoPrj2') &&! doc.getField ('whoPrj3'))
Oculte a terceira resolução (área com props.name === 'prj3') se a caixa de seleção 'projectO' estiver DESLIGADA ou os executores não forem inseridos nos campos de resolução 2 e 3 (os campos 'whoPrj2' e 'whoPrj3' estão vazios).
O nome do campo ao chamar funções não diferencia maiúsculas de minúsculas.
WHOPRJ2 é uma caixa de combinação; quando você seleciona um valor, o controlador também gera um evento recalc, o que também causa um redesenho. Ao escolher um artista na segunda resolução, você abrirá a terceira.
Nos arquivos js carregados, você pode:
- gerenciar esconderijo;
- gerenciar apenas leitura;
- responder a alterações de campo;
- executar comandos de botão;
- faça validação de campos e formulários antes de salvar;
Faça o download do arquivo para o formulário 'fo':
window.sovaActions = window.sovaActions || {}; window.sovaActions.fo = {
Validação de campos - uma função que retorna vazia se tudo estiver OK ou uma mensagem sobre o que está errado. Uma coruja definirá o foco em um campo inválido.
Validação do formulário - Promessa. No exemplo, não há verificações (o sim sempre é chamado), apenas algo é feito antes do envio ao servidor.
Na redux forma, a validação é feita através do trow - algum tipo de selvageria.
Para aqueles que não estão familiarizados com as promessas, um exemplo das mais simples: const confirmDlg = msg => new Promise((ok, cancel) => confirm(msg) ? ok(' ') : cancel(' cancel')); confirmDlg(' ') .then( s => console.log(s)) .catch( s => console.log(s));
A classe Document possui vários comandos predefinidos que podem ser usados em botões:
editar: alternar para o modo de edição de formulário
salvar: salvar formulário
fechar: fechar o formulário
saveClose: salva e fecha o formulário
prn: imprime um formulário com a opção de um modelo para impressão
docOpen: documento aberto
dbOpen: log aberto
xopen: URL aberto
newDoc: crie um novo documento com o formulário desejado
A API de forma Redux é mais rica - na coruja apenas o necessário.
Várias páginas.A classe Document cria um objeto (formulário) incorporado em um elemento.
<div id="root"></div>
.
Vamos chamá-lo de "documento raiz". Se você adicionar um elemento ao documento raiz
<div style = {{position: 'absolute', zIndex: 999}} />, você também pode inserir outro objeto Document nele.
O que fazer com os manipuladores de comando carregáveis? É simples: cada formulário possui seu próprio manipulador (seu próprio js) e o documento raiz deve carregar aqueles que podem ser necessários.
Exemplo para a página inicial sova.online (home.py)
O formulário home.py, para demonstrar a natureza de várias páginas, abre documentos com os formulários "rkckg", "outlet", "outlet.gru", "o".
Para que todos os formulários funcionem corretamente, é necessário registrar scripts para esses formulários em home.py:
javaScriptUrl = ['jsv?api/forms/rkckg/rkckg.js', 'jsv?api/forms/outlet_gru/outlet_gru.js', 'jsv?api/forms/outlet/outlet.js', 'jsv?api/forms/o/o.js', 'jsv?api/forms/home/home.js']
Como ao chamar qualquer função do manipulador, o primeiro parâmetro passa o link para o documento, as ações serão executadas com o documento desejado.
OOP e sem milagres.
Reagir - não reagir
Eu já descrevi o formulário de relatório. Ele abre no gerenciador de relatórios (seta "Reagir") e descreve os parâmetros para coletar o relatório.

Os próprios relatórios (seta "não reagir") são armazenados em documentos subordinados com o formato "rreport" na forma de anexos html. Estávamos envolvidos no desenvolvimento de relatórios quando o React não estava lá, o formulário "rreport" acabou sendo simples (20 linhas de html e 15 linhas simplesmente-js), por que mudar o que funciona por 8 anos.
Abra o Gerenciador de relatórios.O formulário de relatório consiste em 4 botões e um iframe. A coruja antes de abrir o documento substitui src = "" pela linha da URL para fazer o download do anexo html no iframe, o navegador faz o resto.
Os botões EXCEL / WORD são semelhantes: insira os botões de URL para fazer o download com o nome de arquivo “report.html.xls” ou “report.html.doc” e o tipo MIME correspondente no lugar certo. O resto é feito pelo Excel / Word (“esses animais inteligentes entendem perfeitamente tudo o que querem deles”).
Em do_get.py:
downloadUrl = '/download/' + fn + '?' + '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, ctype, flen]) excel = '/download/%s.xls?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/x-excel', flen])) word = '/download/%s.doc?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/msword', flen])) html = html.replace('src=""', 'src="%s"' % downloadUrl).replace('openExcel', excel).replace('openWord', word)
Ao abrir o html no Excel / Word, existem diferenças no navegador, mas são pequenas. E o artigo não é sobre isso.
Faça uma forma a partir do zero.Dados de origem:
Existem 3 funções
def snd (* msg, cat = 'snd'):
def err (* msg, cat = 'tudo'):
def dbg (* msg, cat = 'snd'):
, que são distribuídos de maneira mais ou menos uniforme no código e gravam mensagens de erro e outras porcarias no arquivo de log.
O formato da mensagem é passado para o log.Formatter como:
'% (tempo de ascensão) s% (nome do nível) s [% (nome) s]% (mensagem) s'
O arquivo está cheio de mensagens
...
09/02/2018 17:50:07 DEBUG [http-server] addr ('127.0.0.1', 49964), “GET / arm HTTP / 1.1” 200 -
09/02/2018 17:54:07 INFO [Espaço livre] Os anexos são salvos em ". \ DB \ files" Grátis 68557 Mb
09/02/2018 17:58:07 ERRO [do_get.py] getScript: [erro 2] Nenhum arquivo ou diretório existe: 'sova / api / forms / o / oo.js'
...
Data e hora, depois nível e, entre colchetes, uma categoria dentro do nível e uma mensagem.
Desafio:
Faça uma página para visualizar o arquivo de log. Que tipo

Vamos chamar o formulário "lm", que será formado pela função page no módulo api / forms / lm.py
def page(dbAlias, mode, userName, multiPage): return dict( style(background='url(/image?bg51.jpg)', backgroundSize='100% 100%'), div=[ dict( style(width='200px', float='left', background='rgba(210,218,203, 0.5)', padding='0 5px'), div=[ _field('type', 'list', [' |all', '|err', '|info', '|debug'], saveAlias=1, **style(margin='10px auto', width=170, height=110) ), _field('cat', 'list', 'TYPE_ALIAS|||api.get?loadDropList&logger|keys_{FIELD}', listItemClassName='repName', listItemSelClassName='repNameSel', **style(height='calc(100vh - 133px)', overflow='auto') ) ], ), _field('msg', 'fd', br=1, **style(overflow='auto', height='100vh', font='bold 12px Courier', background='rgba(255,255,255, 0.8)') ), ] )
No lado esquerdo, existem 2 campos, ambos com o tipo de lista: tipo e gato (tipo e categoria de mensagem).
À direita, há um campo de mensagem do tipo fd (forDisplayOnly).
Os tipos de mensagem são gravados na descrição do campo (['All log | all', 'Errors | err', ...),
as categorias são extraídas por xhr do dicionário global com uma chamada de URL complicada:
api.get? loadDropList & logger | keys_err retornará no formato json uma matriz (lista) de categorias do dicionário global. Algo como bem ('logger', 'keys_err').
As mensagens são geradas quando um documento é aberto pela função queryOpen em lm.py
def queryOpen(d, mode, ground): logParser() ls = well('logger_all', 'AL L') s = '\n'.join(reversed(ls)) d.msg = s d.type_alias = 'all'
logParser lê e analisa o arquivo de log. Ele decompõe os resultados em várias matrizes e os salva no dicionário global. Nada interessante: 2 loop simples de re e iterador.
Funções para trabalhar com o dicionário global:
toWell (o, key1, [key2]) - salva o objeto “o” no dicionário global
well (key1, [key2]) - pega um objeto do dicionário global por chave (por duas chaves).
Isso é suficiente para o primeiro desenho. Para poder exibir mensagens do tipo e categoria desejados, é necessário criar js carregáveis.
Em lm.py, adicione a linha
javaScriptUrl = 'jsv? api / forms / lm / lm.js'
e crie lm.js:
window.sovaActions = window.sovaActions || {}; window.sovaActions.lm = {
getLogData extrai mensagens do servidor do tipo e categoria desejados:
def getLogData(par, un): lg, _, cat = par.partition('|') msg = well('logger_' + lg, cat) return 200, 'text/html; charset=UTF-8', '\n'.join(reversed(msg))
Você pode aproveitar o formulário
AQUI .
Inicialmente, o registro era feito com base no módulo de registro padrão
usando logging.FileHandler, .addHandler e outros getLogger e setFormatter.
Como ensinar Mas, ao mesmo tempo, era de buggy. Você pode jogar pedras, mas quando joguei o log e comecei a escrever em um arquivo, o código ficou mais curto, mais compreensível e as falhas desapareceram.
Está incluído um servidor wsgi multithread auto-escrito com autorização Digest. Isto não é para sites. Por que ele era necessário?
O cliente tem 40 jur. pessoas, na maioria dos casos, 1-2-3 pessoas trabalham com o sistema. É proibido armazenar dados na Internet. Todos ganham 7. Requer facilidade de instalação e configuração.
Solução: usando o cx-Freeze e o Inno Setup, criamos o instalador, executamos no computador do responsável e obtemos um servidor mini-http para a rede local, começando como um serviço do Windows. Nada mais. Você não pode usar o wsgiref.simple_server interno ou o wsgi_Werkzeug embutido no Python, porque eles são de thread único: enquanto um pedido falha, outros aguardam.
É improvável que surpreenda alguém relatando que o Django WSGIServer / 0.2 CPython / 3.5.3 embutido é várias vezes mais rápido que o Python auto-escrito. Somente isso não importa - formulários e diretórios são armazenados em cache no cliente, apenas os dados do banco de dados são transmitidos muito rapidamente pela rede local.
Há mais um motivo: o aplicativo da área de trabalho tem acesso aos recursos do computador (assinatura digital, arquivos, scanner ...). Para obter o mesmo acesso do navegador, você deve gravar um plug-in ou desligar um pequeno servidor http nos serviços, que podem ser detectados pelo servidor principal e executar as ações necessárias na área local.
A coruja não usa as ferramentas de estrutura para trabalhar com o banco de dados. No diretório dbToolkit, algo semelhante em estrutura ao MongoDB (ou Lotus Notes) no SQLite3:
Classe Book - db (na terminologia do MongoDB e Lotus Notes)
Classe DocumentCollection - uma coleção de documentos do Livro
A classe Document é um documento (um objeto que contém qualquer número de campos).
Instalação:Faça o download do
owl.zip no sova.online
O arquivo contém o diretório owl, no qual você pode executar o Owl no django, falcon ou sem frameworks.
Baixe, descompacte.
Instale o Python3 (3.5 ou superior)
1. coruja - sem quadros. Atenção! Login: 1, Senha: 1
Linux:
cd ./owl
python3 wsgi_sova.py
ou em uma janela separada
screen -Udm python3 wsgi_server.py
Windows:
cd ./owl
wsgi_sova.py
2. Django
Linux:
Instale o django:
pip3 install django
cd ./owl
python3 manage.py runserver
ou em uma janela separada
screen -Udm python3 manage.py runserver 127.0.0.1:8000
Windows:
Instale o django:
pip install django
cd ./owl
manage.py runserver
3. falcão
Linux:
pip3 install falcon
cd ./owl
python3 wsgi_sova.py falconApp: api 8001 log_falcon / falcon
Windows:
pip install falcon
cd ./owl
wsgi_sova.py falconApp: api 8001 log_falcon / falcon
**********************
- o título do artigo é estranho, você entendeu o que é "Multipage SPA"?
- jogada de marketing normal
- por que sem o Redux? Todo mundo usa Redux.
- Eu não gosto da palavra "redutor"
- mas sério? ombineReducers em qualquer nível da hierarquia ... É tão bonito
É uma página múltipla, querida. Os manipuladores de comando devem estar dentro do formulário e não como chifres de veado
- Por que você escreveu um artigo?
- PR