¿Alguna vez te has preguntado cómo funcionan los frameworks? El autor del material, cuya traducción publicamos hoy, dice que cuando, hace muchos años, después de estudiar
jQuery , se
encontró con
Angular.js , lo que vio le pareció muy complicado e incomprensible. Luego apareció Vue.js, y al tratar con este marco, se inspiró para escribir su propio sistema de
enlace de datos bidireccional. Tales experimentos contribuyen al desarrollo profesional del programador. Este artículo está destinado a aquellos que desean expandir su propio conocimiento en el campo de las tecnologías en las que se basan los marcos JS modernos. En particular, se centrará en cómo escribir el núcleo de su propio marco que admite atributos personalizados de elementos HTML, reactividad y enlace de datos bidireccional.

Sobre el sistema de reactividad
Será bueno si, al principio, descubrimos cómo funcionan los sistemas de reactividad de los marcos modernos. De hecho, todo aquí es bastante simple. Por ejemplo, Vue.js, al declarar un nuevo componente,
representa cada propiedad (getters y setters) utilizando el patrón de diseño
"proxy ".
Como resultado, el marco podrá detectar cada hecho de que el valor cambie, ejecutado tanto desde el código como desde la interfaz de usuario.
Patrón de proxy
El patrón proxy se basa en sobrecargar los mecanismos de acceso a un objeto. Esto es similar a cómo las personas trabajan con sus cuentas bancarias.
Por ejemplo, nadie puede trabajar directamente con su cuenta, cambiando su saldo de acuerdo con sus necesidades. Para llevar a cabo cualquier acción con la cuenta, debe comunicarse con alguien que tenga permiso para trabajar con él, es decir, con el banco. El banco actúa como representante entre el titular de la cuenta y la cuenta misma.
var account = { balance: 5000 } var bank = new Proxy(account, { get: function (target, prop) { return 9000000; } }); console.log(account.balance);
En este ejemplo, cuando se utiliza el objeto
bank
para acceder al saldo de la cuenta representado por el objeto de la
account
, la función getter se sobrecarga, lo que lleva al hecho de que, como resultado de dicha solicitud, siempre se devuelve el valor 9,000,000, en lugar del valor de la propiedad real, incluso si Esta propiedad no existe.
Y aquí hay un ejemplo de sobrecarga de una función 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 ( )
Aquí, al sobrecargar la función
set
, puede controlar su comportamiento. Por ejemplo, puede cambiar el valor que desea escribir en una propiedad, escribir datos en otra propiedad o simplemente no hacer nada.
Ejemplo de sistema de reactividad
Ahora que hemos descubierto el patrón proxy, comenzaremos a desarrollar nuestro propio marco JS.
Para no complicarlo, utilizaremos una sintaxis muy similar a la utilizada en Angular.js. Como resultado, la declaración del controlador y la vinculación de los elementos de la plantilla a las propiedades del controlador se verán simples y claras.
Aquí está el código de la plantilla.
<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>
Primero debe declarar un controlador con propiedades. Luego, use este controlador en la plantilla y, finalmente, use el atributo
ng-bind
para establecer el enlace de datos bidireccional para el valor del elemento.
Analizar una plantilla e instanciar un controlador
Para que podamos tener ciertas propiedades a las que se pueden adjuntar datos, necesitamos un lugar (controlador) donde se puedan declarar estas propiedades. Por lo tanto, es necesario describir el controlador e incluirlo en el marco.
En el proceso de trabajar con controladores, el marco buscará elementos que tengan atributos
ng-controller
. Si lo que puede encontrar coincide con uno de los controladores declarados, el marco creará una nueva instancia de este controlador. Esta instancia de controlador es responsable solo de este fragmento particular de la plantilla.
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);
A continuación se muestra la declaración de un
controllers
variable "casero". Tenga en cuenta que el objeto de
controllers
contiene todos los controladores declarados en el marco llamando a
addController
.
var controllers = { InputController: { factory: function InputController(){ this.message = "Hello World!"; }, instances: [ {message: "Hello World"} ] } };
Cada controlador tiene una función de
factory
, esto se hace para que, si es necesario, pueda crear una instancia de un nuevo controlador. Además, el marco almacena, en la propiedad de
instances
, todas las instancias del controlador del mismo tipo utilizado en la plantilla.
Buscar elementos involucrados en el enlace de datos
Por el momento, tenemos una instancia del controlador y un fragmento de la plantilla que utiliza este controlador. Nuestro próximo paso será buscar elementos con datos que necesite vincular a las propiedades del 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); });
Aquí
se muestra la organización del almacenamiento de todos los enlaces de objetos utilizando una
tabla hash . La variable considerada contiene todas las propiedades para el enlace, con sus valores actuales, y todos los elementos DOM vinculados a una propiedad específica.
Así es como se ve nuestra versión de la variable de
bindings
:
var bindings = { message: { // : // controllers.InputController.instances[0].message boundValue: 'Hello World', // HTML- ( ng-bind="message") elements: [ Object { ... }, Object { ... } ] } };
Enlace bilateral de las propiedades del controlador
Una vez que el marco ha completado la preparación preliminar, llega el momento de una cosa interesante: el enlace de datos bidireccional. El significado de esto es el siguiente. En primer lugar, las propiedades del controlador deben estar vinculadas a los elementos DOM, lo que les permitirá actualizarse cuando los valores de las propiedades cambien del código, y en segundo lugar, los elementos DOM también deben estar vinculados a las propiedades del controlador. Debido a esto, cuando el usuario actúa sobre dichos elementos, esto lleva a un cambio en las propiedades del controlador. Y si se adjuntan varios elementos HTML a la propiedad, esto también lleva al hecho de que su estado también se actualiza.
Detectar cambios realizados desde el código usando un proxy
Como se mencionó anteriormente, Vue.js envuelve los componentes en un proxy para detectar cambios de propiedad. Haremos lo mismo representando solo el setter para las propiedades del controlador involucradas en el enlace de datos:
// : 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, resulta que cuando el valor se escribe en la propiedad vinculada, el proxy detectará todos los elementos vinculados a esta propiedad y les pasará un nuevo valor.
En este ejemplo, solo admitimos el enlace de elementos de
input
, ya que aquí solo se establece el atributo de
value
.
Respuesta a eventos de elementos
Ahora solo tenemos que proporcionar una respuesta del sistema a las acciones del usuario. Para hacer esto, tenga en cuenta el hecho de que los elementos DOM generan eventos cuando detectan cambios en sus valores.
Organizamos escuchar estos eventos y grabar en las propiedades asociadas con los elementos nuevos datos obtenidos de los eventos. Todos los demás elementos vinculados a la misma propiedad, gracias al proxy, se actualizarán automáticamente.
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; // , }); }) });
Resumen
Como resultado, al reunir todo lo que discutimos aquí, obtendrá su propio sistema que implementa los mecanismos básicos de los marcos JS modernos.
Aquí hay un ejemplo práctico de la implementación de dicho sistema. Esperamos que este material ayude a todos a comprender mejor cómo funcionan las herramientas modernas de desarrollo web.
Estimados lectores! Si utiliza profesionalmente marcos JS modernos, cuéntenos cómo comenzó su estudio y qué le ayudó a comprenderlos.
