Você já se perguntou como as estruturas funcionam? O autor do material, cuja tradução publicamos hoje, diz que, quando ele, há muitos anos, depois de estudar o
jQuery , encontrou o
Angular.js , o que viu parecia muito complicado e incompreensível para ele. Então o Vue.js apareceu e, lidando com essa estrutura, ele foi inspirado a escrever seu próprio sistema de
ligação de dados bidirecional. Tais experimentos contribuem para o desenvolvimento profissional do programador. Este artigo é destinado a quem deseja expandir seu próprio conhecimento no campo de tecnologias nas quais as estruturas JS modernas se baseiam. Em particular, ele se concentrará em como escrever o núcleo de sua própria estrutura que suporta atributos personalizados de elementos HTML, reatividade e ligação de dados bidirecional.

Sobre o sistema de reatividade
Será bom se, desde o início, descobrirmos como os sistemas de reatividade das estruturas modernas funcionam. De fato, tudo aqui é bastante simples. Por exemplo, o Vue.js, ao declarar um novo componente, faz
proxies de cada propriedade (getters e setters) usando o padrão de design "
proxy ".
Como resultado, a estrutura poderá detectar todo fato de que o valor muda, executado tanto a partir do código quanto da interface do usuário.
Padrão de proxy
O padrão de proxy é baseado na sobrecarga de mecanismos de acesso a um objeto. É semelhante à maneira como as pessoas trabalham com suas contas bancárias.
Por exemplo, ninguém pode trabalhar diretamente com sua conta, alterando seu saldo de acordo com suas necessidades. Para realizar qualquer ação com a conta, você precisa entrar em contato com alguém que tenha permissão para trabalhar com ele, ou seja, no banco. O banco atua como um proxy entre o titular da conta e a própria conta.
var account = { balance: 5000 } var bank = new Proxy(account, { get: function (target, prop) { return 9000000; } }); console.log(account.balance);
Neste exemplo, ao usar o objeto do
bank
para acessar o saldo da conta representado pelo objeto da
account
, a função getter é sobrecarregada, o que leva ao fato de que, como resultado de uma solicitação, o valor 9.000.000 sempre é retornado, em vez do valor da propriedade real, mesmo se Esta propriedade não existe.
E aqui está um exemplo de sobrecarga de uma função setter.
var bank = new Proxy(account, { set: function (target, prop, value) { // 0 return Reflect.set(target, prop, 0); } }); account.balance = 5800; console.log(account.balance); // 5,800 bank.balance = 5400; console.log(account.balance); // 0 ( )
Aqui, sobrecarregando a função
set
, você pode controlar seu comportamento. Por exemplo, você pode alterar o valor que deseja gravar em uma propriedade, gravar dados em outra propriedade ou simplesmente não fazer nada.
Exemplo de sistema de reatividade
Agora que descobrimos o padrão de proxy, começaremos a desenvolver nossa própria estrutura JS.
Para não complicar, usaremos uma sintaxe muito semelhante à usada no Angular.js. Como resultado, a declaração do controlador e a ligação dos elementos do modelo às propriedades do controlador parecerão simples e claras.
Aqui está o código do modelo.
<div ng-controller="InputController"> <input ng-bind="message"/> <input ng-bind="message"/> </div> <script type="javascript"> function InputController () { this.message = 'Hello World!'; } angular.controller('InputController', InputController); </script>
Primeiro você precisa declarar um controlador com propriedades. Em seguida, use este controlador no modelo e, finalmente, use o atributo
ng-bind
para estabelecer uma ligação de dados bidirecional para o valor do elemento.
Analisando um Modelo e Instanciando um Controlador
Para termos certas propriedades às quais os dados podem ser anexados, precisamos de um local (controlador) onde essas propriedades possam ser declaradas. Portanto, é necessário descrever o controlador e incluí-lo na estrutura.
No processo de trabalho com controladores, a estrutura procurará elementos que possuam atributos de
ng-controller
. Se o que você puder encontrar corresponder a um dos controladores declarados, a estrutura criará uma nova instância desse controlador. Essa instância do controlador é responsável apenas por esse fragmento específico do modelo.
var controllers = {}; var addController = function (name, constructor) { // controllers[name] = { factory: constructor, instances: [] }; // , var element = document.querySelector('[ng-controller=' + name + ']'); if (!element){ return; // , } // var ctrl = new controllers[name].factory; controllers[name].instances.push(ctrl); // ..... }; addController('InputController', InputController);
Abaixo está a declaração de um
controllers
variável "caseiro". Observe que o objeto
controllers
contém todos os controladores declarados na estrutura chamando
addController
.
var controllers = { InputController: { factory: function InputController(){ this.message = "Hello World!"; }, instances: [ {message: "Hello World"} ] } };
Cada controlador possui uma função de
factory
, para que, se necessário, você possa criar uma instância de um novo controlador. Além disso, a estrutura armazena, na propriedade
instances
, todas as instâncias do controlador do mesmo tipo usadas no modelo.
Procure itens envolvidos na ligação de dados
No momento, temos uma instância do controlador e um fragmento do modelo usando esse controlador. Nosso próximo passo será procurar elementos com dados que você precisa vincular às propriedades do controlador.
var bindings = {}; // : element DOM, Array.prototype.slice.call(element.querySelectorAll('[ng-bind]')) .map(function (element) { var boundValue = element.getAttribute('ng-bind'); if(!bindings[boundValue]) { bindings[boundValue] = { boundValue: boundValue, elements: [] } } bindings[boundValue].elements.push(element); });
A organização do armazenamento de todas as ligações de objetos usando uma
tabela de hash é mostrada aqui. A variável considerada contém todas as propriedades para ligação, com seus valores atuais e todos os elementos DOM vinculados a uma propriedade específica.
Aqui está a aparência de nossa versão da variável
bindings
:
var bindings = { message: { // : // controllers.InputController.instances[0].message boundValue: 'Hello World', // HTML- ( ng-bind="message") elements: [ Object { ... }, Object { ... } ] } };
Ligação bilateral das propriedades do controlador
Depois que a estrutura concluiu a preparação preliminar, chegou a hora de uma coisa interessante: ligação de dados bidirecional. O significado disso é o seguinte. Em primeiro lugar, as propriedades do controlador precisam ser vinculadas aos elementos DOM, o que permitirá que sejam atualizados quando os valores das propriedades mudarem do código e, em segundo lugar, os elementos DOM também devem ser vinculados às propriedades do controlador. Devido a isso, quando o usuário age sobre esses elementos, isso leva a uma alteração nas propriedades do controlador. E se vários elementos HTML estiverem anexados à propriedade, isso também levará ao fato de que seu estado também será atualizado.
Detectando alterações feitas no código usando um proxy
Como mencionado acima, o Vue.js agrupa os componentes em um proxy para detectar alterações de propriedade. Faremos o mesmo por proxy apenas o setter para as propriedades do controlador envolvidas na ligação de dados:
// : ctrl - var proxy = new Proxy(ctrl, { set: function (target, prop, value) { var bind = bindings[prop]; if(bind) { // DOM, bind.elements.forEach(function (element) { element.value = value; element.setAttribute('value', value); }); } return Reflect.set(target, prop, value); } });
Como resultado, acontece que quando o valor é gravado na propriedade vinculada, o proxy detecta todos os elementos vinculados a essa propriedade e passa a eles um novo valor.
Neste exemplo, suportamos apenas a ligação de elementos de
input
, pois apenas o atributo
value
é definido aqui.
Resposta a eventos de elemento
Agora, apenas precisamos fornecer uma resposta do sistema às ações do usuário. Para fazer isso, leve em consideração o fato de que os elementos DOM geram eventos quando detectam alterações em seus valores.
Organizamos a escuta desses eventos e a gravação nas propriedades associadas aos elementos novos dados obtidos dos eventos. Todos os outros elementos vinculados à mesma propriedade serão, graças ao proxy, atualizados automaticamente.
Object.keys(bindings).forEach(function (boundValue) { var bind = bindings[boundValue]; // bind.elements.forEach(function (element) { element.addEventListener('input', function (event) { proxy[bind.boundValue] = event.target.value; // , }); }) });
Sumário
Como resultado, reunindo tudo o que discutimos aqui, você obterá seu próprio sistema que implementa os mecanismos básicos das estruturas JS modernas.
Aqui está um exemplo de trabalho da implementação desse sistema. Esperamos que este material ajude todos a entender melhor como as modernas ferramentas de desenvolvimento da web funcionam.
Caros leitores! Se você usa profissionalmente estruturas JS modernas, conte-nos como você começou o estudo e sobre o que o ajudou a entendê-las.
