vue官网笔记

学习了vue后又重新过了一遍官网的教程,选择性地摘抄了一些自己觉得比较重要的知识点。以备后面查缺补漏用。


计算属性

计算属性mounted中,属性值函数将用作属性的getter函数。当函数中的依赖发生改变时候,其值会更新。

计算属性缓存vs方法

计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

所以对于性能开销比较大的属性要选择计算属性。

侦听器watch

当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

watch中的属性名是要侦听变化的变量名,属性值是一个函数,参数分别是变化后的值和变化前的值。

用 key 管理可复用的元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。(复用的元素就不会重新渲染)

如果要表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可。(这样元素就会重新渲染)

v-if vs v-show

v-if: “真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。惰性渲染,初始为false时候不会渲染。支持template元素。

v-show: 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。不支持template元素。

数组更新检测

变异方法:一些会改变原数组的数组方法再调用后能够被Vue侦听,从而触发视图更新。如push, pop, shift, unshift, splice, sort, reverse 等

非变异方法: 而一些返回新数组的方法,建议直接用新的数组替换旧数组。

Vue不能检测到的情况:

  1. 利用索引直接设置一个数组项(改用$set方法)
  2. 修改数组的长度(改用splice方法)

组件注册

Vue.component 属于全局注册,任何新建的Vue根实例都可以使用,而且注册的组件之间也可以相互使用。

components属于局部注册,注册的子组件之间不能互相使用。

prop验证

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String, // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

在组件上使用v-model

<input v-model="searchText">

等价于

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

当在组件上使用v-model时:

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

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

  • 将其 value 特性绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出

在代码中是这样的:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

非prop的特性

就是指在组件标签上添加的属性,它会在渲染出组件后自动添加到根元素上面。但是对于不同的属性,会进行替换或合并操作。例如是style或class属性就会进行合并操作,如果是type属性则会替换掉。

如果不希望根元素继承特性,则可以在组件的选项中设置inheritAttrs: false。(但是不会阻止style和class属性的继承)

从标签中继承过来的属性被保存在$attrs这个对象中,可以通过这个属性来决定这些属性被赋予在组件中的哪个元素中,这样在使用组件标签的时候就不需要担心哪个元素是真正的根元素:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
  `
})

具名插槽

目的是让组件起始和结束标签之间的内容能够对应到组件中具体的位置中去,在标签之间的内容使用template模板标签,添加一个v-slot:值,值是对应到组件中相应位置的slot标签上的name属性值(如果不加默认是default)。

可以将v-slot:缩写成#。

作用域插槽

如果想让插槽内容能够访问子组件中的数据,需要用到插槽prop,即在元素上用v-bind绑定一个属性,值是想要暴露给外面的内容,然后在template上面用v-slot带一个值,这个值是包含所有插槽prop的对象。

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

<!-- 还可以解构插槽prop -->
<current-user>
  <template v-slot:default="{user: {firstName}}">
    {{ firstName }}
  </template>
</current-user>

插槽作为可复用的模板

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    <!--
    我们为每个 todo 准备了一个插槽,
    将 `todo` 对象作为一个插槽的 prop 传入。
    -->
    <slot name="todo" v-bind:todo="todo">
      <!-- 后备内容 -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

<todo-list v-bind:todos="todos">
  <template v-slot:todo="{ todo }">
    <span v-if="todo.isComplete">✓</span>
    {{ todo.text }}
  </template>
</todo-list>

动态组件

使用标签和is特性可以来切换不同的组件。每次在组件之间切换的时候都会重新渲染,如果想保持组件的状态,避免反复重渲染导致的性能问题,在标签外面包裹一个标签即可。

依赖注入

在祖先元素上提供给后代组件数据和方法,使用provide和inject。实际上可以把依赖注入看作一部分“大范围有效的prop”,除了:1.祖先组件不需要知道哪些后代组件使用它提供的属性;2.后代组件不需要知道被注入的属性来自哪里。

// 祖先元素provide
provide: function () {
  return {
    getMap: this.getMap
  }
}

// 后代元素inject
inject: ['getMap']

但是依赖注入也有负面影响,其中很重要的一点是所提供的属性是非响应式的。因此尽量考虑vuex方案。

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

如果有些组件,内容包含了大量的静态内容。在这种情况下可以在根元素上添加v-once特性确保这些内容只计算一次然后缓存起来。

混入

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 全局注册定义一个使用混入对象的组件
Vue.mixin(myMixin)

// 局部注册混入对象
new Vue({
    mixins: [myMixin]
})

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”:

  • 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
  • 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
  • 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

自定义指令

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

// 注册局部指令
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

然后你可以在模板中任何元素上使用新的 v-focus 属性:

一个指令定义对象提供如下几个钩子函数:

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

钩子函数的参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。

  • binding:一个对象,包含以下属性:

    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。

  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

举例如下:

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})

new Vue({
  el: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})

// 输出结果
name: "demo"
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder

动态指令参数

指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

例子:

Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')
    el.style[s] = binding.value + 'px'
  }
})

new Vue({
  el: '#dynamicexample',
  data: function () {
    return {
      direction: 'left'
    }
  }
})

函数简写

当bind和update时触发相同行为,而不关心其它的钩子的情况下可以进行简写:

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

渲染函数

使用render函数来替代模板template。

举个例子,如果想写一个只能通过 level prop 动态生成标题 (heading) 的组件时,使用template的写法是这样的:

<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>

Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

这样显得非常冗长,如果是用render函数就会很简短:

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

其中render函数的一个参数createElement是一个用来生成虚拟DOM(VNode)的函数,其更准确的名字是createNodeDescription。

createElement参数

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

  // {Object}
  // 一个与模板中属性对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {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 特性
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  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',
  // 其它特殊顶层属性
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

JSX

使用JSX需要结合Babel插件,具体用法查看官网

import AnchoredHeading from './AnchoredHeading.vue'

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

如何追踪变化

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

data

posted @ 2019-08-22 21:40  simple小前端  阅读(787)  评论(0编辑  收藏  举报