Vue2 基础知识(三)

Posted by violetks on January 3, 2023

Vue2 于 2023年12月31日停止维护,官网:https://v2.cn.vuejs.org/

一、处理边界情况(★)

1、访问元素 & 组件
(1)访问根实例:在每个new Vue实例的子组件中,其根实例可以通过$root访问。

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})
// 所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。
// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

(2)访问父级组件实例:子组件使用$parent访问父组件实例。
(3)访问子组件实例或子元素:使用refref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

<base-input ref="usernameInput"></base-input>

this.$refs.usernameInput

refv-for一起使用,你得到的ref将会是一个包含了对应数据源的这些子组件的数组。
$refs只会在组件渲染完成之后生效,并且它们不是响应式的。
(4)依赖注入provideinject。使用$parent无法很好的扩展到更深层级的嵌套组件上,这时可以使用provideinjectprovide指定想要提供给后代组件的数据/方法,在任何后代组件里使用inject接收。

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

2、循环引用
(1)递归组件:组件是可以在它们自己的模板中调用自身的。递归组件可能会导致无限循环,需确保递归调用是条件性的(例如使用一个最终会得到falsev-if)。
(2)组件之间的循环引用:组件 A 和组件 B,A 依赖 B,B 又依赖 A,但是 A 又依赖 B,变成一个循环,这些组件在渲染树中互为对方的后代和祖先。
解决方法一:等到生命周期钩子beforeCreate时去注册组件 B:

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

解决方法二:在本地注册组件时,可以使用 webpack 的异步import

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

3、强制更新$forceUpdate

二、Mixin 混入

混入(mixin)可以用于分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
1、定义一个混入对象。混入对象和组件对象基本是相同,都有 data、method 和一系列生命周期钩子函数。

// mixin.js
const myMixin = {
  data () {
    return {
      mixinData: "混入的数据",
      obj: { name: "zhangsan", gender: "male" }
    }
  },
  created () {
    console.log("混入的created");
  },
  methods: {
    test () {
      console.log("混入的method");
    }
  }
}

2、混入分为局部混入和全局混入两种方式。
(1)局部混入:在需要使用的组件中,用mixins属性来混入,mixins是一个数组,可以使用多个混入。

let vm = new Vue({
  el: "#app",
  mixins: [myMixin], // 局部混入
  data: {
    msg: "hello"
  },
  created () {
    console.log("Vue的created")
  }
})

(2)全局混入:将定义的混入对象注册到全局,每个 Vue 实例都会混入这个对象,应当避免使用。

Vue.mixin(myMixin)

3、混入的规则。
(1)data数据对象进行合并,发生冲突时以组件数据优先,组件中的数据会覆盖混入对象的数据。
(2)生命周期钩子函数混合为一个数组,当使用组件时,组件的函数和混合的函数都执行,先执行混入中的函数,再执行组件自身的函数。
(3)值为对象的选项,例如methodscomponentsdirectives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

三、render 函数

render: function (createElement) {
  return createElement("h1", this.blogTitle)
}

createElement参数:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下面“深入数据对象”)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

深入数据对象:

{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML attribute
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层 property
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

render函数是 Vue2 新增的一个函数,主要用来提升节点的性能,它是基于 JavaScript 计算。使用render函数将 template 里面的节点解析成虚拟 DOM。
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程能力。这时可以用渲染函数,它比模板更接近编译器。
简单来说,在 Vue 中我们使用模板 HTML 语法组建页面的,使用render函数我们可以用 JavaScript 语言来构建 DOM。因为 Vue 是虚拟 DOM,所以在拿到 template 模板时也要转译成 VNode 的函数,而用render函数构建 DOM,Vue 就免去了转译的过程。

new Vue({
  el: "#app",
  render: h => h(App)
})

使用 JavaScript 代替模板功能:
1、VNode 必须唯一。
2、在模板中使用的v-ifv-for,在渲染函数中可以用 JavaScript 的if/elsemap来重写。
3、渲染函数中没有与v-model的直接对应,需要自己实现。
4、事件 & 按键修饰符:

modifier.png

on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

other_modifier.png

5、插槽:
(1)通过this.$slots访问静态插槽的内容,每个插槽都是一个 VNode 数组:

render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div', this.$slots.default)
}

(2)通过this.$scopedSlots访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数:

props: ['message'],
render: function (createElement) {
  // `<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

(3)如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的scopedSlots字段:

render: function (createElement) {
  // `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
  return createElement('div', [
    createElement('child', {
      // 在数据对象中传递 `scopedSlots`
      // 格式为 { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}

函数式组件:只是一个接受一些 prop 的函数。可以将组件标记为functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。
在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明:

<template functional>
</template>

关于不同版本的 Vue:
1、vue.js 与 vue.runtime.xxx.js 的区别:
(1)vue.js 是完整版的 Vue,包含核心功能和模板解析器。
(2)vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器。
2、因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用render函数接收到的createElement函数去指定具体内容。

四、插件

1、插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制,一般有下面几种:
(1)添加全局方法或者属性。
(2)添加全局资源:指令/过滤器/过渡等。
(3)通过全局混入来添加一些组件选项。
(4)添加 Vue 实例方法,通过把它们添加到Vue.prototype上实现。
(5)一个库,提供自己的 API,同时提供上面提到的一个或多个功能。
2、本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据。
3、定义插件:

MyPlugin.install = function (Vue, options) {
  // 1、添加全局过滤器
  Vue.filter(...)

  // 2、添加全局指令
  Vue.directive(...)

  // 3、配置全局混入
  Vue.mixin(...)

  // 4、添加实例方法
  Vue.prototype.$myMethod = function () {...}
  Vue.prototype.$myProperty = xxxx
}

4、使用插件:

Vue.use(MyPlugin)

Vue.use会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。

五、过滤器

1、创建一个过滤器。
(1)全局过滤器:

Vue.filter('capitalize', function (value) {
  if (!value) return '';
  value = value.toString();
  return value.charAt(0).toUpperCase() + value.slice(1);
})

new Vue({
  // ...
})

(2)局部过滤器:在一个组件的选项中定义本地的过滤器:

new Vue({
  filters: {
    capitalize: function (value) {
      if (!value) return '';
      value = value.toString();
      return value.charAt(0).toUpperCase() + value.slice(1);
    }
  }
})

注意:当全局过滤器和局部过滤器重名时,会采用局部过滤器。
2、使用过滤器:插值表达式或v-bind

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

3、过滤器也可以接收额外参数,多个过滤器可以串联。

{{ message | filterA | filterB }}

{{ message | filterA('arg1', arg2) }}

4、过滤器并没有改变原本的数据,是产生新的对应的数据。

六、单文件组件

在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为*.vue文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。

七、component 标签的作用

<component :is="componentId"></component>

Vue 中提供了<component>内置组件,通过它的is属性可以实现像多标签界面中不同组件的动态切换,创建动态组件。

八、keep-alive 标签的原理是什么?有什么功能?

<!-- 失活的组件将会被缓存!-->
<keep-alive :include="allowList" :exclude="noAllowList" :max="amount">
  <component :is="componentId"></component>
</keep-alive>

Vue 中提供了<keep-alive>内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和<transition>相似,<keep-alive>是一个抽象组件,自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
作用:在组件切换过程中将状态保留在内存中,防止重复渲染 DOM,减少加载时间及性能消耗,提高用户体验性。
属性:
(1)include:可传字符串、正则表达式、数组。只有名称匹配的组件会被缓存。
(2)exclude:可传字符串、正则表达式、数组。任何名称匹配的组件都不会被缓存。
(3)max:数字。最多可以缓存多少组件实例。
生命周期:被包含在<keep-alive>中创建的组件,会多出两个生命周期钩子activateddeactivated
1、activated,在<keep-alive>组件激活时调用。
2、deactivated,在<keep-alive>组件停用时调用。