Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue.js #29

Open
2 of 4 tasks
rogerxu opened this issue Oct 29, 2016 · 22 comments
Open
2 of 4 tasks

Vue.js #29

rogerxu opened this issue Oct 29, 2016 · 22 comments

Comments

@rogerxu
Copy link
Owner

rogerxu commented Oct 29, 2016

Introduction - vue.js

  • data binding
  • component aggregation
  • routing
  • state management

Resources

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

The Vue Instance

The Vue Instance — Vue.js

Constructor

var MyComponent = Vue.extend({
  // extension options
})

Properties and Methods

var vm = new Vue({
  el: '#example',
  data: {
    name: 'name'
  }
});

vm.name === vm.$data.name // -> true
vm.$data === data // -> true
vm.$el === document.getElementById('example') // -> true

// $watch is an instance method
vm.$watch('a', function (newVal, oldVal) {
  // this callback will be called when `vm.a` changes
});

Instance Lifecycle Hooks

var vm = new Vue({
  created: function () {},
  mounted: function () {},
  updated: function () {},
  destroyed: function () {},
});

Lifecycle Diagram

image
http://vuejs.org/images/lifecycle.png

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Template Syntax

Template Syntax — Vue.js

Interpolations

Text

<span v-once>This will never change: {{ msg }}</span>

Raw HTML

<div v-html="rawHtml"></div>

Attributes

<button v-bind:disabled="someDynamicCondition">Button</button>

Using JavaScript Expressions

<div v-bind:id="'list-' + id"></div>

Filters

{{ message | capitalize }}
{{ message | filterA | filterB }}
{{ message | filterA('arg1', arg2) }}
new Vue({
  // ...
  filters: {
    capitalize: function (value) {
      if (!value) return '';
      value = value.toString();
      return value.charAt(0).toUpperCase() + value.slice(1);
    }
  }
});

Directives

Directives are special attributes with the v- prefix. Directive attribute values are expected to be a single JavaScript expression.

Arguments

Some directives can take an “argument”, denoted by a colon after the directive name.

<a v-bind:href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>

Modifiers

Modifiers are special postfixes denoted by a dot, which indicate that a directive should be bound in some special way.

<form v-on:submit.prevent="onSubmit"> ... </form>

Shorthands

v-bind

<!-- full syntax -->
<a v-bind:href="url"> ... </a>

<!-- shorthand -->
<a :href="url"> ... </a>

v-on

<!-- full syntax -->
<a v-on:click="doSomething"> ... </a>

<!-- shorthand -->
<a @click="doSomething"> ... </a>

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Computed Properties and Watchers

Computed Properties and Watchers — Vue.js

Computed Properties

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    reversedMessage: function() {
      return this.message.split('').reverse().join('');
    }
  }
});

Watched Properties

var vm = new Vue({
  el: '#watch-example',
  data: {
    question: '',
  },
  watch: {
    // whenever question changes, this function will run
    question: function (newQuestion) {
      this.answer = 'Waiting for you to stop typing...';
      this.getAnswer();
    }
  }
});

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Class and Style Bindings

Class and Style Bindings — Vue.js

Binding HTML Classes

Object Syntax

<div class="static"
     v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

<div v-bind:class="classObject"></div>

Array Syntax

<div v-bind:class="[{ active: isActive }, errorClass]">

Binding Inline Styles

Object Syntax

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

Array Syntax

<div v-bind:style="[baseStyles, overridingStyles]"></div>

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Conditional Rendering

Conditional Rendering — Vue.js

v-if

Conditional Groups with v-if on <template>

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-else

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>

v-else-if

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

Conntrolling Reusable Elements with key

Vue offers a way for you to say, “These two elements are completely separate - don’t re-use them.” Add a key attribute with unique values:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

v-show

<h1 v-show="ok">Hello!</h1>

The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.

v-if vs v-show

Generally speaking, v-if has higher toggle costs while v-show has higher initial render costs. So prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime.

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

List Rendering

List Rendering — Vue.js

v-for with an Array

<ul id="example-1">
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }} - {{ item.message }}
  </li>
</ul>

v-for with an Object

<div v-for="(value, key, index) in object" :key="value.id">
  {{ index }}. {{ key }} : {{ value }}
</div>

key

<div v-for="item in items" :key="item.id">
  <!-- content -->
</div>

Array Change Detection

Mutation Methods

Vue wraps an observed array’s mutation methods so they will also trigger view updates.

Replacing an Array

When working with non-mutating methods, you can replace the old array with the new one:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/);
});

Caveats

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

  1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  2. When you modify the length of the array, e.g. vm.items.length = newLength
// Vue.set
Vue.set(vm.items, indexOfItem, newValue);
vm.$set(vm.items, indexOfItem, newValue);

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue);
vm.items.splice(newLength)

Object Chagne Detection Caveats

Due to limitations of modern JavaScript, Vue cannot detect property addition or deletion.

var vm = new Vue({
  data: {
    a: 1
  }
});
// `vm.a` is reactive

vm.b = 2;
// `vm.b` is NOT reactive

Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method.

Vue.set(vm.userProfile, 'age', 27);
vm.$set(vm.userProfile, 'age', 27);

// should create a fresh object
vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

Displaying Filtered/Sorted Results

In this case, you can create a computed property or a method that returns the filtered or sorted array.

v-for with a Range

<div>
  <span v-for="n in 10">{{ n }}</span>
</div>

v-for on a <template>

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for with a Component

When using v-for with a component, a key is now required.

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id">
</my-component>

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Event Handling

Event Handling — Vue.js

Listening to Events

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>

Method Event Handlers

<button v-on:click="greet">Greet</button>

Methods in Inline Handlers

You can pass it into a method using the special $event variable.

<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="warn('Form cannot be submitted yet.', $event)">Submit</button>

Event Modifiers

Vue provides event modifiers for v-on.

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>

<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- just the modifier -->
<form v-on:submit.prevent></form>

<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div v-on:click.capture="doThis">...</div>

<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div v-on:click.self="doThat">...</div>

<!-- the click event will be triggered at most once -->
<a v-on:click.once="doThis"></a>

<!-- corresponding to addEventListener‘s passive option -->
<!-- the scroll event's default behavior (scrolling) will happen -->
<!-- immediately, instead of waiting for `onScroll` to complete  -->
<!-- in case it contains `event.preventDefault()`                -->
<div v-on:scroll.passive="onScroll">...</div>

Key Modifiers

<input @keyup.enter="submit">
  • .enter
  • .tab
  • .delete (captures both “Delete” and “Backspace” keys)
  • .esc - keyup.esc would only works if you are focusing on the element
  • .space
  • .up
  • .down
  • .left
  • .right

You can also define custom key modifier aliases via the global config.keyCodes object:

// enable `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112;

Automatic Key Modifiers

You can also directly use any valid key names exposed via KeyboardEvent.key as modifiers by converting them to kebab-case:

// the handler will only be called if $event.key === 'PageDown'.
<input @keyup.page-down="onPageDown">

System Modifier Keys

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

.exact Modifier

<!-- this will fire even if Alt or Shift is also pressed -->
<button @click.ctrl="onClick">A</button>

<!-- this will only fire when Ctrl and no other keys are pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- this will only fire when no system modifiers are pressed -->
<button @click.exact="onClick">A</button>

Mouse Button Modifiers

  • .left
  • .right
  • .middle

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Form Input Bindings

Form Input Bindings — Vue.js

Value Bindings

<select v-model="selected">
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  </option>
</select>

<span>Selected: {{ selected }}</span>

<select v-model="selected">
  <!-- inline object literal -->
  <option v-bind:value="{ number: 123 }">123</option>
</select>

<span>Selected: {{ selected.number }}</span>

Modifiers

.lazy

By default is liveChange event, .lazy is for change event

<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" >

.number

Auto cast input value to number.

<input v-model.number="age" type="number">

.trim

<input v-model.trim="msg">

Forms

Vue.js — Forms, components and considerations – webf

Accessibility and structure

1. Do not forget <form> element

2. Do not forget <label> element

3. Nested forms

Nested form elements are forbidden in HTML.

4. Clean form structure

Unless you have a neat HTML structure, your JavaScript code can never be optimal.

Presentation and Styling

5. Consistent aesthetics

6. Ease of Style Customization

  • No hard-coded values for height and colors
  • Easy to change font-size
  • BEM like flat selectors for style overriding
  • Exposing CSS variables for common elements
  • No pre-specified margins

7. Minimum and Viable Styling for state

A Component should out-of-box provide minimal styling for different states. Every form input component has at least following state:

  • Default - input control is untouched
  • Error  - when validation has failed for given input
  • Focus and blur state
  • Disabled state
  • Read-only state (different from disabled state)

JavaScript and Interactions

8. Focus and Blur event

9. Emit what you accept

If a component accepts a string, then it should emit a string on change. If component accepts a date object, then it should emit a date object. However, if component accepts a date as as string, then do not emit value of date as a Date object on change.

10. Stick to :value and @input binding

<!-- Two-way binding syntax sugar using v-model -->
<custom-input v-model="data"></custom-input>

<!-- Two-way binding under the hood -->
<custom-input
  v-bind:value="data"
  v-on:input="data = $event"></custom-input>

11. @input and @change events

HTML5 specification has defined two events  -  input and change.

input is fired whenever the value of the input control is changed. change is fired when a value of the input control is changed and that change is committed by the user. Change is committed when a user has made a selection or lost focus to the component.

<input type="text" @input="onInput" @change="onChange">

In a nutshell, ‘input’ event is fired repeatedly on each keystroke while ‘change’ event is fired less often than input event, on blur or selection.

12. Do not emit @input event from the value watcher

Do never fire @input event on changing the value programmatically.

Input event should be emitted by the component when a user has interacted with the component and changed the value.

If a parent component has set the value of the custom input control and if you are watching for value prop, then no matter what, never emit input event from the watcher.

// bad
// POOR ABSTRACTION
Vue.component('custom-input', {
    // ... More options
    watch: {
        value(newValue) {
             const toEmit = someTransform(newValue);
             const isProgrammatic = true;
             // ABSOLUTELY FORBIDDEN
             this.$emit('input', toEmit, isProgrammatic);
        }
    }
}

13. Support for unidirectional/one-way data flow

In order for custom input controls to work well with unidirectional architecture, two things are a must:

  1. Support for :value and @input bindings. v-model is just a syntax sugar.
  2. Support for immutable data. Custom components typically deal with complex objects. Vue.js cannot detect mutations to the nested object values. Thus as a developer, you should ensure that whenever a value is changed, a new copy of the data is returned instead of mutating original :value passed to the input control.

14. Consistent form validations

Vue.js leaves Form Validation concerns outside of the core for good reasons. Form Validation is not just a development thing; lots of UX concerns are also involved. Every situation calls for a unique form validation technique and its error-state representation.

Ideal form validation should support many design styles as no style fits all. Highly recommend the use of Vuelidate  -  Model based form validation. Vuelidate | A Vue.js model validation library

15. Native form input events

<!-- Capture focus, blur from child input controls -->
<form @focus="resetError" @blur="validate" @input="doSomething">
    <custom-input-1></custom-input-1>
    <custom-input-2></custom-input-2>
</form>

Our events should bubble up so that form element can listen to them. But, the way event handling works in Vue.js, events do not bubble up for the custom elements. Also, just because we emit focus event, it doesn’t count as a native focus event.

// Within Vue.js component
const customEvent = new Event('input');

// Vue.js only event
this.$emit('input');

// Native HTML event
this.$el.dispatchEvent(customEvent);

Using dispatchEvent, we can create real native event which will bubble up.

16. Integration with Forms

To make custom input behave like native input control, trick is to use hidden input field.

<form>
    <input id="x" type="hidden" name="content">
    <trix-editor input="x"></trix-editor>
</form>

You can access your custom input control just like native input using HTMLFormControlsCollection returned by HTMLFormElement.elements.

@rogerxu
Copy link
Owner Author

rogerxu commented Oct 29, 2016

Components

Components Basics — Vue.js

Reusing Components

data Must Be a Function

A component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object.

Vue.component('simple-counter', {
  data: function () {
    return {
      counter: 0,
    };
  },
});

DOM Template Parsing Caveats

<table>
  <tr is="blog-post-row"></tr>
</table>

Use string templates if possible.

Global Registration

Vue.component('my-component-name', {
  // options
});

Make sure the component is registered before you instantiate the root Vue instance.

Local Registration

import ComponentA from './ComponentA.vue';

export default {
  components: {
    ComponentA
  },
  // ...
}

Module Systems

Automatic Global Registration of Base Components

If you’re using Webpack (or Vue CLI 3+, which uses Webpack internally), you can use require.context to globally register only these very common base components.

import Vue from 'vue';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';

const requireComponent = require.context(
  // The relative path of the components folder './components',
  // Whether or not to look in subfolders
  false,
  // The regular expression used to match base component filenames
  /Base[A-Z]\w+\.(vue|js)$/
);

requireComponent.keys().forEach(fileName => {
  // Get component config
  const componentConfig = requireComponent(fileName);

  // Get PascalCase name of component
  const componentName = upperFirst(
    camelCase(
      // Strip the leading `./` and extension from the filename
      fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
    )
  );

  // Register component globally
  Vue.component(
    componentName,
    // Look for the component options on `.default`, which will
    // exist if the component was exported with `export default`,
    // otherwise fall back to module's root.
    componentConfig.default || componentConfig
  );
});

Composing Components

In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events.

Props & Events

Props

Prop Casing (camelCase vs kebab-case)

HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents:

Vue.component('blog-post', {
  // camelCase in JavaScript
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>

Prop Types

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object
}

Passing Data with Props

Every component instance has its own isolated scope. This means you cannot (and should not) directly reference parent data in a child component’s template.

Passing Static or Dynamic Props

<!-- this passes down an actual number -->
<blog-post :likes="42"></blog-post>

<!-- Including the prop with no value will imply `true`. -->
<blog-post is-published></blog-post>

<!-- Even though `false` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.          -->
<blog-post :is-published="false"></blog-post>

<blog-post :comment-ids="[234, 266, 273]"></blog-post>

<blog-post :author="{ name: 'Veronica', company: 'Veridian Dynamics' }"></blog-post>

To pass all the properties of an object as props, you can use v-bind without an argument (v-bind instead of v-bind:prop-name)

<blog-post v-bind="post"></blog-post>

One-Way Data Flow

All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around.

Every time the parent component is updated, all props in the child components will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component.

  1. The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards.
  • Define a local data property that use the prop as its initial value.
    props: {
      initialCount: Number,
    },
    data() {
      return {
        counter: this.initialCounter,
      };
    }
  1. The prop is passed in as a raw value that needs to be transformed.
  • Define a computed property that is computed from the prop's value.
    props: {
      size: String,
    },
    computed: {
      normalizedSize() {
        return this.size.trim().toLowerCase();
      },
    }

Prop Validation

Vue.component('example', {
  props: {
    propA: Number,
    propB: [String, Number],
    propC: {
      type: String,
      required: true,
    },
    propD: {
      type: Number,
      default: 100,
    },
    propE: {
      type: Object,
      default: () => ({ message: 'hello' }),
    },
    propF: {
      validator(value) {
        return value > 10;
      },
    },
  },
});

Sending Messages to Parents with Events

To emit an event to the parent, we can call the built-in $emit method, passing the name of the event.

Vue.component('button-counter', {
  template: '<button @click="onClick">{{ count }}</button',
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    onClick() {
      this.count += 1;
      this.$emit('increment', 1);
    },
  },
});

Listen for this event with v-on.

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter @increment="incrementTotal"></button-counter>
</div>
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0,
  },
  methods: {
    incrementTotal(value) {
      this.total += value;
    },
  },
});

Using v-model on Components

<input v-model="something">

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value"
>
  • v-bind:value="something" => node.value = something
  • v-on:input="something = $event.target.value" => something = $event.target.value

When used on a component, v-model instead does this:

<custom-input
  v-bind:value="something"
  v-on:input="something = $event"
></custom-input>

So for a component to work with v-model, it must:

  • Bind the value attribute to a value prop
  • on input, emit its own custom input event with the new value
<div id="v-model-example">
  <p>{{ message }}</p>
  <my-input v-model="message"></my-input>
</div>
Vue.component('my-input', {
  template: '\
    <div class="form-group">\
      <label v-bind:for="randomId">{{ label }}:</label>\
      <input v-bind:id="randomId" v-bind:value="value" v-on:input="onInput">\
    </div>\
  ',
  props: ['value'],
  methods: {
    onInput: function (event) {
      this.$emit('input', event.target.value)
    }
  },
})

Custom Events

Event Names

We recommend you always use kebab-case for event names.

Customizing Component v-model

By default, v-model on a component uses value as the prop and input as the event, but some input types such as checkboxes and radio buttons may want to use the value attribute for a different purpose. Using the model option can avoid a conflict in such cases:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
});
<base-checkbox v-model="lovingVue"></base-checkbox>

Binding Native Events to Components

<base-input v-on:focus.native="onFocus"></base-input>

Vue provides a $listeners property containing an object of listeners being used on the component.

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

It’s often useful to create a new computed property for listeners

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` merges objects together to form a new object
      return Object.assign({},
        // We add all the listeners from the parent
        this.$listeners,
        // Then we can add custom listeners or override the
        // behavior of some listeners.
        {
          // This ensures that the component works with v-model
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

.sync Modifier

In some cases, we may need “two-way binding” for a prop.

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

For convenience, we offer a shorthand for this pattern with the .sync modifier:

<text-document v-bind:title.sync="doc.title"></text-document>
<text-document v-bind.sync="doc"></text-document>

Slots

Slot Content

<navigation-link url="/profile">
  Your Profile
</navigation-link>
<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>

Named Slots

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>
<base-layout>
  <h1 slot="header">Here might be a page title</h1>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <p slot="footer">Here's some contact info</p>
</base-layout>

Default Slot Content

<button type="submit">
  <slot>Submit</slot>
</button>

Compilation Scope

<navigation-link url="/profile">
  Logged in as {{ user.name }}
</navigation-link>

That slot has access to the same instance properties (i.e. the same “scope”) as the rest of the template. The slot does not have access to <navigation-link>‘s scope.

Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.

Scoped Slots

<ul>
  <li
    v-for="todo in todos"
    v-bind:key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

But in some parts of our app, we want the individual todo items to render something different than just the todo.text.

<ul>
  <li
    v-for="todo in todos"
    v-bind:key="todo.id"
  >
    <!-- We have a slot for each todo, passing it the -->
    <!-- `todo` object as a slot prop.                -->
    <slot v-bind:todo="todo">
      <!-- Fallback content -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

Now when we use the <todo-list> component, we can optionally define an alternative <template> for todo items, but with access to data from the child via the slot-scope attribute:

<todo-list v-bind:todos="todos">
  <!-- Define `slotProps` as the name of our slot scope -->
  <template slot-scope="slotProps">
    <!-- Define a custom template for todo items, using -->
    <!-- `slotProps` to customize each todo.            -->
    <span v-if="slotProps.todo.isComplete"></span>
    {{ slotProps.todo.text }}
  </template>
</todo-list>
<todo-list v-bind:todos="todos">
  <template slot-scope="{ todo }">
    <span v-if="todo.isComplete"></span>
    {{ todo.text }}
  </template>
</todo-list>

Dynamic Components

<component v-bind:is="currentTabComponent"></component>

We can wrap our dynamic component with a <keep-alive> element

<!-- Inactive components will be cached! -->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

Async Components

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // Pass the component definition to the resolve callback
    resolve({
      template: '<div>I am async!</div>'
    });
  }, 1000);
});

Recommend to use async components together with Webpack’s code-splitting feature:

Vue.component('async-webpack-example', function (resolve) {
  // This special require syntax will instruct Webpack to
  // automatically split your built code into bundles which
  // are loaded over Ajax requests.
  require(['./my-async-component'], resolve);
});

You can also return a Promise in the factory function:

Vue.component(
  'async-webpack-example',
  // The `import` function returns a Promise.
  () => import('./my-async-component')
);

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
});

Handling Edge Cases

Accessing the Root Instance

$root

// Get root data
this.$root.foo

// Set root data
this.$root.foo = 2

Accessing the Parent Component Instance

$parent

Child Component Refs

<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput
<input ref="input">
methods: {
  // Used to focus the input from the parent
  focus: function () {
    this.$refs.input.focus()
  }
}

Dependency Injection

provide: function () {
  return {
    getMap: this.getMap
  }
}
inject: ['getMap']

Programmatic Event Listeners

mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}

Circular References

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

Or use Webpack’s asynchronous import when you register the component locally:

components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

Inline Templates

``html

These are compiled as the component's own template.

Not parent's transclusion content.

```

X-Templates

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

Forcing an Update

$forceUpdate

Cheap Static Components with v-once

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

@rogerxu
Copy link
Owner Author

rogerxu commented Nov 29, 2016

Vue Instance

const vm = new Vue({
  el: '#app',
  data: {
    name: 'Roger',
  },
  computed: {
    now() {
      return (new Date()).toString();
    },
  },
  watch: {
    name: function(value) {
      this.fullName = 'hello ' + value;
    },
  },
  created() {
    console.log('vm created');
  },
  filters: {
    lowercase(value) {
      return value.toLowerCase();
    },
  },
  components: {
    
  },
  methods: {
    onAddItem(event) {
      console.log('add item');
    },
  },
});

@rogerxu
Copy link
Owner Author

rogerxu commented Nov 29, 2016

Vue Component

Vue.component('todo-item', {
  template: '#item-template',
  replace: true,
  props: {
    name: {
      type: String,
      required: true,
      default() {
        return 'Unnamed';
      },
      validator(value) {
        return value.length < 20;
      },
    },
  },
  data() {
    return {
      message: 'hello',
    };
  },
  computed: {
    now() {
      return (new Date()).toString();
    },
  },
  methods: {
    increment() {
      this.count += 1;
      this.$emit('increment');
    },
  },
});

@rogerxu
Copy link
Owner Author

rogerxu commented Dec 9, 2016

Transitions

Transitioning Single Elements/Components

  • Conditional rendering (using v-if)
  • Conditional rendering (using v-show)
  • Dynamic components
  • Component root nodes

Transition Classes

  1. v-enter - Starting state for enter. Applied before element is inserted, removed after one frame.
  2. v-enter-active - Active and ending state for enter. Applied before element is inserted, removed when transition/animation finishes.
  3. v-leave - Staring state for leave. Applied when leave transition is triggered, removed after one frame.
  4. v-leave-active - Active and ending state for leave. Applied when leave transitions is triggered, removed when the transition/animation finishes.

Transition Classes

Each of these classes will be prefixed with the name of the transition. If you use <transition name="fade"> for example, then the v-enter class would be fade-enter.

v-enter-active and v-leave-active give you the ability to specify different easing curves for enter/leave transitions.

CSS Transitions

<div id="example">
  <transition name="fade">
    <p v-if="show">hello</p>
</div>
new Vue({
  el: '#example',
  data: {
    show: true,
  },
});
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s ease;
}

.fade-enter, .fade-leave-active {
  opacity: 0;
}

CSS Animations

v-enter is not removed immediately after the element is inserted, but on an animationend event.

<div id="example">
  <transition name="fade">
    <p v-if="show">hello</p>
</div>
new Vue({
  el: '#example',
  data: {
    show: true,
  },
});
.bounce-enter-active {
  animation: bounce-in .5s;
}

.bounce-leave-active {
  animation: bounce-out .5s;
}

@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes bounce-out {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(0);
  }
}

Custom Transition Classes

  • enter-class
  • enter-active-class
  • leave-class
  • leave-active-class

It is useful when you want to combine Vue's transition system with an existing CSS animation library, such as Animate.css.

<div id="example">
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#example',
  data: {
    show: true,
  },
});

@rogerxu
Copy link
Owner Author

rogerxu commented May 21, 2018

@rogerxu
Copy link
Owner Author

rogerxu commented May 21, 2018

@rogerxu
Copy link
Owner Author

rogerxu commented May 21, 2018

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 7, 2018

Vuex

What is Vuex? | Vuex

image
https://vuex.vuejs.org/vuex.png

State

State | Vuex

The mapState Helper

// in full builds helpers are exposed as Vuex.mapState
import { mapState } from 'vuex';

export default {
  // ...
  computed: mapState({
    // arrow functions can make the code very succinct!
    count: state => state.count,

    // passing the string value 'count' is same as `state => state.count`
    countAlias: 'count',

    // to access local state with `this`, a normal function must be used
    countPlusLocalState (state) {
      return state.count + this.localCount
    },
  }),
};

We can also pass a string array to mapState when the name of a mapped computed property is the same as a state sub tree name.

computed: mapState([
  // map this.count to store.state.count
  'count'
]),

Object Spread Operator

computed: {
  localComputed () { /* ... */ },
  // mix this into the outer object with the object spread operator
  ...mapState({
    // ...
  })
}

Getters

Getters | Vuex

Mutations

Mutations | Vuex

The only way to actually change state in a Vuex store is by committing a mutation.

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment(state, payload) {
      // mutate state
      state.count += payload.amount;
    }
  }
});

Mutations are triggered with store.commit method:

store.commit('increment', {
  amount: 10,
);

Usage in Components

Direct use this.$store.commit(type, payload).

mapMutations helper:

import { mapMutations } from 'vuex';

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // map `this.increment()` to `this.$store.commit('increment')`

      // `mapMutations` also supports payloads:
      'incrementBy' // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // map `this.add()` to `this.$store.commit('increment')`
    })
  }
}

Actions

Actions | Vuex

Action handlers receive a context object which exposes the same set of methods/properties on the store instance.

actions: {
  increment({commit}) {
    commit('increment');
  }
}

We can perform asynchronous operations inside an action:

actions: {
  incrementAsync({commit}) {
    setTimeout(() => {
      commit('increment');
    }, 1000);
  }
}

Actions are triggered with the store.dispatch method:

store.dispatch('increment', {
  amount: 10
});
actions: {
  checkout ({ commit, state }, products) {
    // save the items currently in the cart
    const savedCartItems = [...state.cart.added]
    // send out checkout request, and optimistically
    // clear the cart
    commit(types.CHECKOUT_REQUEST)
    // the shop API accepts a success callback and a failure callback
    shop.buyProducts(
      products,
      // handle success
      () => commit(types.CHECKOUT_SUCCESS),
      // handle failure
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

store.dispatch can handle Promise returned by the triggered action handler and it also returns Promise:

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // wait for `actionA` to finish
    commit('gotOtherData', await getOtherData())
  }
}

mapActions

import { mapActions } from 'vuex';

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // map `this.increment()` to `this.$store.dispatch('increment')`

      // `mapActions` also supports payloads:
      'incrementBy' // map `this.incrementBy(amount)` to `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')`
    }),
  }
};

Modules

Modules | Vuex

Plugins

Plugins | Vuex

const myPlugin = store => {
  // called when the store is initialized
  store.subscribe((mutation, state) => {
    // called after every mutation.
    // The mutation comes in the format of `{ type, payload }`.
  })
};

Comparison

Vuex与Redux对比 - CSDN博客

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 7, 2018

SSR

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 14, 2018

Mixins

Mixins — Vue.js

Option Merging

var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 14, 2018

Custom Directives

Custom Directives — Vue.js

directives: {
  focus: {
    // directive definition
    inserted: function (el) {
      el.focus()
    }
  }
}
<input v-focus>

Hook Functions

  • bind
  • inserted
  • update
  • componentUpdated
  • unbind

Directive Hook Arguments

  • el
  • binding
    • name: The name of the directive, without the v- prefix.
    • value: The value passed to the directive.
    • expression
    • arg: The argument passed to the directive. v-my-directive:foo
    • modifiers: An object containing modifiers. v-my-directive.foo.bar

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 14, 2018

Render Functions

Render Functions & JSX — Vue.js

The Virtual DOM

render: function (createElement) {
  return createElement('h1', data, children);
}

The Data Object

{
  class: {
    foo: true,
    bar: true
  },
  style: {
    color: 'red',
    fontSize: '14px'
  },
  attrs: {
    id: 'foo'
  },
  props: {},
  domProps: {
    innerHTML: 'baz'
  },
  on: {
    click: this.clickHandler
  },
  nativeOn: {
    click: this.nativeClickHandler
  },
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
    }
  ],
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  slot: 'name-of-slot',
  key: 'myKey',
  ref: 'myRef'
}

VNodes Must Be Unique

Invalid:

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // Yikes - duplicate VNodes!
    myParagraphVNode, myParagraphVNode
  ])
}

JSX

<anchored-heading :level="1">
  <span>Hello</span> world!
</anchored-heading>
createElement(
  'anchored-heading', {
    props: {
      level: 1
    }
  }, [
    createElement('span', 'Hello'),
    ' world!'
  ]
)
import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
});

Functional Components

Stateless and instanceless.

<template functional>
</template>
var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items;

      if (items.length === 0)           return EmptyList;
      if (typeof items[0] === 'object') return TableList;
      if (context.props.isOrdered)      return OrderedList;

      return UnorderedList;
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    );
  }
});

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 14, 2018

Plugins

Plugins — Vue.js

Writing a Plugin

Plugins usually add global-level functionality to Vue.

A Vue.js plugin should expose an install method. The method will be called with the Vue constructor as the first argument, along with possible options:

MyPlugin.install = function (Vue, options) {
  // 1. add global method or property
  Vue.myGlobalMethod = function () {
    // something logic ...
  }

  // 2. add a global asset
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // something logic ...
    }
    ...
  })

  // 3. inject some component options
  Vue.mixin({
    created: function () {
      // something logic ...
    }
    ...
  })

  // 4. add an instance method
  Vue.prototype.$myMethod = function (methodOptions) {
    // something logic ...
  }
}

Using a Plugin

// calls `MyPlugin.install(Vue)`
Vue.use(MyPlugin, { someOption: true })

Vue.use automatically prevents you from using the same plugin more than once, so calling it multiple times on the same plugin will install the plugin only once.

// When using CommonJS via Browserify or Webpack
var Vue = require('vue');
var VueRouter = require('vue-router');

// Don't forget to call this
Vue.use(VueRouter);

@rogerxu
Copy link
Owner Author

rogerxu commented Aug 14, 2018

Filters

Filters — Vue.js

<!-- in mustaches -->
{{ message | capitalize }}
{{ message | filterA | filterB }}
{{ message | filterA('arg1', arg2) }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
new Vue({
  // ...
  filters: {
    capitalize: function (value) {
      if (!value) return '';
      value = value.toString();
      return value.charAt(0).toUpperCase() + value.slice(1);
    }
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant