Vue2学习(二)

77 阅读8分钟

8.条件渲染

  • v-if 支持 template>

  • v-show 不支持template 元素始终会被渲染并保留在 DOM 中

  • v-if vs v-show

    • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
    • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
    • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
    • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
  • v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级

    • 当你只想为部分项渲染节点时很适用

    • 有条件跳过循环

      #部分项渲染节点
      <li v-for="todo in todos" v-if="!todo.isComplete">
        {{ todo }}
      </li>
      ​
      #有条件跳过循环
      <ul v-if="todos.length">
        <li v-for="todo in todos">
          {{ todo }}
        </li>
      </ul>
      <p v-else>No todos left!</p>
      

9.列表渲染

  • v-for遍历数组

    <ul id="example-2">
      <li v-for="(item, index) in items">
        {{ parentMessage }} - {{ index }} - {{ item.message }}
      </li>
    </ul>
    ​
    var example2 = new Vue({
      el: '#example-2',
      data: {
        parentMessage: 'Parent',
        items: [
          { message: 'Foo' },
          { message: 'Bar' }
        ]
      }
    })
    
  • v-for遍历对象

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>
​
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})
  • 数组更新检测

    • 变更方法 更新原数组

      • push()
      • pop()
      • shift()
      • unshift()
      • splice()
      • sort()
      • reverse()
    • 非变更方法 不更新原数组

      • filter()
      • concat()
      • slice()

10.事件处理

  • 监听事件 v-on

  • 事件处理方法 v-on:click="funcitonName()"

  • 内联处理器中的方法v-on:click="funcitonName(params)"

  • 事件修饰符

    • .stop

    • .prevent

    • .capture

    • .self

    • .once

    • .passive

      <!-- 阻止单击事件继续传播 -->
      <a v-on:click.stop="doThis"></a><!-- 提交事件不再重载页面 -->
      <form v-on:submit.prevent="onSubmit"></form><!-- 修饰符可以串联 -->
      <a v-on:click.stop.prevent="doThat"></a><!-- 只有修饰符 -->
      <form v-on:submit.prevent></form><!-- 添加事件监听器时使用事件捕获模式 -->
      <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
      <div v-on:click.capture="doThis">...</div><!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
      <!-- 即事件不是从内部元素触发的 -->
      <div v-on:click.self="doThat">...</div><!-- 点击事件将只会触发一次 -->
      <a v-on:click.once="doThis"></a><!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
      <!-- 而不会等待 `onScroll` 完成  -->
      <!-- 这其中包含 `event.preventDefault()` 的情况 -->
      <div v-on:scroll.passive="onScroll">...</div>
      
  • 按键修饰符

    • .enter

    • .tab

    • .delete (捕获“删除”和“退格”键)

    • .esc

    • .space

    • .up

    • .down

    • .left

    • .right

      <!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
      <input v-on:keyup.enter="submit"><input v-on:keyup.page-down="onPageDown"><input v-on:keyup.13="submit">
      ​
      //全局 config.keyCodes 对象自定义按键修饰符别名:
      // 可以使用 `v-on:keyup.f1`
      Vue.config.keyCodes.f1 = 112
      
  • 系统修饰键

    • .ctrl

    • .alt

    • .shift

    • .meta

    • .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

      <!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
      <button v-on:click.ctrl="onClick">A</button><!-- 有且只有 Ctrl 被按下的时候才触发 -->
      <button v-on:click.ctrl.exact="onCtrlClick">A</button><!-- 没有任何系统修饰符被按下的时候才触发 -->
      <button v-on:click.exact="onClick">A</button>
      
    • .left

    • .right

    • .middle

11.表单输入绑定

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。
修饰符
  • .lazy 在 change 事件之后进行同步
  • .number 将用户的输入值转为数值类型
  • .trim 自动过滤用户输入的首尾空白字符

12.组件基础

1.一个组件的 data 选项必须是一个函数

因此每个实例可以维护一份被返回对象的独立的拷贝:

data: function () {
  return {
    count: 0
  }
}

2.组件的组织

  • 全局注册

    全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

    Vue.component('my-component-name', {
      // ... options ...
    }
    
  • 局部注册

    import ComponentA from './ComponentA'
    import ComponentC from './ComponentC'export default {
      components: {
        ComponentA,
        ComponentC
      },
      // ...
    }
    
  • 基础组件的自动化全局注册

    import Vue from 'vue'
    import upperFirst from 'lodash/upperFirst'
    import camelCase from 'lodash/camelCase'const requireComponent = require.context(
      // 其组件目录的相对路径
      './components',
      // 是否查询其子目录
      false,
      // 匹配基础组件文件名的正则表达式
      /Base[A-Z]\w+.(vue|js)$/
    )
    ​
    requireComponent.keys().forEach(fileName => {
      // 获取组件配置
      const componentConfig = requireComponent(fileName)
    ​
      // 获取组件的 PascalCase 命名
      const componentName = upperFirst(
        camelCase(
          // 获取和目录深度无关的文件名
          fileName
            .split('/')
            .pop()
            .replace(/.\w+$/, '')
        )
      )
    ​
      // 全局注册组件
      Vue.component(
        componentName,
        // 如果这个组件选项是通过 `export default` 导出的,
        // 那么就会优先使用 `.default`,
        // 否则回退到使用模块的根。
        componentConfig.default || componentConfig
      )
    })
    ​
    //真实案例
    // Globally register all base components for convenience, because they
    // will be used very frequently. Components are registered using the
    // PascalCased version of their file name.import Vue from 'vue'// https://webpack.js.org/guides/dependency-management/#require-context
    const requireComponent = require.context(
      // Look for files in the current directory
      '.',
      // Do not look in subdirectories
      false,
      // Only include "_base-" prefixed .vue files
      /_base-[\w-]+.vue$/
    )
    ​
    // For each matching file name...
    requireComponent.keys().forEach((fileName) => {
      // Get the component config
      const componentConfig = requireComponent(fileName)
      // Get the PascalCase version of the component name
      const componentName = fileName
        // Remove the "./_" from the beginning
        .replace(/^./_/, '')
        // Remove the file extension from the end
        .replace(/.\w+$/, '')
        // Split up kebabs
        .split('-')
        // Upper case
        .map((kebab) => kebab.charAt(0).toUpperCase() + kebab.slice(1))
        // Concatenated
        .join('')
    ​
      // Globally register the component
      Vue.component(componentName, componentConfig.default || componentConfig)
    })
    

    3.通过Prop向子组件传递数据

    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
    ​
    Vue.component('blog-post', {
      props: ['post'],
      template: `
        <div class="blog-post">
          <h3>{{ post.title }}</h3>
          <div v-html="post.content"></div>
        </div>
      `
    })
    ​
    

4.监听子组件事件

  • 使用事件抛出一个值

    <button v-on:click="$emit('enlarge-text', 0.1)">
      Enlarge text
    </button>
    ​
    <blog-post
      ...
      v-on:enlarge-text="onEnlargeText"
    ></blog-post>
    ​
    methods: {
      onEnlargeText: function (enlargeAmount) {
        this.postFontSize += enlargeAmount
      }
    }
    
  • 在组件上使用v-model

    为了让它正常工作,这个组件内的 <input> 必须:

    • 将其 value attribute 绑定到一个名叫 value 的 prop 上
    • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
    Vue.component('custom-input', {
      props: ['value'],
      template: `
        <input
          v-bind:value="value"
          v-on:input="$emit('input', $event.target.value)"
        >
      `
    })
    ​
    <custom-input v-model="searchText"></custom-input>
    

5.插槽

6.动态组件

13.深入了解组件

1.Prop

1.Prop大小写

  • 浏览器对大小写不敏感,统一转换成小写

  • camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

    <!-- 在 HTML 中是 kebab-case 的 -->
    <blog-post post-title="hello!"></blog-post>
    ​
    Vue.component('blog-post', {
      // 在 JavaScript 中是 camelCase 的
      props: ['postTitle'],
      template: '<h3>{{ postTitle }}</h3>'
    }
    
  • 使用字符串模板,那么这个限制就不存在

2.Prop类型

3.传入静态或动态Prop

4.单项数据流

5.Prop验证

6.非prop的attribute

  • 替换/合并已有的attribute
  • 禁用attribute inheritAttrs: false

2.自定义组件

推荐始终使用kebab-case的事件名

  • v-model

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的model 选项可以用来避免这样的冲突:

    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>
    ​
    

    这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

    注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。

  • 将原生事件绑定到组件

    Vue.component('base-input', {
      inheritAttrs: false,
      props: ['label', 'value'],
      computed: {
        inputListeners: function () {
          var vm = this
          // `Object.assign` 将所有的对象合并为一个新对象
          return Object.assign({},
            // 我们从父级添加所有的监听器
            this.$listeners,
            // 然后我们添加自定义监听器,
            // 或覆写一些监听器的行为
            {
              // 这里确保组件配合 `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>
      `
    })
    

    现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

  • .sync修饰符

    • 使用emit出发事件更新数据
    • this.$emit('update:title', newTitle)
      <text-document
        v-bind:title="doc.title"
        v-on:update:title="doc.title = $event"
      ></text-document>
      //为了方便起见,为这种模式提供一个缩写,即 .sync 修饰符:
      <text-document v-bind:title.sync="doc.title"></text-document>
      
    • 注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

3.插槽 slot

  • 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

  • 后备内容 (Submit 作为后备内容 可被默认渲染或替换)

    <button type="submit">
      <slot>Submit</slot>
    </button>
    
  • 具名插槽

    <slot name='header'></slot>
    <slot name='footer'></slot>
    <slot name='default'></slot>
    
    • v-slot 只能添加在 <template>,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上
  • 作用域插槽

    • 为了让插槽中的内容可以访问父级内容,可以将父级内容作为slot元素的一个attibute绑定上去:

      <span>
        <slot v-bind:user="user">
          {{ user.lastName }}
        </slot>
      </span>
      
    • 绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

      <current-user>
        <template v-slot:default="slotProps">
          {{ slotProps.user.firstName }}
        </template>
      </current-user>
      
    • 独占默认插槽的缩写语法

      <current-user v-slot="slotProps">
        {{ slotProps.user.firstName }}
      </current-user>
      
    • 注意默认插槽的缩写语法不能和具名插槽混用

    • 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法

    • 解构插槽prop

      • 作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里
      • <current-user v-slot="{ user: person }">  {{ person.firstName }}</current-user>
        <current-user v-slot="{ user = { firstName: 'Guest' } }">
          {{ user.firstName }}
        </current-user>
        
  • 动态插槽名

    <base-layout>
    ​
      <template v-slot:[dynamicSlotName]>
        ...
      </template></base-layout>
    
  • 具名插槽缩写 v-slot: -> #

    <current-user #default="{ user }">
      {{ user.firstName }}
    </current-user>
    

4.动态组件&异步组件

  • 在动态组件上使用keep-alive

    <!-- 失活的组件将会被缓存!-->
    <keep-alive>
      <component v-bind:is="currentTabComponent"></component>
    </keep-alive>
    
  • 异步组件

    Vue.component('async-example', function (resolve, reject) {
      setTimeout(function () {
        // 向 `resolve` 回调传递组件定义
        resolve({
          template: '<div>I am async!</div>'
        })
      }, 1000)
    })
    ​
    Vue.component('async-webpack-example', function (resolve) {
      // 这个特殊的 `require` 语法将会告诉 webpack
      // 自动将你的构建代码切割成多个包,这些包
      // 会通过 Ajax 请求加载
      require(['./my-async-component'], resolve)
    })
    ​
    Vue.component(
      'async-webpack-example',
      // 这个动态导入会返回一个 `Promise` 对象。
      () => import('./my-async-component')
    )
    ​
    new Vue({
      // ...
      components: {
        'my-component': () => import('./my-async-component')
      }
    })
    

    处理加载状态

    const AsyncComponent = () => ({
      // 需要加载的组件 (应该是一个 `Promise` 对象)
      component: import('./MyComponent.vue'),
      // 异步组件加载时使用的组件
      loading: LoadingComponent,
      // 加载失败时使用的组件
      error: ErrorComponent,
      // 展示加载时组件的延时时间。默认值是 200 (毫秒)
      delay: 200,
      // 如果提供了超时时间且组件加载也超时了,
      // 则使用加载失败时使用的组件。默认值是:`Infinity`
      timeout: 3000
    })
    

5.处理边界情况

  • 依赖注入 provide inject

    provide 选项允许我们指定我们想要提供给后代组件的数据/方法。在这个例子中,就是 <google-map> 内部的 getMap 方法:
    provide: function () {
      return {
        getMap: this.getMap
      }
    }
    然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 propertyinject: ['getMap']
    
  • 程序化的事件侦听器

    mounted: function () {
      this.attachDatepicker('startDateInput')
      this.attachDatepicker('endDateInput')
    },
    methods: {
      attachDatepicker: function (refName) {
        var picker = new Pikaday({
          field: this.$refs[refName],
          format: 'YYYY-MM-DD'
        })
    ​
        this.$once('hook:beforeDestroy', function () {
          picker.destroy()
        })
      }
    }
    
  • 控制更新

    • $forceUpdate
    • 通过v-once创建低开销的静态组件

14.过渡&动画

1.过渡的类名

Transition Diagram

2.CSS动画

.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

15.可复用性&组合

  1. 混入

    // 定义一个混入对象
    var myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin!')
        }
      }
    }
    ​
    // 定义一个使用混入对象的组件
    var Component = Vue.extend({
      mixins: [myMixin]
    })
    ​
    var component = new Component() // => "hello from mixin!"
    
  2. 自定义指令

    // 注册一个全局自定义指令 `v-focus`
    Vue.directive('focus', {
      // 当被绑定的元素插入到 DOM 中时……
      inserted: function (el) {
        // 聚焦元素
        el.focus()
      }
    })
    //注册局部指令
    directives: {
      focus: {
        // 指令的定义
        inserted: function (el) {
          el.focus()
        }
      }
    }
    
  3. 钩子函数

    一个指令定义对象可以提供如下几个钩子函数 (均为可选):

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
    • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
    • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    • unbind:只调用一次,指令与元素解绑时调用。

响应式原理

data