No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Implementando effect, track y trigger en MiniVue

12/37
Recursos

Aportes 41

Preguntas 0

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

Reto solucionado!!!

  • HTML aplicando directiva:
<img p-bind="src:image">
  • image en data
 data() {
                return {
                    message: "Hola Platzi",
                    image: "https://www.manejandodatos.es/wp-content/uploads/2018/02/vueJS.png"
                };
            }                	
  • Recorrer elementos con directiva p-bind ( en mount() ):
document.querySelectorAll('*[p-bind]').forEach(Element => {
            let Atributo = Element.getAttribute('p-bind').split(':')[0];
            let Valor = Element.getAttribute('p-bind').split(':')[1];
            
            this.pBind(Element, this.$data, Atributo, Valor)
        })
  • M茅todo pBind() :
pBind(element, origin, attribute, value) {
        element.setAttribute(attribute, Reflect.get(origin, value));
    }

Wow鈥 me acabo de aventar un c贸digo m谩s similar a Vue, en mi caso lo quise hacer con el formato de Vue:

<img p-bind:src="image" p-bind:id="imageId">

En Vue el atributo se pone despu茅s de p-bind, adem谩s, en Vue puedes tener varios 鈥渂indeos鈥, por lo que mi data ser铆a esta:

data() {
    return {
        message: "Hola Vue", 
        image: "https://via.placeholder.com/600/771796",
        imageId: "myId"
    }
},

Para hacer esto funcionar hice una busqueda de todos elementos y filtr茅 por los que tuvieran v.bind, recorr铆 cada uno y tambi茅n recorr铆 cada atributo de dicho elemento para ver cu谩les hab铆a que bindear, por lo que a pBind le paso los datos m谩s el atributo a bindear:

Array.from(document.querySelectorAll("*"))
.filter(el => {
    // Metemos en un array sus atributos y los recorrermos
    return [...el.attributes].some(
        attr => attr.name.startsWith('p-bind:')
    )
})
.forEach(el => {
    [...el.attributes].forEach(attribute => {

        const name = attribute.value;
        const attr = attribute.name.split(":").pop();

        this.pBind(el, this.$data, name, attr);

    })
});

En pBind me aprovecho del reflect que ya ten铆amos para establecer dicho atributo (puede ser CUALQUIER tributo), yo hice la prueba con src y con id``` (conclass`` no funciona porque hay que hacer otras movidas xD):

pBind(el, target, name, attr) {

    Reflect.set(el, attr, Reflect.get(target, name))

}

Para hacerlo reactivo simplemente en el set hago la misma b煤squeda pero esta vez filtrando por el atributo que necesito:

Array.from(document.querySelectorAll("*"))
.filter(el => {
    // Metemos en un array sus atributos y los recorrermos
    return [...el.attributes].some(
        attr => attr.name.startsWith('p-bind:') && attr.value == name
    )
})
.forEach(el => {
    console.log(el);
    [...el.attributes].forEach(attribute => {

        const value = attribute.value;
        const attr = attribute.name.split(":").pop();
        if(name == value)
            this.pBind(el, target, name, attr);

    })
});

隆Y funciona! Tiene algunos cu谩ntos detalles pero funciona xD
.
Este es el c贸digo completo funcionando:

https://github.com/RetaxMaster/mini-vue/tree/b6d8da9e9c081f9e761970398b03759dfa852002

1. Attributo en el tag

<img p-bind="src:foto" alt="imagen">

2. Dentro de mount

document.querySelectorAll('*[p-bind]').forEach(el => {
  const attribute = el.getAttribute('p-bind');
  this.pBind(el, this.$data, attribute)
})

3. codigo de la directiva

pBind(el, target, attr) {
  const [attribute, key] = attr.split(':')
 el.setAttribute(attribute, Reflect.get(target, key))
}

Para resolver el reto

En el html agregu茅

<input type="text" p-model="image" />
<br>
<div>
    <img p-bind="src:image">
</div>

en el archivo js, en la funci贸n mount agregu茅

document.querySelectorAll('*[p-bind]').forEach(el => {
  const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g);
  this.pBind(el, this.$data, name, attr);
});

Luego agregue la funci贸n pBind

pBind(el, target, name, attr) {
  el.setAttribute(attr, Reflect.get(target, name));
}

luego para observar los cambios de la informaci贸n que se ingresa el input, agregue en la funcion track

document.querySelectorAll('*[p-bind]').forEach(el => {
  const [attr, nameBind] = el.getAttribute('p-bind').match(/(\w+)/g);
  this.pBind(el, target, nameBind, attr);
});

Mi explicac贸n sobre esta clase:
Generamos una variable dependencias es cual solo almacena el nombre de los objetos que definimos en nuestra data() y su efecto el cual en este caso era nuestra funcion p-text (cambiar el innerHTML).

En la funcion GET de nuestro proxy implementamos la llamada a la funci贸n lo que esto quiere decir es:
Cuando alguien quiera acceder al valor de X tambien ejecuta el efecto que tenga asociado dentro de deps y si no tiene asociado nada le creamos un efecto. En res煤men es eso

Agregue un nuevo input para poder editar el src de mi componente img

<input type="text" p-model="url">
<img p-bind="src:url" />

Edite la data inicial para tener una imagen por defecto

            const app = createApp({
                data() {
                    return {
                        message: "Hello Vue!",
                        url: "https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png"
                    }
                }
            });

dentro del mount agregue esto para darle un valor inicial al atributo

        document.querySelectorAll("*[p-bind]").forEach(el => {
            const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g);
            this.pBind(el, this.$data, name, attr)
        });

el pBind le da el valor al atributo

    pBind(el, target, name, attr) {
        el.setAttribute(attr, Reflect.get(target, name));
    }

Y agregue este snippet dentro del effect handler, con lo que el valor del atributo se actualiza siempre que la data cambia de valor

                document.querySelectorAll(`*[p-bind*=${name}]`).forEach((el) => {
                    const [attr, name] = el.getAttribute("p-bind").match(/(\w+)/g);
                    this.pBind(el, target, name, attr);
                });```

IMG

MOUNT

pBind

Reto solucionado:

En el HTML tenemos:

<img p-bind="src:image" />

Y en mi data:

data() {
  return {
	message: "Hola Platzi!",
        image: 'https://pbs.twimg.com/media/EiNmaOCXsAI2sp-.png'
  };
}

Para la implementaci贸n de tal como lo menciono mi compa帽ero Juan, salvo la diferencia fue que a帽ad铆 en el effect que se ejecute el pBind tmb

track (target, name) {
    if (!this.deps.has(name)) {
      const effect = () => {
        document.querySelectorAll([`*[p-text=${name}]`]).forEach(el => {
          this.pText(el, target, name)
        })
        document.querySelectorAll('*[p-bind]').forEach(el => {
          const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g)
          this.pBind(el, this.$data, name, attr)
        })
      }
      this.deps.set(name, effect)
    }
  }

Con esto si el model y el bind tienen un atributo similar la reactividad funciona

mi aporte al reto

img

<img p-bind="src:image" />

data

data(){
return {
image: "https://cdn.icon-icons.com/icons2/2107/PNG/512/file_type_vue_icon_130078.png",
}
}

mount

document.querySelectorAll("*[p-bind]").forEach((el) => {
      const name = el.getAttribute("p-bind");

      this.pBind(el, this.$data, name.split(":"));
    });

pBind

pBind(el, target, name) {
    el.setAttribute(name[0], Reflect.get(target, name[1]));
  }
  • Sume un input para editar la imagen en el html
  • Agregue un listener a cada atributo de tipo p-bind sumando el atributo src para el selector dentro del dentro del track
  • Agregue un listener a cada atributo de tipo p-bind dentro del mount llamando al pBind

Html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hola Mundo!</title>
</head>

<body>
    <div id="app">
        <span p-text="message"></span>
        <br>
        <input type="text" p-model="message">

        <br>
        <input type="text" p-model="image">
        <br>
        <img p-bind="src:image">
    </div>

    <script src="./platzi.js"></script>
    <script>
        const { createApp } = Platzi;

        const app = createApp({
            data() {
                return {
                    message: 'Hola Platzi!',
                    image: 'https://via.placeholder.com/600/771796'
                };
            }
        });

        app.mount();
    </script>
</body>

</html>

Javascript

class PlatziReactive {
    // Dependencies
    deps = new Map();

    constructor(options) {
        const self = this;

        // Origin
        this.origin = options.data();

        // Target
        this.$data = new Proxy(this.origin, {
            get(target, name) {
                if (!(Reflect.has(target, name))) {
                    console.warn('La propiedad', name, 'no existe');
                    return '';
                }

                self.track(target, name);
                return Reflect.get(target, name);
            },
            set(target, name, value) {
                Reflect.set(target, name, value);
                self.trigger(name);
            }
        });
    }

    track(target, name) {
        if (!(this.deps.has(name))) {
            const effect = () => {
                document.querySelectorAll(`*[p-text=${name}]`).forEach(e => {
                    this.pText(e, target, name);
                });

                document.querySelectorAll(`*[p-bind='src:${name}']`).forEach(e => {
                    this.pBind(e, target, name, 'src');
                });
            };

            this.deps.set(name, effect);
        }
    }

    trigger(name) {
        const effect = this.deps.get(name);
        effect();
    }

    mount() {
        document.querySelectorAll('*[p-text]').forEach(e => {
            this.pText(e, this.$data, e.getAttribute('p-text'));
        });

        document.querySelectorAll('*[p-model]').forEach(e => {
            const name = e.getAttribute('p-model');
            this.pModel(e, this.$data, name);

            e.addEventListener('input', () => {
                Reflect.set(this.$data, name, e.value);
            });
        });

        document.querySelectorAll('*[p-bind]').forEach(e => {
            const [attr, name] = e.getAttribute('p-bind').match(/(\w+)/g);
            this.pBind(e, this.$data, name, attr);

            e.addEventListener('input', () => {
                Reflect.set(this.$data, name, e.value);
            });
        });
    }

    pText(e, target, name) {
        e.innerText = Reflect.get(target, name);
    }

    pModel(e, target, name) {
        e.value = Reflect.get(target, name);
    }

    pBind(e, target, name, attr) {
        e.setAttribute(attr, Reflect.get(target, name));
    }
}

const Platzi = {
    createApp(options) {
        return new PlatziReactive(options);
    }
};
As铆 lo resolvi: `class PlatziReactive {` ` deps = new Map();` ` constructor(options) { ``this``.origin = options.data()` ` const self = ``this``;` ` ``this``.$data = new Proxy(``this``.origin, { get(target, name) { if (Reflect.has(target, name)) { self.track(target, name) return Reflect.get(target, name) } console.warn('la propiedad ', name, ' no existe'); }, set(target, name, value) { Reflect.set(target, name, value) self.trigger(name); } }) } track(target, name) { if (!``this```.deps.has(name)) { const effect = () => { document.querySelectorAll(`*[p-text=${name}]`).forEach(el => { ```this```.pText(el, target, name) }); document.querySelectorAll(`*[p-model=${name}]`).forEach(el => { const name = el.getAttribute('p-model'); ```this``.pModel(el, ``this``.$data, name); }); document.querySelectorAll('*[p-bind=[a-zA-Z]+:${name}]').forEach(el => { const [attr, name] = el.getAttribute("p-bind").split(":") ``this``.pBind(el, ``this``.$data, attr, name); }); } ``this``.deps.set(name, effect); } } trigger(name) { const effect = ``this``.deps.get(name); effect() } pModel(el, target, name) { el.value = Reflect.get(target, name); } pText(el, target, name) { el.innerHTML = Reflect.get(target, name); } pBind(el, target, attr, name) { console.log(Reflect.get(target, name)) el.setAttribute(attr, Reflect.get(target, name)); } mount() { document.querySelectorAll('*[p-text]').forEach(el => { ``this``.pText(el, ``this``.$data, el.getAttribute('p-text')); });` ` document.querySelectorAll('*[p-model]').forEach(el => { const name = el.getAttribute('p-model'); ``this``.pModel(el, ``this``.$data, name); el.addEventListener('input', () => { Reflect.set(``this``.$data, name, el.value) }) }); document.querySelectorAll('*[p-bind]').forEach(el => { const [attr, name] = el.getAttribute("p-bind").split(":") ``this``.pBind(el, ``this``.$data, attr, name); }) }}` `var Platzi = { createApp(options) { return new PlatziReactive(options); }}`
Resumen del reto: * index.html\ data() { 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 return { 聽 聽 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 聽 聽 message: "Mensaje...", 聽 聽 聽 聽 password: "Contrase帽a...", 聽 聽聽 聽 聽 聽 image: 'https://via.placeholder.com/600/771796'聽 聽 } 聽}```js data() { return { message: "Mensaje...", password: "Contrase帽a...", image: 'https://via.placeholder.com/600/771796' } } ``` * clase.js 1. Array para im谩genes aleatorias:this.imagesArray = \[聽 聽 'https://via.placeholder.com/600/771796',聽 聽 'https://via.placeholder.com/600/456123',聽 聽 'https://via.placeholder.com/600/345765',聽 聽 'https://via.placeholder.com/600/456623',聽 聽 'https://via.placeholder.com/600/987834',聽 聽 'https://via.placeholder.com/600/765345',聽 聽 'https://via.placeholder.com/600/987734',聽 聽 'https://via.placeholder.com/600/098089',聽 聽 'https://via.placeholder.com/600/454565',聽 聽 'https://via.placeholder.com/600/123321',]```js this.imagesArray = [ 'https://via.placeholder.com/600/771796', 'https://via.placeholder.com/600/456123', 'https://via.placeholder.com/600/345765', 'https://via.placeholder.com/600/456623', 'https://via.placeholder.com/600/987834', 'https://via.placeholder.com/600/765345', 'https://via.placeholder.com/600/987734', 'https://via.placeholder.com/600/098089', 'https://via.placeholder.com/600/454565', 'https://via.placeholder.com/600/123321', ] ``` 2. Funcion para obtener img aleatoria dentro de un vector:getRandomImg(){ 聽 聽 聽 聽聽 聽 const index = Math.floor(Math.random() \* 10); 聽 聽 聽 聽聽 聽 console.log('index: ' + index); 聽 聽 聽 聽聽 聽 return this.imagesArray\[index]; 聽 聽}```js getRandomImg(){ const index = Math.floor(Math.random() * 10); console.log('index: ' + index); return this.imagesArray[index]; } ``` 3. Dentro del track para el pmodel, agregue esto para hacer dinamica la carga de la imagen al escribir en los inputs:document.querySelectorAll(\\\*\\\[p-bind]).forEach( elemento => { 聽 聽 聽 聽 聽 聽聽 聽 this.origienDatos\['image'] = this.getRandomImg(); 聽 聽 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 this.setImgSource(elemento, originData, 'image', "src"); 聽 聽 聽 聽 聽 聽 聽 聽});this.dependencias.set(propiedaNombre+'-images', efectoAttrSrc);```js document.querySelectorAll(\*\[p-bind]).forEach( elemento => { this.origienDatos['image'] = this.getRandomImg(); this.setImgSource(elemento, originData, 'image', "src"); }); this.dependencias.set(propiedaNombre+'-images', efectoAttrSrc); ``` 4. Efecto: // p-bind="src:image" const efectoAttrSrc = () => { 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 document.querySelectorAll(\\\*\\\[p-bind="src:=${propiedaNombre}"]).forEach( elemento => { 聽 聽 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 聽 聽 this.setImgSource(elemento, originData, propiedaNombre, "src"); 聽 聽 聽 聽 聽 聽 聽 聽聽 聽 }); 聽 聽 聽 聽 聽 聽}```js const efectoAttrSrc = () => { document.querySelectorAll(\*\[p-bind="src:=${propiedaNombre}"]).forEach( elemento => { this.setImgSource(elemento, originData, propiedaNombre, "src"); }); } ``` 5. Trigger: Disparador.聽 聽 const efecto\_src = 聽this.dependencias.get(propiedaNombre+'-images'); efecto\_src(); ```js const efecto_src = this.dependencias.get(propiedaNombre+'-images'); efecto_src(); ``` 6. En el mount:document.querySelectorAll('\*\[p-bind]').forEach((elemento) => { 聽 聽 聽 聽 聽 聽聽 聽 const \[atributo, nombre] = elemento.getAttribute('p-bind').match(/(\w+)/g); 聽 聽 聽 聽 聽 聽聽 聽 this.setImgSource(elemento, this.$dataProxy, nombre, atributo); 聽 聽 聽 聽})```js document.querySelectorAll('*[p-bind]').forEach((elemento) => { const [atributo, nombre] = elemento.getAttribute('p-bind').match(/(\w+)/g); this.setImgSource(elemento, this.$dataProxy, nombre, atributo); }) ``` 7. Funcion de seteo para cambio del src:setImgSource(elemento, originData, propiedaNombre, atributo){ 聽 聽 聽 聽聽 聽 elemento.setAttribute(atributo, Reflect.get(originData, propiedaNombre)); 聽 聽}```js setImgSource(elemento, originData, propiedaNombre, atributo){ elemento.setAttribute(atributo, Reflect.get(originData, propiedaNombre)); } ```
Resumen del reto: 1\. index.html \ data() {聽 聽 聽 聽 聽 聽 聽 聽 return {聽 聽 聽 聽 聽 聽 聽 聽 聽 聽 message: "Mensaje...", password: "Contrase帽a...",聽 聽 image: 'https://via.placeholder.com/600/771796'聽 聽 聽 聽 聽 聽 聽 聽 }聽 } 2\. clase.js => this.imagesArray = \[聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/771796',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/456123',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/345765',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/456623',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/987834',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/765345',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/987734',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/098089',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/454565',聽 聽 聽 聽 聽 聽 'https://via.placeholder.com/600/123321',聽 聽 聽 ] Dentro del track para el pmodel, agregue esto para hacer dinamica la carga de la imagen al escribir en los inputs: document.querySelectorAll(`\*\[p-bind]`).forEach( elemento => {聽 聽 聽 聽 聽 聽 this.origienDatos\['image'] = this.getRandomImg();聽 聽 聽 聽 聽 聽 聽 聽 聽 聽 this.setImgSource(elemento, originData, 'image', "src");聽 聽 聽 聽 聽 聽 聽 聽 }); Efecto: // p-bind="src:image"聽 聽 聽 聽 聽 聽 const efectoAttrSrc = () => {聽 聽 聽 聽 聽 聽 聽 聽 document.querySelectorAll(`\*\[p-bind="src:=${propiedaNombre}"]`).forEach( elemento => {聽 聽 聽 聽 聽 聽 聽 聽 聽 聽 this.setImgSource(elemento, originData, propiedaNombre, "src");聽 聽 聽 聽 聽 聽 聽 聽 });聽 聽 聽 聽 聽 聽 } Trigger: const efecto\_src = 聽this.dependencias.get(propiedaNombre+'-images'); efecto\_src(); En el mount: document.querySelectorAll('\*\[p-bind]').forEach((elemento) => {聽 聽 聽 聽 聽 聽 const \[atributo, nombre] = elemento.getAttribute('p-bind').match(/(\w+)/g);聽 聽 聽 聽 聽 聽 //console.log('atributo: ' + atributo + ' / nombre: ' + nombre);聽 聽 聽 聽 聽 聽 this.setImgSource(elemento, this.$dataProxy, nombre, atributo);聽 聽 聽 聽 }) Funcion de seteo para cambio del src: setImgSource(elemento, originData, propiedaNombre, atributo){聽 聽 聽 聽 elemento.setAttribute(atributo, Reflect.get(originData, propiedaNombre));聽 聽 } Funcion para obtener img aleatoria dentro de un vector: getRandomImg(){聽 聽 聽 聽 const index = Math.floor(Math.random() \* 10);聽 聽 聽 聽 console.log('index: ' + index);聽 聽 聽 聽 return this.imagesArray\[index];聽 聽 } this.dependencias.set(propiedaNombre+'-images', efectoAttrSrc);

Solo como aporte a los c贸digos de los compa帽eros que lo publicaron, me es facinante poner aprueba el p-bind, entendiendo que se expande a muchas otras propiedades de etiquetas html

<span p-text="message" p-bind="title:message"></span>
<input p-model="message" p-bind="placeholder:holder" type="text">

Esta super entender c贸mo funciona la reactividad. Gracias Samuel por compartir tu conocimiento

Yo lo hice con la sintaxis de Vue, y funciona con cualquier v-bind (aunque en mi framework yo lo llamo a-bind)

La etiqueta img queda as铆:

<img a-bind:src="imagen" a-bind:width="ancho" />

El objeto data() queda as铆:

 data() {
          return {
            message: "Hola Ameth.js!",
            ancho: "300px",
            imagen:
              "https://assetsnffrgf-a.akamaihd.net/assets/m/2020162/univ/art/2020162_univ_lsr_lg.jpg",
          };
        },

En el mount obtengo un array con un listado de todos los elementos HTML, luego recorro todos sus atributos hasta filtrarlos por los que empiezen con a-bind. Una vez tenga esa lista, recorro nuevamente los atributos de esos elementos filtrados, luego obtengo el valor de la propiedad enlazada separando el nombre por 鈥:鈥 (dos puntos), y tambien el valor que tiene el atributo, y mando esos valores a la funci贸n aBind para que renderice.

As铆 queda el mount()

Y as铆 es la funci贸n aBind():

aBind (el, target, name, attr) {
    el.setAttribute(attr, Reflect.get(target, name))
  }

Soluci贸n del reto:

HTML con la directiva:

<img r-bind=鈥渟rc:image鈥 width=鈥360px鈥 height=鈥400px鈥>

Data:

data() {
	return {
            message: "Hola mundo con mi framework",
            image: "https://images.pexels.com/photos/11721770/pexels-photo-11721770.jpeg?auto=compress&cs=tinysrgb&h=650&w=940"
          };
},

Recorriendo los elementos r-bind en el mount

document.querySelectorAll("*[r-bind]").forEach((el) => {
	const [attr, name] = el.getAttribute(鈥r-bind鈥).match(/(\w+)/g);
	this.rBind(el, this.$data, name, attr);
});

Metodo rBind:

rBind(el, target, name, attr) {
	el.setAttribute(attr,Reflect.get(target,name))
}

Resultado

https://prnt.sc/txWv6cQxOrwT
Con la ayuda del maestro pude introducirme a las expresiones regulares al tratar de entender el c贸digo de ayuda que nos di贸.

No encontr茅 la forma de acerlo reactivo ante el input 馃槮

Aqu铆 mi soluci贸n al reto, donde podemos actualizar la img que queramos mostrar 馃槃

index.html
Agregu茅 el nuevo elemento m谩s el attr. correspondiente. Para probar la reactividad, dej茅 message.

    <div id="app">
      <span p-text="message"></span>
      <span></span>
      <br />
      <input type="text" p-model="message" />
      <br />
      <img p-bind="src:message" />
    </div>

      const app = createApp({
        data() {
          return {
            message: "Hola Vue!",
            image: "https://images.pexels.com/photos/9255553/pexels-photo-9255553.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
          };
        },
      });

platzi.js
A continuaci贸n dejo los m茅todos donde realic茅 los cambios necesarios para el reto.
Nota. Los split que utilizo sin ser acompa帽ados de alg煤n tipo de validaci贸n son por fines pr谩cticos. En otros escenarios hay que considerar que las cosas no siempre ser谩n como lo esperamos.

  track(target, name) {
    if (!this.deps.has(name)) {
      const effect = () => {
        document.querySelectorAll(`*[p-text=${name}]`).forEach((el) => {
          this.pText(el, target, name);
        });
        document.querySelectorAll(`*[p-bind]`).forEach((el) => {
          const [attr, nameVal] = el.getAttribute('p-bind').split(':');
          if (nameVal === name) {
            this.pBind(el, target, name, attr);
          }
        });
      };
      this.deps.set(name, effect);
    }
  }

  mount() {
    document.querySelectorAll("*[p-text]").forEach((el) => {
      this.pText(el, this.$data, el.getAttribute("p-text"));
    });

    document.querySelectorAll("*[p-model]").forEach((el) => {
      const name = el.getAttribute("p-model");
      this.pModel(el, this.$data, name);

      el.addEventListener("input", () => {
        Reflect.set(this.$data, name, el.value);
      });
    });

    document.querySelectorAll("*[p-bind]").forEach((el) => {
      const [attr, name] = el.getAttribute("p-bind").split(':');
      this.pBind(el, this.$data, name, attr);
    });
  }

  pBind(el, target, name, attr) {
    el.setAttribute(attr, Reflect.get(target, name));
  }

Esto fue lo que hice:

Implementando effect, track y trigger en MiniVue

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hola mundo en VueJS 3</title>
</head>
<body>
  <div id="app">
    <span p-text="message"></span>
    <br>
    <input type="text" name="" id="" p-model="message">
    <br>
    <img p-bind="src:image" width="400px">
  </div>

  <script src="platzi.js"></script>
  <script>
    const { createApp } = Platzi;

    const app = createApp({
      data() {
        return {
          message: 'Hola mundo desde Vue JS 3',
          image: 'https://static-cse.canva.com/blob/666309/bestfreestockphotos.jpg'
        }
      }
    })
    app.mount()
  </script>
</body>
</html>
class PlatziReactive {
  dependencies = new Map();

  constructor(options) {
    this.origen = options.data();

    const self = this;

    //* Destino
    this.$data = new Proxy(this.origen, {
      get(target, name) {
        if(Reflect.has(target, name)) {
          self.track(target, name);
          return Reflect.get(target, name);
        }
        console.warn(`$La propiedad {name} no existe`);
        return "";
      },

      set(target, name, value) {
        Reflect.set(target, name, value);
        self.trigger(name)
      }
    })
  }

  track(target, name) {
    if(!this.dependencies.has(name)) {
      const effect = () => {
        document.querySelectorAll(`*[p-text="${name}"]`).forEach(element => {
          this.pText(element, target, name);
        })
      }
      this.dependencies.set(name, effect);
    }
  }

  trigger(name) {
    const effect = this.dependencies.get(name);
    effect();
  }

  mount() {
    document.querySelectorAll("*[p-text]").forEach(element => {
      this.pText(element, this.$data, element.getAttribute("p-text"));
    })

    document.querySelectorAll("*[p-model]").forEach(element => {
      const name = element.getAttribute("p-model");
      this.pModel(element, this.$data, name);

      element.addEventListener("input", () => {
        Reflect.set(this.$data, name, element.value);
      })
    })

    document.querySelectorAll("*[p-bind]").forEach(element => {
      const [attr, name] = element.getAttribute("p-bind").match(/(\w+)/g);
      this.pBind(element, this.$data, name, attr);
    })
  }

  pText(element, target, name) {
    element.innerText = Reflect.get(target, name);
  }

  pModel(element, target, name) {
    element.value = target[name];
  }

  pBind(element, target, name) {
    element.src = target[name];
  }
}

var Platzi = {
  createApp(options) {
    return new PlatziReactive(options);
  }
}

Retooo!!!

  • html
 <img p-bind='src:image' >
 <input type="text" p-model="image"> 
  • en el script
 const app = createApp({
            data(){
                return {
                    message: 'Hola vue',
                    image: 'https://via.placeholder.com/600/771792'
                }
            }
        });
  • en el mount ()
 document.querySelectorAll('*[p-bind]').forEach(el => {
      const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g);
      this.pBind(el, this.$data, name, attr);
    })
  • metodo pBing
 pBind(el, target, name, attr) {
    el.setAttribute(attr, Reflect.get(target, name) )
  }
  • en el track
track(target, name){
    if(!this.deps.has(name)){
      const effect = ()=> {
        document.querySelectorAll(`*[p-text=${name}]`).forEach( el => {
          this.pText(el, target, name);
        });
        document.querySelectorAll(`*[p-bind$=${name}]`).forEach(el => {
          const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g);
          this.pBind(el, this.$data, name, attr);
        })
      };
      this.deps.set(name, effect)
    }
  }

Para estandarizar los atributos, refactoric茅 un poco las funciones.

Cree las propiedades attributes y attFunctions para luego poder recorrer y ejecutar cada atributo

constructor(options) {
        this.origin = options.data();
        const self = this;
        this.attributes = ["d-text", "d-model", "d-bind"]
        this.attFunctions = { "d-text": this.dText, "d-model": this.dModel, "d-bind": this.dBind }
...

En vez de buscar los selectores por separado en el track, ahora hago un for que haga el match. No busca la palabra exacta, sino que busca las coincidencias que tengan la palabra.

track(target, name) {
        if (!this.deps.has(name)) {
            const effect = () => {
                for (let attribute of this.attributes) {
                    document.querySelectorAll(`*[${attribute}*=${name}]`).forEach(element => {
                        if(element.hasAttribute(attribute)){
                            this.attFunctions[attribute](element, target, element.getAttribute(attribute))
                        }
                    });
                }
            }
            this.deps.set(name, effect)
        }
    }

En el mount lo mismo, un for para lo gen茅rico y un if para lo espec铆fico

mount() {
        for (let attribute of this.attributes) {
            document.querySelectorAll(`*[${attribute}]`).forEach(element => {
                const name = element.getAttribute(attribute)
                this.attFunctions[attribute](element, this.$data, name);

                if (attribute === "d-model") {
                    element.addEventListener("input", () => {
                        Reflect.set(this.$data, name, element.value)
                    })
                }

            });


        }
    }

y para el bind, es hacer lo mismo que con los dem谩s.

dBind(element, target, value) {
        const [attribute, data] = value.split(":")
        value = Reflect.get(target, data)
        element.setAttribute(attribute, value)
    }

Ah铆 est谩 mi soluci贸n.
No la entiendo del todo. Luego tendr茅 que leerla varias veces para ver c贸mo lo logr茅 馃し鈥嶁檧锔

https://codepen.io/marinrmarco/pen/YzQvvwy

se agrega el m茅todo pBind

pBind(ele, target, attr, name) {
ele.setAttribute(attr, target[name]);
}

Mi soluci贸n

HTML

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <span p-text="message"></span>
      <br />
      <input type="text" p-model="message" />
      <br />
      <p>Aca va el reto de la imagen</p>
      <input type="text" p-model="image" style="width: 500px" />
      <br />
      <br />
      <img p-bind="src:image" alt="image" />
    </div>
    <script src="platzi.js"></script>
    <script>
      const { createApp } = Platzi;
      const app = createApp({
        data() {
          return {
            message: "Hola Platzi!",
            image: "https://via.placeholder.com/600/771796",
          };
        },
      });
      app.mount();
    </script>
  </body>
</html>

JS

class PlatziReactive {
  //Dependencias
  deps = new Map();
  constructor(options) {
    this.origen = options.data();
    const self = this;
    //Destino
    this.$data = new Proxy(this.origen, {
      get(target, name) {
        if (Reflect.has(target, name)) {
          self.track(target, name);
          return Reflect.get(target, name);
        }
        console.warn(`La propiedad ${name} no existe.`);
        return "";
      },
      set(target, name, value) {
        Reflect.set(target, name, value);
        self.trigger(name);
      },
    });
  }

  track(target, name) {
    if (!this.deps.has(name)) {
      const effect = () => {
        document.querySelectorAll(`*[p-text=${name}]`).forEach((el) => {
          this.pText(el, target, name);
        });

        document.querySelectorAll(`*[p-bind='src:${name}']`).forEach((el) => {
          this.pBind(el, target, `src:${name}`);
        });
      };
      this.deps.set(name, effect);
    }
  }

  trigger(name) {
    const effect = this.deps.get(name);
    effect();
  }

  mount() {
    document.querySelectorAll("*[p-text]").forEach((el) => {
      this.pText(el, this.$data, el.getAttribute("p-text"));
    });

    document.querySelectorAll("*[p-model]").forEach((el) => {
      const name = el.getAttribute("p-model");
      this.pModel(el, this.$data, name);
      el.addEventListener("input", () => {
        Reflect.set(this.$data, name, el.value);
      });
    });

    document.querySelectorAll("*[p-bind]").forEach((el) => {
      const name = el.getAttribute("p-bind");
      this.pBind(el, this.$data, el.getAttribute("p-bind"));
      el.addEventListener("input", () => {
        Reflect.set(this.$data, name, el.value);
      });
    });
  }

  pText(el, target, name) {
    el.innerText = Reflect.get(target, name);
  }

  pModel(el, target, name) {
    el.value = Reflect.get(target, name);
  }

  pBind(el, target, attr) {
    const [attribute, key] = attr.split(":");
    el.setAttribute(attribute, Reflect.get(target, key));
  }
}

var Platzi = {
  createApp(options) {
    return new PlatziReactive(options);
  },
};

Como un extra de lo que fue el reto, me propuse implementar que el atributo de p-bind rastree el objeto de data que se le est谩 pasando como valor. Por ejemplo, un elemento con el atributo p-model que tenga como valor image modificar谩 el valor del objeto image en data(). Para actualizarlo cuando esto pase, lo que hice fue agregar una serie de instrucciones en el m茅todo track(), dentro de la funci贸n effect:

document.querySelectorAll(`*[p-bind]`).forEach(el => {
    if ( el.getAttribute("p-bind").includes(name) ) {
        const [attr, name] = el.getAttribute("p-bind").split(":");
        this.pBind(el, target, name, attr);
    }
});

Al final, el c贸digo de la funci贸n track() me qued贸 as铆:

track(target, name) {
    if (!this.deps.has(name)) {
        const effect = () => {
            document.querySelectorAll(`*[p-text=${name}]`).forEach(el => {
                this.pText(el, target, name);
            });
            document.querySelectorAll(`*[p-bind]`).forEach(el => {
                if ( el.getAttribute("p-bind").includes(name) ) {
                    const [attr, name] = el.getAttribute("p-bind").split(":");
                    this.pBind(el, target, name, attr);
                }
            });
        }
        this.deps.set(name, effect);
    }
}

Para poner esto en pr谩ctica, vayan al archivo index.html y agreguen este elemento:

<input type="text" p-model="image">

En este nuevo input, cambien la ruta que aparece por defecto gracias al objeto image en data() por la URL o el path de otra imagen.

Hola!, yo lo hice de la siguiente manera, me permite usar una manera mas parecida a vuejs normal y tambien me parece que es mas escalable para ir incrementando las directrices que se vayan poniendo.

Yo lo prob茅 con el siguiente c贸digo y funciona perfect!

<img a-bind:src="image" a-bind:alt="message">

Aqui dejo mi source del js. El html ya lo conocen

class AccentioReactive {
    // Dependencias
    deps = new Map();
    appEl;

    constructor(options) {
        const self = this;

        this.origen = options.data();
        // Destino
        this.$data = new Proxy(this.origen, {
            get(target, name) {
                if (Reflect.has(target, name)) {
                    self.track(target, name);
                    return Reflect.get(target, name);
                }
                console.warn(`Propiedad ${name} no existe dentro de ${target.constructor.name}`);
                return '';
            },
            set(target, name, value) {
                Reflect.set(target, name, value);
                self.trigger(name)
            }
        });
    }

    track(target, name) {
        if(!this.deps.has(name)) {
            const effect = () => {
                this.appEl.querySelectorAll("*").forEach(el => {
                    const attributes = el.attributes;
                    for (let i = 0; i < attributes.length; i++) {
                        const att = attributes[i];

                        if(att.name === "a-text" && att.value === name) {
                            this.aText(el, this.$data, name);
                        }

                        if(att.name.startsWith("a-bind:") && att.value === name) {
                            this.abind(el, this.$data, att, name);
                        }
                    }
                });
            }
            this.deps.set(name, effect);
        }
    }
    trigger(name) {
        const effect = this.deps.get(name);
        effect();
    }
    mount(appId) {
        this.appEl = document.querySelector(appId);
        this.appEl.querySelectorAll("*").forEach(el => {
            const attributes = el.attributes;

            for (let i = 0; i < attributes.length; i++) {
                const att = attributes[i];
                const attName = att.name;

                if(attName.startsWith("a-bind:")) {
                    const name = att.value;
                    this.abind(el, this.$data, att, name);
                }

                if(attName === "a-text") {
                    const name = att.value;
                    this.aText(el, this.$data, name);
                }

                if(attName === "a-model") {
                    const name = att.value;
                    this.aModel(el, this.$data, name);
                    el.addEventListener("input", () => {
                        Reflect.set(this.$data, name, el.value);
                    });
                }
            }

        });
    }
    aText(el, target, name) {
        el.innerText = Reflect.get(target, name);
    }
    aModel(el, target, name) {
        el.value = Reflect.get(target, name);
    }
    abind(el, target, att, name) {
        // el[target][name] = Reflect.get(target, name);
        const bindedAtt = att.name.replace("a-bind:", "");
        el.setAttribute(bindedAtt, Reflect.get(target, name));
        
    }
}
var Accentio = {
    createApp(options) {
        return new AccentioReactive(options);
    }
}

Mi HTML

<h3>Change SRC Dynamic</h3>
<input type="text" p-model="image" />
<span p-text="image"></span>
<br/>
<img p-bind="src:image" />

js

class PlatziMiniVue {
  // Dependencias
  deps = new Map()

  constructor (options) {
    this.origen = options.data()

    self = this
    // destino
    this.$data = new Proxy(this.origen, {
      get(target, name) {
        if (Reflect.has(target, name)) {
          self.track(target, name)
          return Reflect.get(target, name)
        }
        console.warn('La propiedad: ', name, 'no existe')
        return ''
      },
      set (target, name,  value) {
        Reflect.set(target, name, value)
        self.trigger(name)
      }
    })
  }

  track (target, name) {
    if (!this.deps.has(name)) {
      const effect = () => {
        document.querySelectorAll(`*[p-text=${name}]`).forEach(el => {
          this.pText(el, target, name)
        })
        document.querySelectorAll('*[p-bind]').forEach(el => {
          const [attr] = el.getAttribute('p-bind').match(/(\w+)/g)
          this.pBind(el,target, name, attr)
        })
      }
      this.deps.set(name, effect)
    }
  }

  trigger (name) {
    const effect = this.deps.get(name)
    effect()
  }

  mount () {
    document.querySelectorAll("*[p-text]").forEach(el => {
      this.pText(el, this.$data, el.getAttribute('p-text'))
    })

    document.querySelectorAll("*[p-model]").forEach(el => {
      const name = el.getAttribute('p-model')
      this.pModel(el, this.$data, name)
      el.addEventListener('input', () => {
        Reflect.set(this.$data, name, el.value)
      })
    })

    document.querySelectorAll('*[p-bind]').forEach(el => {
      const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g)
      this.pBind(el, this.$data, name, attr)
    })
  }

  pText (el, target, name) {
    el.innerText = Reflect.get(target, name)
  }

  pModel (el, target, name) {
    el.value = Reflect.get(target, name)
  }

  pBind(el, target, name, attr) {
    if (el.hasAttribute(attr)) {
      el.removeAttribute(attr)
    }
    const attrValue = Reflect.get(target, name)
    el.setAttribute(attr, attrValue)
  }
}

var Platzi = {
  createApp (options) {
    return new PlatziMiniVue(options)
  }
} 

El reto:
Realmente falta implementar lo del input que pueda modificar el src de la imagen, siento que en ese caso es importante modificar el track para verificar que tipo de directiva es.

Mi funci贸n mount qued贸 de la siguiente forma:

mount() {
        document.querySelectorAll('*[p-text]')
        .forEach( el => {
            this.pText(el, this.$data, el.getAttribute('p-text'));
        });

        document.querySelectorAll('*[p-model]')
        .forEach(el => {
            const name = el.getAttribute('p-model');
            this.pModel(el, this.$data, name);
        });
        document.querySelectorAll(`*[p-bind]`)
        .forEach(el => {
            const name = el.getAttribute('p-bind');
            this.pBind(el, this.$data, name);
        })
    }

Y la nueva de pBind de la siguiente forma:

pBind(element, target, name) {
        const [att,value] = name.split(':');
        element[att] = Reflect.get(target,value);
    }

He aprendido muchas cosa en esta clase y genial como intenta implementar Vue que su producto sea reactivo, gracias samuel!

Para que la directiva p-model actualice todos los valores asociados se debe agregar en el m茅todo pModel

el.addEventListener("input", () => {
Reflect.set(target, name, el.value);
 });

Esto es el resumen de lo que entend铆 de esta clase. El tracker obtiene y mapea todas los objetos que tienen una dependencia de un valor din谩mico con su efecto cambia. Los trigger son lo que ejecutan el efecto del valor din谩mico cuando este cambia

Si quieren saber que significo ese selector magico,

pueden verlo en este tweet donde el mismo samuel me lo explico
https://twitter.com/iosamuel/status/1391884374635855879

Reto: Para la directiva p-bind, en la definici贸n de su efecto en el track, se debe recorrer todos los [p-bind] en el DOM, pero al mismo tiempo debe verificarse que la propiedad 鈥渘ame鈥 sea la correspondiente al efecto ejecutado.

    track(target, name) {
        if (!this.deps.has(name)) {
            const effect = () => {
                //document.querySelectorAll(`*[p-text=${name}]`).forEach(el => {
                //    this.pText(el, target, name);
                //});
                //document.querySelectorAll(`*[p-model=${name}]`).forEach(el => {
                //    this.pModel(el, target, name);
                //});
                document.querySelectorAll(`*[p-bind]`).forEach(el => {
                    const attr = (el.getAttribute("p-bind").split(':')[0]);
                    const _name = (el.getAttribute("p-bind").split(':')[1]);
                    if (name == _name) {
                        this.pBind(el, target, name, attr);
                    }
                });
            }
            this.deps.set(name, effect);
        }
    }

Reto solucionado con input:

  1. Se a帽aden las directivas dentro del html :
    <input type="text" p-model="data_imagen">
    <img p-bind="src/data_imagen">
  1. Se a帽ade la funcion para encontrar los valores dentro del dom:
  mount(){
    document.querySelectorAll("*[p-text]").forEach(el=>{
      this.pText(el, this.$data, el.getAttribute('p-text'));
    });

    document.querySelectorAll("*[p-model]").forEach(el => {
      const name = el.getAttribute("p-model");
      this.pModel(el,this.$data, name);

      el.addEventListener("input", () => {
        Reflect.set(this.$data,name, el.value)
      });
    });

    document.querySelectorAll("*[p-bind]").forEach(el => {
      const [attr, name] = el.getAttribute("p-bind").match(/(\w+)/g)
      this.pBind(el, this.$data,name,attr);
    });
  }
  1. Se crea la funcion pBind:
  pText(el,target,name){
    el.innerText = Reflect.get(target,name)
  }

  pModel(el,target,name){
    el.value = Reflect.get(target,name);
  }

  pBind(el,target,name, attr){
    if(attr == "src"){
      el.src = Reflect.get(target,name);
    } 
  }
  1. Se coloca la funcion para encontrar los valores actulalizados dentro del track:
  track(target,name){
    if(!this.deps.has(name)){
      const effect = () => {
        document.querySelectorAll([`*[p-text=${name}]`]).forEach(el => {
          this.pText(el,target,name);
        });
        document.querySelectorAll("*[p-bind]").forEach(el => {
          const [attr, name] = el.getAttribute("p-bind").match(/(\w+)/g)
          this.pBind(el, this.$data,name,attr);
        });
      }
      this.deps.set(name, effect);
    }
  }

b

HTML:

<img p-bind="src:imagen" alt="imagen">

Data:

imagen: 'https://upload.wikimedia.org/wikipedia/commons/3/32/Platzi.jpg'

En el mount, lo que el profesor puso:

	document.querySelectorAll('*[p-bind]').forEach(el => {
            const [attr, name] = el.getAttribute('p-bind').match(/(\w+)/g)
            this.pBind(el, this.$data, name, attr)
        })

Y en la funcion pBind:

	pBind(el, target, name, attr) {
        	el.setAttribute(attr, Reflect.get(target, name))
}

SOLUCIONADO CON IMPUT gracias a Juan esteban, me ayud贸 mucho ver tu c贸digo 馃槂

(Al ocupar p-bind a lo que me quiero referir es a cambiar el .src, asi como el p-text lo hace con el innerHTML y p-model con el .value)

  • HTML directiva
<img p-bind="image"/>

<input type="text" p-model="image"/>
  • Valor de image en data
data(){
        return{
          message: 'Hola miniVue',
          image: "https://source.unsplash.com/random"
        }
      }
  • En el Mount()
document.querySelectorAll('*[p-bind]').forEach((el) => {
      this.pBind(el, this.$data, el.getAttribute('p-bind'));
    });
  • M茅todo pBind()
pBind(el, target, name) {
    el.src = Reflect.get(target, name);
  }

-En el track()

document.querySelectorAll(`*[p-bind=${name}]`).forEach((el) => {
          this.pBind(el, target, name);
        });

Minireto

track(target, name) {
    if(!this.dependencies.has(name)) {
      const effect = () => {
        document.querySelectorAll(`*[f-text=${name}]`).forEach(el => {
          this.fText(el, target, name);
        });

        document.querySelectorAll(`*[f-model=${name}]`).forEach(el => {
          this.fModel(el, target, name);
        });
      };

      this.dependencies.set(name, effect);
    }
  }