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 “bindeos”, 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=“src: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 “name” 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);
    }
  }