Vue 3 et son API de composition

Repo:

1

tag git : step1

Repo des démos

Quels problèmes cheche-t-on à résoudre?

Comment créer des composants web possédant des comportements réutilisables et composables entre eux

Composant

  • Isolation
  • Réutilisabilité
  • Entrées/sorties
  • Comportements

Custom Element

Le standard

let template = `<div id="root"></div>`;

class MyElement extends HTMLElement {

  static get observedAttributes() { return ['name']; }

  get name() {
    return this._name
  }

  set name(name) {
    this._name = name
  }

  constructor() {

    super();
    this.attachShadow({mode:'open'});
    this._name = "";
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = template;
    this.shadowRoot.querySelector('#root').innerHTML = this.name;
  }

  attributeChangedCallback(attrName, oldValue, newValue) {

    if (newValue !== oldValue) {
      this[attrName] = newValue; 
    }
  }

}

customElements.define("my-element", MyElement);
<html>
  <body>
    <my-element name="RMN"</my-element>
    <script>
      document.addEventListener("DOMContentLoaded", function() {
        // code précédent
      });
    </script>
  </body>
</html>

Vue

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    
    <div id="root">
      <my-element name="RMN"></my-element>
    </div>
    	
    <script>
      
      Vue.component('my-element', {
        template : `<div>{{name}}</div>`,
        props: ["name"]
      });
      
      new Vue({
        el: '#root',
      })

    </script>
  </body>
</html>
<template>
    <div>{{name}}</div>
</template>

<script>
    export default {
        name: 'MyElement',
        props: ['name']
    };
</script>

Single File Component (SFC)

<template>
    <button @click="handleClick">Inc {{count}}</button>
</template>

<script>
    export default {
        name: 'MyElement',
        props: ['value'],
        data() {
          return {
              count: 0
          }
        },
        mounted() {
            this.count = this.value ?? 0
        },
        methods: {
            handleClick() {
                this.count++
            }
        }
    };
</script>
<template>
  <div>
    <my-element :value="12"></my-element>
    <my-element :value="24"></my-element>
  </div>
</template>

1

Les mixins

Un moyen de réutiliser les comportements

// counter.js
export default {
    props: ['value'],
    data() {
        return {
            count: 0
        }
    },
    mounted() {
        this.count = this.value ?? 0
    },
    methods: {
        handleClick() {
            this.count++
        }
    }
}
<template>
    <button @click="handleClick">Inc {{count}}</button>
</template>

<script>

    import Counter from './mixins/counter'

    export default {
        name: 'MyElement',
        mixins: [Counter]
    };
</script>

2

// counter.js
export default {
    props: ['value'],
    data() {
        return {
            count: 0
        }
    },
    mounted() {
        console.log("mount counter")
        this.count = this.value ?? 0
    },
    methods: {
        handleClick() {
            console.log("inc");
            this.count++
        }
    }
}
<script>

    import Counter from './mixins/counter';
    import CountDown from './mixins/countDown';

    export default {
        name: 'MyElement',
        mixins: [Counter, CountDown]
    };
</script>
// countDown.js
export default {
    props: ['value'],
    data() {
        return {
            count: 0
        }
    },
    mounted() {
        console.log("mount countdown")
        this.count = this.value ?? 0
    },
    methods: {
        handleClick() {
            console.log("decr");
            this.count--
        }
    }
}
<template>
    <div>
        {{count}}
        <button @click="handleClick">+</button>
        <button @click="handleClick">-</button>
    </div>
</template>

3

On va fixer ça 

(enfin à peu prêt ... )

// counter.js
export default {
	...
    methods: {
        handleClickInc() {
            console.log("inc");
            this.count++
        }
    }
}
<template>
    <div>
        {{count}}
        <button @click="handleClickInc">+</button>
        <button @click="handleClickDecr">-</button>
    </div>
</template>

<script>

    import Counter from './mixins/counter';
    import CountDown from './mixins/countDown';

    export default {
        name: 'MyElement',
        mixins: [Counter, CountDown]
    };
</script>
// countDown.js
export default {
	...
    methods: {
        handleClickDecr() {
            console.log("decr");
            this.count--
        }
    }
}

4

Mais c'est pas bien 👎

On a modifié l'intérieur pour faire correspondre notre extérieur

Vue3

Le petit nouveau

<template>
</template>

<script>
export default {
  name: 'App',
  setup() {
	
  }
};
</script>

La méthode setup

5

<template>
</template>

<script>
export default {
  name: 'App',
  setup() {
    console.log("setup")
  },
  beforeCreate() {
    console.log("beforeCreate")
  }
};
</script>

Cycle de vie

setup
beforeCreate

6

<template>
{{name}}
</template>

<script>
  export default {
      setup() {
          return {
              name: "toto"
          }
      },
  };
</script>

Afficher une variable

<template>
{{name}}
</template>

<script>
  export default {
      data() {
          return {
              name: "toto"
          }
      },
  };
</script>

7

<template>
    <button @click="inc">Inc {{count}}</button>
</template>

<script>
export default {
    name: 'App',
    setup() {

        let count = 0;

        function inc() {
            console.log("inc:" + count)
            count++
        }

        return {
            count,
            inc
        }
    }
};
</script>

Déclarer une fonction

8

Il manque la réactivité

<script>
import {reactive} from "vue";

export default {
    name: 'App',
    setup() {

        let state = reactive({
            count: 0
        });

        function inc() {
            console.log("inc: "+state.count)
            state.count++
        }

        function decr() {
            console.log("decr: "+state.count)
            state.count--
        }

        return {
            state,
            inc,
            decr
        }
    }
};
</script>

Store réactif local

10

<template>
    <div>
        {{state.count}}
        <button @click="inc">+</button>
        <button @click="decr">-</button>
    </div>
</template>

10

<script>
import {ref} from "vue";

export default {
    name: 'App',
    setup() {

        let count = ref(0)

        function inc() {
            console.log("inc: "+count)
            count.value++
        }

        function decr() {
            console.log("decr: "+count.value)
            count.value--
        }

        return {
            count,
            inc,
            decr
        }
    }
};
</script>

Proxy réactif

11

<template>
    <div>
        {{count}}
        <button @click="inc">+</button>
        <button @click="decr">-</button>
    </div>
</template>

11

Composition !

// behaviors/inc.js
export default function (count) {
    function handleClick() {
        console.log("inc "+count)
        count.value++
    }

    return {
        handleClick
    }
}
// behaviors/decr.js
export default function (count) {
    function handleClick() {
        console.log("decr "+count)
        count.value--
    }

    return {
        handleClick
    }
}
<script>
    import {ref} from "vue";
    import incBehavior from './behaviors/inc'
    import decrBehavior from './behaviors/decr'
    export default {
        name: "Counter",
        setup() {
            let count = ref(0);

            const decr = decrBehavior(count);
            const inc = incBehavior(count);

            return {
                inc,
                decr,
                count
            }
        }
    };
</script>
<template>
    <div>
        {{count}}
        <button @click="inc.handleClick">+</button>
        <button @click="decr.handleClick">-</button>
    </div>
</template>

12

// Counter.vue
<script>
    import {ref} from "vue";
    import incBehavior from './behaviors/inc'
    import decrBehavior from './behaviors/decr'
    export default {
        name: "Counter",
        props: ['value'],
        setup(props) {
            let count = ref(props.value ?? 0);

            const decr = decrBehavior(count);
            const inc = incBehavior(count);

            return {
                inc,
                decr,
                count
            }
        }
    };
</script>
// App.vue
<template>
    <counter></counter>
    <counter :value="22"></counter>
</template>

13

Pour aller plus loin

Merci de m'avoir écouté 😀