Mrr: FRP total para reagir

Mrr é uma biblioteca funcional-reativa para o React (peço desculpas pela tautologia imaginária).

A palavra "reatividade" geralmente se refere ao Rx.js como um FRP de referência. No entanto, uma série de artigos recentes sobre esse assunto em Habré ( [1] , [2] , [3] ) mostrou a complexidade das soluções Rx, que em exemplos simples perdiam em clareza e simplicidade quase todas as outras abordagens. Rx é grande e poderoso e é perfeito para resolver problemas nos quais a abstração do fluxo se sugere (na prática, isso é principalmente a coordenação de tarefas assíncronas). Mas você escreveria, por exemplo, uma simples validação de formulário síncrona no Rx? Ele economizaria seu tempo em comparação com as abordagens imperativas usuais?

mrr é uma tentativa de provar que o FRF pode ser uma solução conveniente e eficaz, não apenas em problemas específicos de "streaming", mas também nas tarefas de rotina mais comuns do front-end.

A programação reativa é uma abstração muito poderosa, no momento no frontend está presente de duas maneiras:

  • variáveis ​​reativas (variáveis ​​computadas): simples, confiáveis, intuitivas, mas o potencial do PR está longe de ser totalmente revelado
  • bibliotecas para trabalhar com fluxos, como Rx, Bacon, etc .: poderoso, mas bastante complicado, o escopo do uso prático é limitado a tarefas específicas.

Mrr combina as vantagens dessas abordagens. Ao contrário do Rx.js, o mrr tem uma API curta, que o usuário pode expandir com suas adições. Em vez de dezenas de métodos e operadores - quatro operadores básicos, em vez de Observável (quente e frio), Assunto, etc. - uma abstração: stream. Além disso, o sr. Não possui alguns conceitos complexos que podem complicar significativamente a legibilidade do código, por exemplo, metastreams.

No entanto, o senhor não é um "Rx simplificado de uma nova maneira". Com base nos mesmos princípios básicos de Rx, o sr. Afirma ser um nicho maior: gerenciar o estado do aplicativo global e local (no nível do componente). Embora o conceito original de programação reativa visasse trabalhar com tarefas assíncronas, o srr usou com sucesso abordagens de reatividade para tarefas síncronas comuns. Este é o princípio do "total FRP".

Freqüentemente, ao criar um aplicativo no React, várias tecnologias diferentes são usadas: recompor (ou em breve - ganchos) para o estado do componente, Redux / mobx para o estado global, Rx com redux observável (ou thunk / saga) para gerenciar efeitos colaterais e coordenar assincronamente tarefas no editor. Em vez de uma "salada" de diferentes abordagens e tecnologias dentro do mesmo aplicativo, com o srr você pode usar uma única tecnologia e paradigma.

A interface mrr também é significativamente diferente da Rx e de bibliotecas semelhantes - é mais declarativa. Graças à abstração da reatividade e à abordagem declarativa, o srr permite escrever código expressivo e conciso. Por exemplo, o TodoMVC padrão em mrr usa menos de 50 linhas de código (sem contar o modelo JSX).

Mas publicidade suficiente. Você conseguiu combinar as vantagens do RP "leve" e "pesado" em uma garrafa - você deve julgar, mas primeiro, leia os exemplos de código.

TodoMVC já é bastante dolorido, e o exemplo de download de dados sobre usuários do Github é muito primitivo para poder sentir os recursos da biblioteca nele. Consideraremos o srr como um exemplo de aplicativo condicional para a compra de bilhetes de trem. Na nossa interface do usuário, haverá campos para escolher as estações de partida e chegada, datas. Depois, após o envio dos dados, uma lista de trens e lugares disponíveis será devolvida. Depois de selecionar um trem e um tipo de carro específicos, o usuário inserirá os dados do passageiro e depois adicionará os bilhetes à cesta. Vamos lá

Precisamos de um formulário com uma escolha de estações e datas:



Crie campos de preenchimento automático para inserir estações.

import { withMrr } from 'mrr'; const stations = [ '', '', '', ' ', ... ] const Tickets = withMrr({ //    $init: { stationFromOptions: [], stationFromInput: '', }, //   - "" stationFromOptions: [str => stations.filter(s => s.indexOf(str)===0), 'stationFromInput'], }, (state, props, $) => { return (<div> <h3>    </h3> <div>  : <input onChange={ $('stationFromInput') } /> </div> <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li>{ s }</li>) } </ul> </div>); }); export default Tickets; 

Os componentes de MR são criados usando a função withMrr, que aceita um diagrama de link reativo (descrição dos fluxos) e uma função de renderização. As funções de renderização são passadas para os props do componente, bem como para o estado, que agora é completamente controlado pelo srr. Ele conterá o inicial (bloco $ init) e calculado pelos valores da fórmula das células reativas.

Agora, temos duas células (ou dois fluxos, que são a mesma coisa): stationFromInput , os valores dos quais caem da entrada do usuário usando o $ helper (passando event.target.value por padrão para elementos de entrada de dados) e uma célula derivada dele stationFromOptions , contendo uma matriz de estações correspondentes por nome.

O valor de stationFromOptions é calculado automaticamente sempre que uma célula pai é alterada usando uma função (na terminologia do srr chamada “ fórmula ” - por analogia com as fórmulas do Excel). A sintaxe das expressões mrr é simples: em primeiro lugar, é a função (ou operador), usada para calcular o valor da célula, e há uma lista de células das quais essa célula depende: seus valores são passados ​​para a função. Uma sintaxe tão estranha, à primeira vista, tem muitas vantagens, as quais consideraremos mais adiante. Até agora, a lógica do srr aqui se assemelha à abordagem usual com variáveis ​​computáveis ​​usadas no Vue, Svelte e outras bibliotecas, com a única diferença é que você pode usar funções puras.

Implementamos a substituição da estação selecionada da lista no campo de entrada. Também é necessário ocultar a lista de estações após o usuário clicar em uma delas.

 const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div>  : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); }); 

Um manipulador de eventos criado usando o $ helper na lista de estações emitirá valores fixos para cada opção.

mrr é consistente em sua abordagem declarativa, alheio a qualquer mutação. Depois de selecionar uma estação, não podemos "forçar" a alteração do valor da célula. Em vez disso, criamos uma nova célula stationFrom , que, usando o operador de mesclagem de fluxo de mesclagem (um equivalente aproximado em Rx é combineLatest), coletará os valores de dois fluxos: entrada do usuário ( stationFromInput ) e seleção de estação ( selectStationFrom ).

Devemos mostrar a lista de opções após o usuário digitar algo e ocultar depois que ele selecionou uma das opções. A célula optionsShown será responsável pela visibilidade da lista de opções, que aceitará valores booleanos dependendo das alterações em outras células. Esse é um padrão muito comum para o qual existe açúcar sintático - o operador de alternância . Ele define o valor da célula como true em qualquer alteração do primeiro argumento (stream) e false - o segundo.

Adicione um botão para limpar o texto digitado.

 const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], clearVal: [a => '', 'clear'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', 'clearVal'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div>  : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> { state.stationFrom && <button onClick={ $('clear') }></button> } </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); }); 

Agora, a nossa célula stationFrom , responsável pelo conteúdo do texto no campo de entrada, coleta seus valores não de dois, mas de três fluxos. Este código pode ser simplificado. Uma construção mrr da forma [* formula *, * ... argumentos da célula *] é semelhante às expressões S no Lisp e, como no Lisp, você pode aninhar arbitrariamente essas construções umas nas outras.

Vamos nos livrar da célula clearVal inútil e encurtar o código:

  stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', [a => '', 'clear']], 

Os programas escritos em um estilo imperativo podem ser comparados a uma equipe mal organizada, onde todos constantemente pedem algo um ao outro (eu insisto em chamadas de método e em alterações mutantes); além disso, os dois gerentes são subordinados e vice-versa. Programas declarativos são semelhantes à imagem utópica oposta: um coletivo em que todos sabem claramente como ele deve agir em qualquer situação. Não há necessidade de pedidos nessa equipe; todos estão apenas em seus lugares e trabalhando em resposta ao que está acontecendo.

Em vez de descrever todas as possíveis consequências de um evento (leia - para fazer certas mutações), descrevemos todos os casos em que esse evento pode ocorrer, ou seja, qual o valor que a célula assumirá com as alterações em outras células. Em nosso pequeno exemplo até agora, descrevemos a célula stationFrom e três situações que afetam seu valor. Para um programador acostumado ao código imperativo, essa abordagem pode parecer incomum (ou mesmo uma "muleta", uma "perversão"). De fato, ele permite que você economize esforço devido à brevidade (e estabilidade) do código, como veremos na prática.

E a assincronia? É possível puxar a lista de estações propostas com ajax? Não tem problema! Em essência, não importa para o senhor se a função retorna um valor ou uma promessa. Quando mrr for retornado, ele aguardará sua resolução e enviará os dados recebidos para o fluxo.

 stationFromOptions: [str => fetch('/get_stations?str=' + str).then(res => res.toJSON()), 'stationFromInput'], 

Isso também significa que você pode usar funções assíncronas como fórmulas. Casos mais complexos (tratamento de erros, status de promessa) serão considerados posteriormente.

A funcionalidade para escolher uma estação de partida está pronta. Não faz sentido duplicar o mesmo para a estação de chegada, vale a pena colocá-lo em um componente separado que possa ser reutilizado. Este será um componente de entrada generalizado com preenchimento automático, portanto, renomearemos os campos e criaremos a função para obter opções adequadas definidas nos objetos.

 const OptionsInput = withMrr(props => ({ $init: { options: [], }, val: ['merge', 'valInput', 'selectOption', [a => '', 'clear']], options: [props.getOptions, 'val'], optionsShown: ['toggle', 'valInput', 'selectOption'], }), (state, props, $) => <div> <div> <input onChange={ $('valInput') } value={ state.val } /> </div> { state.optionsShown && <ul className="options"> { state.options.map(s => <li onClick={ $('selectOption', s) }>{ s }</li>) } </ul> } { state.val && <div className="clear" onClick={ $('clear') }> X </div> } </div>) 

Como você pode ver, você pode especificar a estrutura das células mrr como uma função do componente props (no entanto, ele será executado apenas uma vez após a inicialização e não responderá a alterações nos props).

Comunicação entre componentes


Agora conecte esse componente no componente pai e veja como o mrr permite que componentes relacionados troquem dados.

 const getMatchedStations = str => fetch('/get_stations?str=' + str).then(res => res.toJSON()); const Tickets = withMrr({ stationTo: 'selectStationFrom/val', stationFrom: 'selectStationTo/val', }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); }); 

Para associar um componente pai a um componente filho, devemos passar parâmetros para ele usando a função connectAs (quarto argumento da função render). Nesse caso, indicamos o nome que queremos dar ao componente filho. Ao anexar um componente dessa maneira, com esse nome, podemos acessar suas células. Nesse caso, estamos ouvindo células val. O inverso também é possível - escute o componente filho da célula pai.

Como você pode ver, aqui o srr segue uma abordagem declarativa: nenhum retorno de chamada onChange é necessário, basta especificar um nome para o componente filho na função connectAs, após o qual obtemos acesso às células! Nesse caso, novamente devido à declaratividade, não há ameaça de interferência no trabalho de outro componente - não temos a capacidade de "mudar" nada, alterar, podemos apenas "ouvir" os dados.

Sinais e Valores


A próxima etapa é a busca de trens adequados para os parâmetros selecionados. Na abordagem imperativa, provavelmente escreveríamos um determinado processador para enviar o formulário onSubmit, o que iniciaria ações adicionais - uma solicitação ajax e exibição dos resultados. Mas, como você se lembra, não podemos "pedir" nada! Só podemos criar outro conjunto de células derivadas das células do formulário. Vamos escrever mais uma solicitação.

 const getTrains = (from, to, date) => fetch('/get_trains?from=' + from + '&to=' + to + '&date=' + date).then(res => res.toJSON()); const Tickets = withMrr({ stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', results: [getTrains, 'stationFrom', 'stationTo', 'date', 'searchTrains'], }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); }); 

No entanto, esse código não funcionará conforme o esperado. O cálculo (recálculo do valor da célula) é iniciado quando qualquer um dos argumentos muda, portanto, a solicitação será enviada, por exemplo, imediatamente após a seleção da primeira estação, e não apenas clicando em "Pesquisar". Precisamos, por um lado, que as estações e a data sejam passadas para os argumentos da fórmula, mas, por outro lado, não respondam à sua mudança. No sr., Existe um mecanismo elegante para isso chamado de escuta passiva.

  results: [getTrains, '-stationFrom', '-stationTo', '-date', 'searchTrains'], 

Basta adicionar um sinal de menos na frente do nome da célula e pronto! Agora, os resultados responderão apenas às alterações na célula searchTrains .

Nesse caso, a célula searchTrains atua como um "sinal de célula" e as células da estaçãoFrom e outras como "valores de célula". Para uma célula de sinal, apenas o momento em que os valores “fluem” através dela é importante e que tipo de dados serão - de qualquer maneira: pode ser apenas verdadeiro, “1” ou qualquer outra coisa (no nosso caso, esses serão objetos de evento DOM ) Para uma célula de valor, é seu valor que é importante, mas, ao mesmo tempo, os momentos de sua mudança não são significativos. Esses dois tipos de células não são mutuamente exclusivos: muitas células são sinais e valores. No nível de sintaxe em mrr, esses dois tipos de células não diferem de forma alguma, mas o entendimento conceitual de tal diferença é muito importante ao escrever código reativo.

Dividindo fluxos


Uma solicitação para procurar assentos no trem pode levar algum tempo; portanto, devemos mostrar ao carregador e também responder em caso de erro. Já existem poucas promessas para essa abordagem padrão com a resolução automática.

 const Tickets = withMrr({ $init: { results: {}, } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['nested', (cb, query) => { cb({ loading: true, error: null, data: null }); getTrains(query.from, query.to, query.date) .then(res => cb('data', res)) .catch(err => cb('error', err)) .finally(() => cb('loading', false)) }, 'searchQuery'], availableTrains: 'results.data', }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . ,  .   .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train) => <div />) } </div> } </div> </div>); }); 

O operador aninhado permite “decompor” os dados em subcélulas; para isso, o primeiro argumento da fórmula é o retorno de chamada, com o qual você pode “enviar” os dados para a subcélula (uma ou mais). Agora, temos fluxos separados que são responsáveis ​​pelo erro, pelo status da promessa e pelos dados recebidos. O operador aninhado é uma ferramenta muito poderosa e um dos poucos imperativos em mrr (nós mesmos especificamos em quais células colocar os dados). Enquanto o operador de mesclagem mescla vários threads em um, aninhado divide o thread em vários sub-threads, sendo assim o oposto.

O exemplo acima é uma maneira padrão de trabalhar com promessas, em mrr é generalizado como um operador de promessa e permite que você reduza o código:

  results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], //     availableTrains: 'results.data', 

Além disso, o operador da promessa garante que apenas os resultados da promessa mais recente sejam usados.



Componente para exibir os assentos disponíveis (por simplicidade, recusaremos os diferentes tipos de carros)

 const TrainSeats = withMrr({ selectSeats: [(seatsNumber, { id }) => new Array(Number(seatsNumber)).fill(true).map(() => ({ trainId: id })), '-seatsNumber', '-$props', 'select'], seatsNumber: [() => 0, 'selectSeats'], }, (state, props, $) => <div className="train">  №{ props.num } { props.from } - { props.to }.   : { props.seats || 0 } { props.seats && <div>     : <input type="number" onChange={ $('seatsNumber') } value={ state.seatsNumber || 0 } max={ props.seats } /> <button onClick={ $('select') }></button> </div> } </div>); 

Para acessar adereços em uma fórmula, você pode se inscrever na caixa especial $ props.

 const Tickets = withMrr({ ... selectedSeats: '*/selectSeats', }, (state, props, $, connectAs) => { ... <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } 

Novamente, usamos a audição passiva para selecionar o número de lugares selecionados quando você clica no botão "Selecionar". Associamos cada componente filho ao pai usando a função connectAs. O usuário pode selecionar assentos em qualquer um dos trens propostos, por isso ouvimos as alterações em todos os componentes filhos usando a máscara "*".

Mas aqui está o problema: o usuário pode adicionar assentos primeiro em um trem, depois em outro, para que os novos dados triturem os anteriores. Como "acumular" dados de fluxo? Para fazer isso, existe um operador de fechamento , que, juntamente com aninhado e funil, forma a base do sr. (Todos os outros nada mais são do que açúcar sintático com base nesses três).

  selectedSeats: ['closure', () => { let seats = []; //     return selectedSeats => { seats = [...seats, selectedSeats]; return seats; } }, '*/selectSeats'], 

Ao usar o fechamento primeiro (em componentDidMount), é criado um fechamento que retorna a fórmula. Ela, portanto, tem acesso a variáveis ​​de fechamento. Isso permite que você salve dados entre as chamadas de maneira segura - sem cair no abismo das variáveis ​​globais e do estado mutável compartilhado. Assim, o fechamento permite implementar a funcionalidade dos operadores Rx, como varredura e outros. No entanto, esse método é bom para casos difíceis. Se precisarmos salvar apenas o valor de uma variável, podemos simplesmente usar o link para o valor anterior da célula usando o nome especial "^":

  selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] 

Agora, o usuário deve inserir um nome e sobrenome para cada ticket selecionado.

 const SeatDetails = withMrr({}, (state, props, $) => { return (<div> { props.trainId } <input name="name" value={ props.name } onChange={ $('setDetails', e => ['name', e.target.value, props.i]) } /> <input name="surname" value={ props.surname } onChange={ $('setDetails', e => ['surname', e.target.value, props.i]) }/> <a href="#" onClick={ $('removeSeat', props.i) }>X</a> </div>); }) const Tickets = withMrr({ $init: { results: {}, selectedSeats: [], } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], availableTrains: 'results.data', selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . ,  .   .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } { state.selectedSeats.map((seat, i) => <SeatDetails key={i} i={i} { ...seat } {...connectAs('seat' + i)}/>) } </div> </div>); }); 

A célula selectedSeats contém uma matriz de locais selecionados. À medida que o usuário digita o nome e o sobrenome de cada ticket, devemos alterar os dados nos elementos correspondentes da matriz.

  selectedSeats: [(seats, details, prev) => { // ??? }, '*/selectSeats', '*/setDetails', '^'] 

A abordagem padrão não é adequada para nós: na fórmula, precisamos saber qual célula mudou e responder de acordo. Uma das formas do operador de mesclagem nos ajudará.

  selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, }, '^'/*,       */], 

É um pouco como redutores Redux, mas com sintaxe mais flexível e poderosa. E você não pode ter medo de alterar a matriz, porque apenas a fórmula de uma célula tem controle sobre ela, respectivamente, alterações paralelas são excluídas (mas a modificação de matrizes que são passadas como argumentos certamente não vale a pena).

Coleções reativas


O padrão quando a célula armazena e altera a matriz é muito comum. Todas as operações com a matriz são de três tipos: inserir, modificar, excluir. Para descrever isso, existe um elegante operador de coll . Use-o para simplificar o cálculo dos bancos selecionados .

Foi:

  selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, 'addToCart': () => [], }, '^'] 

tornou-se:

  selectedSeats: ['coll', { create: '*/selectSeats', update: '*/setDetails', delete: ['merge', '*/removeSeat', [() => ({}), 'addToCart']] }] 

no entanto, o formato dos dados no fluxo setDetails precisa ser alterado um pouco:

  <input name="name" onChange={ $('setDetails', e => [{ name: e.target.value }, props.i]) } /> <input name="surname" onChange={ $('setDetails', e => [{ surname: e.target.value }, props.i]) }/> 

Usando o operador coll , descrevemos três threads que afetarão nossa matriz. Nesse caso, o fluxo de criação deve conter os próprios elementos, que devem ser adicionados à matriz (geralmente objetos). O fluxo de exclusão aceita os índices dos elementos a serem excluídos (ambos em '* / removeSeat') e as máscaras. A máscara {} excluirá todos os elementos e, por exemplo, a máscara {name: 'Carl'} excluirá todos os elementos com o nome Carl. O fluxo de atualização aceita pares de valores: a alteração que precisa ser feita com o elemento (máscara ou função) e o índice ou máscara dos elementos que precisam ser alterados. Por exemplo, [{sobrenome: 'Johnson'}, {}] definirá o sobrenome Johnson para todos os elementos da matriz.

O operador coll usa algo como uma linguagem de consulta interna, facilitando o trabalho com coleções e tornando-a mais declarativa.

O código completo da nossa aplicação no JsFiddle.

Nós nos familiarizamos com quase todas as funcionalidades básicas necessárias do srr. Um tópico bastante significativo que permaneceu no mar é o gerenciamento de patrimônio global, que pode ser discutido em artigos futuros. Mas agora você pode começar a usar o mrr para gerenciar o estado dentro de um componente ou grupo de componentes relacionados.

Conclusões


Qual é o poder do srr?


O mrr permite que você escreva aplicativos no React com um estilo reativo funcional (o mrr pode ser descriptografado como Tornar o React Reactivo). mrr é muito expressivo - você gasta menos tempo escrevendo linhas de código.

O srr fornece um pequeno conjunto de abstrações básicas, o suficiente para todos os casos - este artigo descreve quase todos os principais recursos e técnicas do srr.Também existem ferramentas para expandir esse conjunto básico (a capacidade de criar operadores personalizados). Você pode escrever um código declarativo bonito sem ler centenas de páginas do manual e sem sequer estudar as profundezas teóricas da programação funcional - é improvável que você precise usar, digamos, mônadas, porque O próprio srr é uma mônada gigante que separa a computação pura das mutações de estado.

Enquanto em outras bibliotecas coexistem abordagens heterogêneas (imperativas usando métodos e declarativas usando ligantes reativos), das quais o programador mistura aleatoriamente “salada”, no sr. Existe uma única essência básica - um fluxo, que contribui para a homogeneidade e uniformidade do código. Conforto, conveniência, simplicidade, economizar o tempo de um programador são as principais vantagens do srr (a partir daqui é outra decodificação do srr como "srrrr", ou seja, ronronar um gato que está satisfeito com a vida de um gato).

Quais são as desvantagens?


Programar com "linhas" tem suas vantagens e desvantagens. Você não poderá preencher automaticamente o nome da célula, nem procurar o local onde está definido. Por outro lado, em mrr sempre existe um e apenas um local em que o comportamento da célula é determinado, e é fácil encontrá-lo com uma simples pesquisa de texto, enquanto procura o local em que o valor do campo Redux da loja é determinado, ou ainda menos o campo state ao usar o setState nativo pode demorar mais.

Quem poderia estar interessado nisso?


Antes de tudo, os adeptos da programação funcional são pessoas para quem a vantagem de uma abordagem declarativa é óbvia. Obviamente, as soluções kosher do ClojureScript já existem, mas ainda assim continuam sendo um produto de nicho, enquanto o React domina a bola. Se o seu projeto já usa o Redux, você pode começar a usar o mrr para gerenciar o estado local e, no futuro, mudar para o global. Mesmo se você não planeja usar novas tecnologias no momento, pode lidar com o sr. Para "esticar seu cérebro", examinando tarefas familiares sob uma nova luz, porque o sr. É significativamente diferente das bibliotecas de gerenciamento de estado comuns.

Isso já pode ser usado?


Em princípio, sim :) A biblioteca é jovem, até agora foi ativamente usada em vários projetos, mas a API da funcionalidade básica já foi estabelecida, agora o trabalho está sendo realizado principalmente em diferentes loções (açúcar sintático), projetadas para acelerar e facilitar o desenvolvimento. A propósito, nos princípios de mrr não há nada específico para o React, ele pode ser adaptado para uso com qualquer biblioteca de componentes (o React foi escolhido devido à falta de reatividade interna ou a uma biblioteca geralmente aceita para isso).

Obrigado por sua atenção, serei grato pelo feedback e críticas construtivas!

Source: https://habr.com/ru/post/pt428870/


All Articles