Esta é a segunda parte final do tutorial em que escrevemos o cliente TodoMVC usando a estrutura
ds js reativa minimalista.
Resumo da
primeira parte : recebemos uma lista de tarefas do servidor no formato JSON, construímos uma lista HTML, adicionamos a capacidade de editar o nome e o sinal de conclusão de cada caso e implementamos uma notificação do servidor sobre essas edições.
Resta realizar: exclusão de casos arbitrários, adição de novos casos, instalação / redefinição em massa e filtragem de casos com base na conclusão e na função de excluir todos os casos concluídos. É isso que faremos. A versão final do cliente, a qual abordaremos neste artigo, pode ser vista
aqui .
A opção que escolhemos da última vez pode ser atualizada
aqui .
Aqui está o código dele:
'#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed $editing= $patch=; a!" ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) .FUNC({ convert:{ dehttp: url=>url.replace(/^https?\:/,'') } }) .RENDER()
Agora, existem apenas cinquenta linhas aqui, mas até o final do artigo, haverá duas vezes mais - até 100. Haverá muitas solicitações HTTP para o servidor; portanto, abra as ferramentas do desenvolvedor (no Chrome, como você se lembra, Ctrl + Shift + I) - haverá Primeiro, a guia Rede é interessante e, em segundo lugar, o console. Além disso, não se esqueça de visualizar o código de cada versão da nossa página - no Chrome, é Ctrl + U.
Aqui eu tenho que fazer uma pequena digressão. Se você não leu a
primeira parte do tutorial, ainda recomendo começar com ele. Se você o leu, mas não entendeu nada, é melhor lê-lo novamente. Como mostram os comentários nos meus dois artigos anteriores, a sintaxe e o princípio da dap nem sempre são imediatamente entendidos por um leitor despreparado. Outro artigo não é recomendado para leitura para pessoas que se sentem desconfortáveis com a aparência de sintaxe não semelhante a si.

Esta segunda parte do tutorial será um pouco mais complicada e interessante que a primeira.
[TODO: peça ao token para encontrar uma imagem de cérebro explodindo de um aluno na internet] .
Com sua permissão, continuarei a numerar os capítulos com a parte 1. Lá contamos até 7. Então,
8. Fazendo uma lista de tarefas da variável de estado
Para remover um caso da lista, existe um botão
BUTTON.destroy
. A remoção consiste em enviar uma solicitação DELETE para o servidor e remover de vista o elemento
UL#todo-list > LI
correspondente
UL#todo-list > LI
com todo o conteúdo. Com o envio de uma solicitação DELETE, tudo fica claro:
,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query")
Mas com a remoção de um elemento da tela, são possíveis opções. Pode-se simplesmente introduzir outra variável de estado, digamos
$deleted
, e ocultar o elemento com CSS, incluindo a classe CSS
deleted
,'LI'.d("$completed=.completed $editing= $patch= $deleted=; a!"
E funcionaria. Mas seria trapaça. Além disso, mais adiante, teremos filtros e contadores de casos ativos e concluídos (o que está em
#footer
). Portanto, é melhor remover imediatamente o objeto da lista de tarefas com honestidade, "fisicamente". Ou seja, precisamos da capacidade de modificar a própria matriz, que recebemos inicialmente do servidor - o que significa que essa matriz também deve se tornar uma variável de estado. Vamos chamá-la de
$todos
.
O escopo da variável
$todos
é selecionar o ancestral comum de todos os elementos que acessarão essa variável. E será acessado por
INPUT#new-todo
partir de
#header
e contadores de
#footer
e, na verdade,
UL#todo-list
. O ancestral comum de todos eles é o elemento raiz do modelo,
#todoapp
. Portanto, em sua regra d, definiremos a variável
$todos
. No mesmo local, enviamos imediatamente os dados do servidor para ele. E para criar a lista
UL#todo-list
, agora também faremos dela:
'#todoapp'.d("$todos=todos:query"
É importante. Se durante o teste repentinamente a lista de tarefas a carregar não for possível - é bem possível que alguém tenha excluído todos eles (este é um servidor público e tudo pode acontecer lá).
Nesse caso, vá para um
exemplo completo e crie alguns casos para experimentar.
Nós olhamos . Aqui
$todos
declarado na regra d do elemento
#todoapp
e é
inicializado imediatamente
com os dados necessários. Tudo parece funcionar, mas um recurso desagradável apareceu. Se o servidor responder à solicitação por um longo tempo (o Chrome permite simular essa situação: na guia Rede das ferramentas do desenvolvedor, você pode escolher diferentes modos de simular redes lentas), nossa nova versão do aplicativo até que a solicitação seja concluída parece um pouco triste - não há nada além de CSS artefatos. Essa imagem definitivamente não adicionará entusiasmo ao usuário. Embora a versão anterior não sofresse com isso - até os dados serem recebidos na página, apenas a lista estava ausente, mas outros elementos apareceram imediatamente, sem aguardar os dados.
Aqui está a coisa. Como você se lembra, o conversor
:query
é assíncrono. Essa assincronia é expressa no fato de que, até que a solicitação seja concluída, apenas a execução da regra atual é bloqueada, ou seja, a geração de um elemento que realmente precisa dos dados solicitados (o que é lógico). A geração de outros elementos não está bloqueada. Portanto, quando
UL#todo-list
acessou o servidor, apenas ele foi bloqueado, mas não o
#header
e o
#footer
, que foram desenhados imediatamente. Agora, todo o
#todoapp
está aguardando a conclusão da solicitação.
9. Carregamento atrasado de dados
Para corrigir a situação e evitar o bloqueio de elementos não envolvidos, adiaremos o carregamento inicial de dados até que tudo já esteja desenhado. Para fazer isso, não carregaremos imediatamente os dados na variável
$todos
, mas, primeiro, simplesmente os inicializamos com "nothing"
'#todoapp'.d("$todos="
Portanto, ela não bloqueará nada e todo o modelo funcionará - embora por enquanto com uma "lista de tarefas" vazia. Mas agora, com uma tela inicial chata, você pode
modificar com segurança
$todos
fazendo o upload de uma lista de tarefas. Para fazer isso, adicione este descendente a
#todoapp
,'loader' .u("$todos=todos:query")
Este elemento possui uma regra-u que se parece exatamente com a regra de bloqueio que rejeitamos, mas há uma diferença fundamental.
Deixe-me lembrá-lo de que a regra-d (de
baixo para
baixo ) é a regra de geração de elemento que é executada quando o modelo é criado de cima para
baixo , de pai para descendente; e u-rules (de
cima para
baixo ) são regras de reação que são executadas em resposta a um evento que aparece de baixo para
cima , de filho para pai.
Portanto, se algo (incluindo "nada") for atribuído
a uma variável
na regra d , isso significa sua
declaração e inicialização no escopo deste elemento e seus descendentes (escopos aninhados são implementados no dap, como em JS ) A atribuição
em up-rules significa uma
modificação de uma variável declarada anteriormente no escopo. A declaração e a inicialização de variáveis na regra d permitem que o pai transmita aos descendentes pela hierarquia as informações necessárias para a construção, e a modificação permite que o pai transmita atualizações dessas informações para o topo e, assim, inicie a reestruturação correspondente de todos os elementos que dependem dela.
O elemento
loader
, descendente de
#todoapp
, em sua regra-u
modifica a variável
$todos
, carregando dados do servidor para ele, o que causa a regeneração automática de todos os elementos de consumo dessa variável (e somente eles, o que é importante!). Os consumidores de uma variável são elementos cujas regras d contêm essa variável como um rvalor, ou seja, Quem
lê essa variável (levando em consideração o escopo) ao construir.
Agora temos um consumidor da variável
$todos
- a própria
UL#todo-list
, que, consequentemente, será reconstruída após o carregamento dos dados.
,'UL#todo-list'.d("*@ $todos"
Portanto,
agora temos uma lista de tarefas a fazer é uma variável de estado em
#todoapp
, sem bloquear a renderização inicial do modelo.
10. Excluindo e adicionando tarefas
Agora podemos modificar
$todos
todas as formas. Vamos começar removendo os itens. Já temos um botão cruzado
BUTTON.destroy
, que até agora simplesmente envia as solicitações de remoção do servidor
,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query")
É necessário garantir que o objeto correspondente também seja excluído da variável
$todos
- e como isso será uma modificação,
UL#todo-list
, como consumidor dessa variável, será reconstruída automaticamente, mas sem o elemento excluído.
Por si só, o dap não fornece nenhum meio especial para manipular dados. As manipulações podem ser perfeitamente escritas em funções em JS, e as regras dap simplesmente fornecem dados para elas e capturam o resultado. Nós escrevemos uma função JS para remover um objeto de uma matriz sem saber seu número. Por exemplo, isto:
const remove = (arr,tgt)=> arr.filter( obj => obj!=tgt );
Você provavelmente pode escrever algo mais eficaz, mas não é sobre isso agora. É improvável que nosso aplicativo precise trabalhar com listas de tarefas de milhões de itens. O importante é que a função retorne um novo objeto de matriz e não apenas remova o elemento do que é.
Para tornar essa função acessível a partir das regras dap, você precisa adicioná-la à seção
.FUNC
, mas antes disso, decida como queremos chamá-la. A opção mais simples nesse caso é, talvez, chamá-lo do conversor, que aceita o objeto
{ todos, tgt }
e retorna uma matriz filtrada
.FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''),
mas não há nada que o impeça de definir essa função dentro do
.FUNC
(eu já disse que
.FUNC
é na verdade um método JS comum e seu argumento é um objeto JS comum?)
.FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ) } })
Agora podemos acessar este conversor a partir das regras dap
,'BUTTON.destroy' .ui("$todos=($todos $@tgt):remove (@method`DELETE .url:dehttp):query")
Aqui, primeiro formamos um objeto que, na notação JS, corresponde a
{ todos, tgt:$ }
, passa-o para o conversor
:remove
descrito em
.FUNC
e retorna o resultado filtrado para
$todos
, modificando-o. Aqui
$
é o
contexto de dados do elemento, esse objeto de negócios da matriz
$todos
no qual o modelo é construído. Após o símbolo
@
, o alias do argumento é indicado. Se
@
ausente, o nome do argumento será usado. Isso é semelhante à recente inovação ES6 -
abreviação de propriedades .
Da mesma forma, adicionamos um novo caso à lista usando o elemento
INPUT#new-todo
e a solicitação POST
,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") ... .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ),
A regra de reação do elemento
INPUT#new-todo
a um evento de interface do usuário padrão (para os elementos
INPUT
, o evento de
change
é considerado o dap padrão) inclui: ler a entrada do usuário a partir da propriedade
value
desse elemento, formando um contexto
$
local com esse valor como um campo
.title
, enviando
$
context para o servidor usando o método POST, modificando a matriz
$todos
adicionando o contexto
$
como um novo elemento e finalmente limpando a propriedade
value
do elemento
INPUT
.
Aqui, um jovem leitor pode perguntar: por que usar
concat()
ao adicionar um elemento a uma matriz, se isso pode ser feito usando
push()
regular
push()
? Um leitor experiente entenderá imediatamente qual é o problema e escreverá sua resposta nos comentários.
Nós olhamos o que
aconteceu: os casos são adicionados e excluídos normalmente, as solicitações correspondentes são enviadas ao servidor corretamente (você mantém a guia Rede aberta o tempo todo, certo?). Mas e se quisermos mudar o nome ou o status de um caso recém-adicionado? O problema é que, para notificar o servidor sobre essas alterações, precisamos de
.url
, que atribui o servidor a esse negócio. Quando criamos o negócio, não conhecíamos seu
.url
, respectivamente, não podemos formar a solicitação PATCH correta para alteração.
De fato, todas as informações necessárias sobre o caso estão contidas na resposta do servidor à solicitação POST, e seria mais correto criar um novo objeto de negócios não apenas a partir da entrada do usuário, mas da resposta do servidor, e adicionar esse objeto a
$todos
- com todas as informações fornecidas. informações do servidor, incluindo o campo
.url
,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (@method`POST todos@url headers (#.value@title)):query@tgt ):insert #.value=")
Olhamos - tudo bem, agora tudo está sendo resolvido corretamente. As notificações ao servidor sobre a edição de casos recém-criados dão certo.
Pode-se parar com isso, mas ... Mas se você olhar atentamente, ainda poderá notar um pequeno atraso entre inserir o nome do novo caso e o momento em que ele aparecer na lista. Esse atraso é claramente visível se você ativar uma rede lenta simulada. Como você deve ter adivinhado, a questão é uma solicitação ao servidor: primeiro, solicitamos dados para um novo caso do servidor e somente após recebê-los, modificamos
$todos
. O próximo passo, tentaremos corrigir essa situação, mas primeiro voltarei sua atenção para outro ponto interessante. Se voltarmos um pouco para a
versão anterior , observamos: embora a solicitação também esteja lá, o novo caso é adicionado à lista instantaneamente, sem esperar que a solicitação termine.
Esse é outro recurso da elaboração de conversores assíncronos no dap: se o resultado do conversor assíncrono não for usado (ou seja, não estiver atribuído a nada), você não poderá esperar pela conclusão - e a execução da regra não será bloqueada. Isso geralmente é útil: você pode ter notado que, ao excluir casos da lista, eles desaparecem da tela instantaneamente, sem aguardar o resultado da solicitação DELETE. Isso é especialmente perceptível se você excluir rapidamente vários casos seguidos e rastrear solicitações no painel Rede.
Mas, como usamos o resultado da solicitação POST - atribua-a ao contexto
$
- precisamos esperar que ela seja concluída. Portanto, você precisa encontrar outra maneira de modificar
$todos
antes de executar a solicitação POST. Solução: ainda, primeiro crie um novo objeto de negócios e adicione-o imediatamente a
$todos
, deixe a lista ser
.url
e somente depois, após a renderização, se o negócio não tiver
.url
(ou seja, o negócio acabou de ser criado), execute uma solicitação POST e impor seu resultado no contexto de dados deste caso.
Então, primeiro basta adicionar um espaço em branco contendo apenas
.title
à
.title
,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (#.value@title)@tgt):insert #.value=")
UL#todo-list > LI
A regra de geração de elemento
UL#todo-list > LI
já contém
a!
iniciando uma regra após o primeiro elemento ser desenhado. Lá, podemos adicionar o lançamento de uma solicitação POST na ausência de
.url
. Para injetar campos adicionais no contexto, dap possui o operador
&
.a("!? $completed $editing; ? .url:!; & (@method`POST todos@url headers $):query")
Nós olhamos . Outra coisa! Mesmo com uma rede lenta, a lista de tarefas é atualizada instantaneamente e a notificação do servidor e o carregamento de dados ausentes ocorrem em segundo plano, após o desenho da lista atualizada.
11. Daw todos!
No elemento
#header
, há um botão para instalação em massa / redefinição do sinal de conclusão para todos os casos na lista. Para a atribuição em massa de valores aos campos dos elementos da matriz, basta escrever outro conversor
:assign
, e aplicá-lo a
$todos
, clicando em
INPUT#toggle-all
,'INPUT#toggle-all type=checkbox' .ui("$todos=($todos (#.checked@completed)@src):assign") ... assign: x => x.todos && x.todos.map(todo => Object.assign(todo,x.src))
Nesse caso, estamos interessados apenas no campo
.completed
, mas é fácil ver que, com esse conversor, você pode alterar massivamente os valores de qualquer campo dos elementos da matriz.
Ok, na matriz
$todos
as
$todos
trocadas, agora precisamos notificar o servidor sobre as alterações feitas. No exemplo original, isso é feito enviando solicitações PATCH para cada caso - não uma estratégia muito eficaz, mas não depende mais de nós. Ok, para cada caso, enviamos uma solicitação PATCH
.ui("*@ $todos=($todos (#.checked@completed)@src):assign; (@method`PATCH .url:dehttp headers (.completed)):query")
Observamos : Um clique em um daw comum alinha todos os daws individuais e o servidor é notificado por solicitações de PATCH apropriadas. Norma
12. Filtrando casos com base na conclusão
Além da lista de tarefas pendentes, o aplicativo também deve poder filtrar casos pelo sinal de conclusão e mostrar contadores de tarefas concluídas e inacabadas. Obviamente, para filtrar, seremos triviais usar o mesmo método
filter()
fornecido pelo próprio JS.
Mas primeiro você precisa garantir que o campo
.completed
de cada caso seja sempre verdadeiro e, quando você clicar, o daw individual do caso será atualizado junto com a variável
$completed
. Anteriormente, isso não era importante para nós, mas agora será.
,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()")
O ponto importante aqui é que o contexto de dados de cada caso é o próprio objeto de caso, que fica na matriz
$todos
. Não é uma cópia única ou construção relacionada, mas o próprio objeto. E todas as chamadas para os campos
.title
,
.completed
,
.completed
url
- leitura e gravação - se aplicam diretamente a esse objeto. Portanto, para filtrar a matriz
$todos
para funcionar corretamente, precisamos garantir que a conclusão do caso seja refletida não apenas na madrugada na tela, mas também no campo
.completed
do objeto de
.completed
.
Para mostrar apenas os casos com o sinal necessário de completude
.completed
na lista, filtraremos simplesmente
$todos
acordo com o filtro selecionado. O filtro selecionado é, você adivinhou, outra variável de estado de nosso aplicativo, e nós o chamaremos:
$filter
. Para filtrar
$todos
acordo com o
$filter
selecionado
$filter
vamos seguir a miniatura e apenas adicionar outro conversor, do formulário
{list, filter} => a lista filtrada , e pegaremos os nomes e as funções de filtragem da "matriz associativa" (ou seja, JS comum objeto)
todoFilters
const todoFilters={ "All": null, "Active": todo => !todo.completed, "Completed": todo => !!todo.completed }; '#todoapp'.d("$todos= $filter="
Nós verificamos . Os filtros funcionam corretamente. Há uma nuance em que os nomes dos filtros são exibidos juntos, porque aqui nos afastamos um pouco da estrutura DOM do original e saímos do CSS. Mas voltaremos a isso um pouco mais tarde.
13. Contadores de casos concluídos e ativos.
Para mostrar os contadores de casos concluídos e ativos, basta filtrar
$todos
com os filtros apropriados e mostrar os comprimentos das matrizes resultantes
,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter" ,'#todo-count'.d("! (active $active.length)format")
Nesse
formulário, os contadores mostram os valores corretos no momento da inicialização, mas não respondem a alterações subseqüentes na conclusão dos assuntos (ao clicar nas daws). O fato é que cliques em gralhas, alterando o estado de cada caso individual, não alteram o estado de
$todos
- uma modificação de um elemento da matriz não é uma modificação da própria matriz. Portanto, precisamos de um sinal adicional sobre a necessidade de registrar novamente os casos. Esse sinal pode ser uma variável de estado adicional, que é modificada toda vez que é necessária a recontagem. Chame de
$recount
.
Declararemos um ancestral comum na regra d, atualizaremos quando clicarmos no daws e #footer
tornaremos o elemento um consumidor - para isso, basta mencionar essa variável em sua regra d '#todoapp'.d("$todos= $filter= $recount="
Agora tudo funciona como deveria, os contadores são atualizados corretamente.14. Exclusão de todos os casos concluídos.
A exclusão em lote de casos no TodoMVC é implementada como não kosher como modificação em lote - por várias solicitações. Bem, vamos dar um suspiro, encolher os ombros e executar por solicitação DELETE para cada caso concluído - e já temos todos eles $completed
. Assim, $todos
após a remoção dos casos concluídos, o que já deveria estar em$active
,'#clear-completed' .d("! (completed $completed.length)format") .ui("$todos=$active; *@ $completed; (@method`DELETE .url:dehttp):query")
Olhamos : criamos alguns assuntos desnecessários, marcamos com daws e excluímos. A guia Rede mostrará o horror dessa abordagem para operações em lote.15. Status na barra de endereço
Voltar para a seleção de filtros. No exemplo original, o filtro selecionado é refletido na barra de endereço após #. Ao alterar o fragmento # na barra de endereços manualmente ou durante a navegação, o filtro selecionado também muda. Isso permite que você vá para a página do aplicativo por URL com o filtro de tarefas já selecionado.Você pode escrever com um location.hash
operador urlhash
, por exemplo, na regra de um elemento #todoapp
(ou qualquer de seus descendentes), que será executado a cada atualização$filter
.a("urlhash $filter")
E você pode inicializar a variável com um $filter
valor da barra de endereço e depois atualizar pelo evento hashchange usando um pseudo-conversor :urlhash
que retorna o estado atual location.hash
(sem #) .d("$todos= $filter=:urlhash $recount=" .e("hashchange","$filter=:urlhash")
O evento hashchange é gerado pelo navegador quando um fragmento # na barra de endereço é alterado. No entanto, por alguma razão única window
, e document.body
pode ouvir este evento. Para rastrear esse evento a partir de um elemento #todoapp
, você precisará adicionar um operador à regra d listen
que assina o elemento para retransmitir eventos do objetowindow
'#todoapp' .a("urlhash $filter") .e("hashchange","$filter=:urlhash") .d("$todos= $filter=:urlhash $recount=; listen @hashchange"
Observamos : alternar filtros, rastrear alterações na barra de endereço, seguir os links com #Active , #All , #Completed . Tudo funciona. Mas voltando ao original. Parece que a escolha do filtro é implementada - clicando nos links. Embora não seja muito prático, faremos o mesmo por uma questão de integridade. ,'UL#filters'.d("* filter" ,'LI'.d("" ,'A'.d("!! (`# .filter)concat@href .filter@") ) )
E para destacar o filtro selecionado, adicione um operador de estilização condicional !?
que adicionará uma classe CSS ao elemento selected
se o valor no campo de .filter
contexto for igual ao valor da variável$filter
,'A'.d("!! (`# .filter)concat@href .filter@; !? (.filter $filter)eq@selected")
Em deste modo a funcionalidade dos nossos Dap-aplicações já estão completamente (tanto quanto eu posso dizer) corresponde ao que faz o original .16. Alguns retoques finais
Não gosto muito disso no original, a forma do cursor não muda sobre os elementos ativos, por isso, adicionaremos head
esse estilo ao nosso documento HTML [ui=click]{cursor:pointer}
Então, pelo menos, veremos onde você pode clicar.Ah sim! Resta escrever a palavra "todos" em letras maiúsculas. Mas aqui talvez eu me permita finalmente mostrar um pouco de imaginação e criatividade e, em vez de apenas "todos", escreverei "dap todos" ,'H1'.d("","dap todos")
Uau. Agora, nosso aplicativo pode ser considerado completo e o tutorial realizado (se você ler honestamente essas linhas).Em conclusão
Talvez, durante a leitura, você tenha tido a impressão de que o programa dap é escrito por tentativa e erro - tudo isso é "vamos ver o que aconteceu", "parece funcionar, mas há uma nuance", etc. Este não é realmente o caso.
Todas essas nuances são bastante óbvias e previsíveis ao escrever código. Mas pensei que seria útil mostrar pelo exemplo dessas nuances por que essa ou aquela decisão está presente nas regras e por que é feita dessa maneira e não de outra maneira.Faça, como eles dizem, perguntas.