tempusfugit (130) [Avatar] Offline
#1
Page 106(110):
6 Working with components
Afterword’s we’ll see a few examples ...
"Afterwards we'll see a few examples ..."


Page 107(111):
6.1 What are components?
... we can pull out redundant parts of our code and separate it into smaller logical parts ...
If it's "redundant" you should eliminate it - probably meant "repeated" or "duplicated" - i.e. components help minimize duplication.

We can re-use each component throughout our application.
Overemphasis of re-use is an OO thing and often leads to premature (over-) generalization (i.e. unnecessary complexity).

Maintainability is the real win as you only need to change the component once and the change propagates to all the places where it is used.

A component can even make sense when you only plan to use one single instance because it establishes a boundary around the component's responsibility through which others have to interface with it.
It’s worth mentioning that you can also have a self-closing tag, <my-component/> if your web browser supports
In general self-closing component tags are discouraged in DOM templates - but then DOM templates are not recommended for production use anyway.

Why You Should Avoid Vue.js DOM Templates:
Non-standard markup may be changed or removed from the page, causing unpredictable results.
... which is later alluded to in the "The is special attribute" sidebar on page 109(113).


6.2 Creating components
However, it’s good practice to name all your components lowercase with hyphens.
Consider pulling the content that appears later in 6.2.2 regarding naming conventions ahead and discuss the issues at hand in a consolidated fashion as this is an important topic given that components present "two faces": 1.) the template markup and 2.) "behaviour" as defined by JavaScript code, to make it very clear that:
  • Names for components and their attributes in template markup always appear in kebab-case as a practical concession to markup being case insensitive.
  • Names for JavaScript component constructors appear in PascalCase (aka "upper CamelCase") as a practical concession to JavaScript naming rules and limitations (e.g. property names that include a hyphen have to be in quotes)
  • Names for JavaScript props appear in "lower camelCase" as a practical concession to JavaScript naming rules and limitations

  • So straight out mentioning "kebab-case" (instead of "lowercase with hyphens") creates an opportunity to explain what that means - and get the whole topic over with once and for all.

    Now component registration will automatically generate the kebab-case name used in markup when a lower camelCase or PascalCase name is supplied but the style guide considers it essential to use the kebab-case name of the component.

    Vue.js Style Guide Component name casing in templates"

    Page 108(112):
    6.2.1 Global Registration
    Listing 6.1 Creating our first global component

    I wonder if this might be better in getting the point across:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const name = 'my-component';
    
         Vue.component(name, {
           template: '<div>Hello From Global Component</div>'
         });
    
         // "h" aka "createElement"
         new Vue({
           el: '#app',
           render: h => h(name)
         });
    
         // FYI: '<div id="app"></div>'
         // is replaced entirely with
         // '<div>Hello From Global Component</div>'
         // i.e. component template is
         // NOT inserted as content into the "app <div>"
        </script>
      </body>
    </html>
    
    This seems to have become a common idiom for the creation of the Vue root instance:
         new Vue({
           el: '#app',
           render: h => h(App)
         });
    
    i.e. it anchors itself at the specified element and then renders the "App component" on top of it. This bypasses the need for the root instance to even have a template (much less fish it's own template out from the DOM).

    Page 109(113):
    The is special attribute

    Great mentioning the caveats here - though I have to admit being in a position where you have to use the "is" attribute really should be an indication that it is time to move on. I guess it's great as a quick prototyping hack but really it seems like a "I need to come back to this and do it properly" type of solution. Also with the mounting "drawbacks" I'd be tempted to completely stick to string templates (i.e. even for the root instance) for the remaining page examples.

    Also discussing the is attribute here seems strange as alternate component templating styles aren't discussed until later in 6.5 (which may be worth mentioning here).

    6.2.2 Local registration
    Listing 6.2 Registering a local component
    FYI:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const name = 'my-component';
         const components = {
    
           [name]: {
             template: '<div>Hello From Local Component</div>'
           }
    
         };
    
         new Vue({
           el: '#app',
           components,
           render: h => h('div', [h(name)])
         });
        </script>
      </body>
    </html>
    
    Sometimes the computed property name syntax comes in handy.

    These computed property (and function) names come in handy when defining Vuex mutation and action name strings and using them as property names on the state object.

    I'm also not so sure about the uppercase on the Component variable (as it is typically reserved for object constructors). The variable references an options object being used to define a component. Single File Component file names (e.g. Component.vue) can get away with the uppercase name as the code contained therein can be be thought of as the base for the component constructor. Inside the file the issue is sidestepped by exposing the content via export default.

    The capitalization is justied when retrieving a component constructor:
      const MyComponent = Vue.component('my-component');
    

    Page 111(114):
    6.4 Using props to pass data

    Just some background.

    To me personally "markup" has always seemed utterly "static" - at least when compared to "program code" which tends to express behaviour. This created for me a bit of an obstacle when "props" are introduced and explained (anywhere) largely in terms of markup attributes.
    <a href="https://www.manning.com/">Home</a>
    
    Here href is clearly some property of the element and "https://www.manning.com/" clearly targets that property. But there is no real sense of where "https://www.manning.com/" comes from - especially as it is just sitting there inside the element tag. It just seems to be where it needs to be, seemingly part of that particular element instance. It really doesn't feel like data being "passed from a parent to a child".

    That insight only comes once it is established that the element exists within the context of its parent element - and therefore the "attribute values" on all child elements "come from" the parent context ... much in the same way that the parameter values of a called function come from the context of the caller.

    The coin only really dropped for me once I stopped viewing the template markup as "markup" and started viewing it as the "source code" for the component's rendering function. Looking at the rendering function it becomes clear that props pass data from the parent to the child component.

    So it may make sense to make the connection between component template and the resulting rendering function more explicit and in some examples show an (equivalent) rendering function instead of the template.
    You can sort of think of props as variables the component has that can only be assigned from the parent.
    Vue.js Component Style Guide:
    In Vue.js your component props are your API.
    "Variables" are probably too passive. All the prop values contribute to the initial render. After that if any one prop changes the component needs to re-render - at which point props act more like a property setters.

    Props seem to be passed to a component whenever it needs to render. Though since 2.2.0 there is a vm.$props instance property. What's also a bit disorienting is that props are mixed in with data on the component instance - unless you are dealing with a functional component (where this doesn't exist) then the props are found on a (transient) props object.

    Page 112(116):
    6.4.1 Literal props
    The easiest to use type of props are literal props.
    Even though the official documentation uses the term "literal prop" (Literal vs. Dynamic) it only serves to create confusion - props themselves aren't literal or dynamic; prop values are set with the literal or dynamic syntax. And to make matters worse even static values that aren't strings require the dynamic syntax (as is mentioned later).

    i.e. avoid "literal prop" and "dynamic prop" altogether in favour of something like: "Using literal syntax to set a component prop value".

    Listing 6.3 Using literal props in our component
    Again, I wonder whether an explicit template option for the instance:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         new Vue({
           el: '#app',
           components: {
             'my-component': {
               template: '<div>Hello {{ text }}! </div>',
               props: ['text']
             }
           },
           template: `<div>
             <my-component text="World" />
           </div>`
         });
        </script>
      </body>
    </html>
    
    may better convey the fact that the root Vue instance wants to render
  • the my-component component
  • with the component's text prop set to a "World" value

  • That may make it easier to make the mental leap to:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const name = 'my-component';
    
         new Vue({
           el: '#app',
           components: {
             [name]: {
               template: '<div>Hello {{ text }}! </div>',
               props: ['text']
             }
           },
           render: h => h('div', [
             h(name, {
               props: {
                 text: "World"
               }
             })
           ])
         });
        </script>
      </body>
    </html>
    
    which makes it clear that the "World" value is in fact handed in from the parent to the component.

    Page 113(117):
    6.4.2 Dynamic props
    Dynamic props are props that are passed in from the parent that are bound to a property that can change (unlike literal props, that are static text).
    Not exactly. The other syntax is called "literal" because the "literal" string is used to set the prop value. The "dynamic" syntax treats the string as a JavaScript expression that needs to be evaluated. The result of that evaluated expression is used to set the prop value. So "dynamic" expresses that the string has to be evaluated (possibly dynamically) - it doesn't have to be bound to a property that can change (though the reactivity system will ensure that it generates an updated prop value if the expression references to an updated reactive value). The dynamic syntax is sometimes used to generate static values.

    Listing 6.4 Dynamic props

    To carry on the theme
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         new Vue({
           el: '#app',
           components: {
             'my-component': {
               template: '<div>Hello {{ text }}! </div>',
               props: ['text']
             }
           },
           data() {
             return {
               message: "From Parent Component"
             };
           },
           template: `<div>
             <my-component :text="message" />
           </div>`
         });
        </script>
      </body>
    </html>
    
    which leads to:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const name = 'my-component';
    
         new Vue({
           el: '#app',
           components: {
             [name]: {
               template: '<div>Hello {{ text }}! </div>',
               props: ['text']
             }
           },
           data() {
             return {
               message: "From Parent Component"
             };
           },
           render: function (h) {
             return h('div', [
               h(name, {
                 props: {
                   text: this.message
                 }
               })
             ]);
           }
         });
        </script>
      </body>
    </html>
    
    Now here the render function couldn't be an arrow function because the "this" context is needed to access the "message" value on the root instance.

    Page 115(119):
    Listing 6.6 Update counters with correct return object

    <!DOCTYPE html>
    <html>
      <head>
        <style type="text/css">
         .stack-btn {
           display: block;
         }
        </style>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         new Vue({
           el: '#app',
           components: {
             'my-component': {
               template:
                 '<button class="stack-btn" @click="counter += 1">{{counter}}</button>',
               data() {
                 return { counter: 0 };
               }
             }
           },
           template: `<div>
             <my-component />
             <my-component />
             <my-component />
           </div>`
         });
        </script>
      </body>
    </html>
    
    <!DOCTYPE html>
    <html>
      <head>
        <style type="text/css">
         .stack-btn {
           display: block;
         }
        </style>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const name = 'my-component';
    
         new Vue({
           el: '#app',
           components: {
             [name]: {
               template:
                 '<button class="stack-btn" @click="counter += 1">{{counter}}</button>',
               data() {
                 return { counter: 0 };
               }
             }
           },
           render: h => h('div',[h(name),h(name),h(name)])
         });
        </script>
      </body>
    </html>
    

    6.4.3 Prop validation
    Listing 6.7 Validating props
    The obj attribute on the my-component tag within the root instance template needs to use dynamic syntax, not literal as shown, to work as expected:
          template: `<div>
             <my-component :num="myNumber" :str="passedString"
                           :even="myNumber" :obj="passedObject" />
           </div>`
    

    In listing 6.7 the ":" is missing in front of obj.

    Page 121(125):
    You’ll need to use something like Webpack or Browserify to transpile the .vue code.
  • Babel is a transpiler
  • Transpiling generally only refers to ES6+ to ES5 "translation" - only one of many pre-processing steps applied to a single file component.
  • Browserify and Webpack are bundlers. Originally Browserify "bundled" JavaScript CommonJS modules (written primarily for Node.js) for use in the browser. These days Browserify can use transforms and Webpack can use loaders that use babel to transpile the code during the bundling/building process.

  • So it probably would make more sense to say: "You’ll need to use something like Webpack or Browserify to build the .vue code."

    Page 122(126):
    6.6 Working with custom events
    Unlike normal events that we saw in chapter 3, custom events are used when passing events from parent to child components.
  • Consider using "DOM events" instead of "normal events" to make the distinction more specific.
  • Vue's custom events were purposefully designed to not escape the instance they are emitted on. However a parent component is in the position to add handlers to the event system of the child component. Viewed that way the event actually stays within the boundary of the child component and it is only the handler that was added by the parent that can have an effect on the parent.
  • Typically, $on(eventname) is used when sending events between different components, that are not parent and child.
    As stated this will tend to give the wrong impression as it creates the notion of events travelling between instances. From vm.$on(event,callback):
    Listen for a custom event on the current vm
  • 1.) Events stay within the confines of the vm on which they are emitted.
  • 2.) A vm may add a handler that triggers on an event that occurs within another vm. Similarly a vm may emit an event on another vm.

  • This gives rise to the notion of using a shared, dedicated Vue instance as an "event bus". Disparate components can interact by listing to and emitting events on a shared Vue instance (the event bus).

    Example of sibling components interacting via a shared event bus:
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://unpkg.com/vue"></script>
      </head>
      <body>
        <div id="app"></div>
    
        <script>
         const incrementEvent = 'INCREMENT_EVENT';
    
         const components = {
    
           'increment-button': {    // button emitting INCREMENT_EVENT
             template: `<div>
               <button @click="increment">{{ label }}</button>
             </div>`,
             inject: ['eventBus'],  // expect eventBus to be provided
             props: {
               quantity: {
                 type: Number,
                 default: 1
               }
             },
             methods: {
               increment() {        // emit increment to the event bus
                 this.eventBus.$emit(incrementEvent, this.quantity);
               }
             },
             computed: {
               label() {            // prefix positive quantity with plus
                 const text = this.quantity.toString();
                 return (this.quantity > 0 ? '+' + text : text);
               }
             }
           },
    
           'counter-display': {     // counter listening for INCREMENT_EVENT
             template: `<p>{{counter}}</p>`,
             inject: ['eventBus'],  // expect eventBus to be provided
             props: {
               initialValue: {      // note: "initial-value" in template
                 type: Number,
                 default: 0
               }
             },
             data() {
               return { counter: this.initialValue };
             },
             methods: {
               incrementCounter(quantity) {
                  this.counter = this.counter + quantity;
               }
             },
             created() {            // create event callback once
               this.$_CounterDisplay_cb = this.incrementCounter.bind(this);
             },
             mounted() {            // add event callback
               this.eventBus.$on(incrementEvent, this.$_CounterDisplay_cb);
             },
             beforeDestroy() {      // remove event callback
               this.eventBus.$off(incrementEvent, this.$_CounterDisplay_cb);
             }
           }
    
         };
    
         new Vue({     // root Vue instance
           el: '#app',
           provide: {  // provide Vue instance to be shared as event bus
             eventBus: new Vue()
           },
           components,
           template: `<div>
             <counter-display />
             <increment-button />
             <counter-display :initial-value="5"/>
             <increment-button :quantity="-2.5"/>
           </div>`
         });
        </script>
      </body>
    </html>
    
    All in all these highly focused small page code examples seemed to convey "how things work" much more effectively than trying to shoehorn these features into some imaginary "real world application".