Vue.js基础

Vue Cli

npm install -g @vue/cli
vue create my-projects

命名规则

? 建议在模板中都使用烤串方式,包括属性等,因为在html中是大小写不敏感的,组件的名称使用AaaBbb,函数及变量使用驼峰

<video-item :big-prop="1"></video-item>
// 虽然声明props时采用驼峰,但是vue中会做一个值的映射,会把驼峰转成烤串方式

使用JSX

// 可以直接这样
data() { ... },
render() {
  return <div></div>
}

Prop属性校验

props: {
  parentName: {
    type: String, // [String, Array]
    required: true,
    default: ‘1‘,
    validator(value) { return true }
  }
}

操作数据的局限性

  • 不能监测到对象属性的添加或删除,给Data新增属性也不行
  • 不能监测到数组长度的变化(通过改变length而增加的长度不能监测到)
  • 不是因为defineProperty的局限性,而是出于性能考虑,不会对数组的每个元素都监听(因为数组有可能是海量的数据,前期进行依赖收集会很耗费性能)
Vue.set(this.classmates, "2", { id: 1, name: ‘wang‘})
this.$set(this.classmates, "2", { id: 1, name: ‘wang‘})
Vue.delete(this.classmates, "2")
this.$set(this.classmates, "2")

? set其实就是又做了一层definePropert

生命周期

? 首先是beforeCreate,实例初始化后被调用,在 beforeCreate 钩子函数调用的时候,是获取不到 props 或者 data 中的数据的。然后会执行 created 钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。

? 接下来会先执行 beforeMount(模板编译后,渲染之前) 钩子函数,开始创建 VDOM,最后执行 mounted 钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。

? 接下来是数据更新时会调用的钩子函数 beforeUpdate (数据改变后,模板改变前)和 updated,这两个钩子函数没什么好说的,就是分别在组件更新前和更新后会调用。

? 另外还有 keep-alive 独有的生命周期,分别为 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

? 最后就是销毁组件的钩子函数 beforeDestroydestroyed。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。(解绑自定义事件event.$off,解绑自定义的dom事件如window.scroll)

组件通信

简单通信

? 父传子简单,即通过props,子传父就通过$emit事件的方式

// 父组件中监听add
<Cmp @add="myadd($event)"/>

// 子组件中触发add
this.$emit(‘add‘, good)

? 如果是兄弟组件,可以用它们共同的父组件或者根组件

this.$parent.$on(‘foo‘, msg => { console.log(msg) })
this.$parent.$emit(‘foo‘, ‘some message‘)

? 祖先和后代之间就是provide和inject(依赖注入的方式),其实主要用于UI库,但是它不是响应式的,也就是说父组件中修改他,子组件是不会更新的

provide() {
  return {
    foo: ‘foo‘
  }
}
inject:[‘foo‘]

任意组件

? 使用eventBus或者vuex

// 事件总线方法
// event.js
import Vue from ‘vue‘

export default new Vue()
// component
import event from ‘./event.js‘

event.$on(‘add‘, this.getName)
event.$emit(‘add‘, this.name)

beforeDestroy() {
  event.$off(‘add‘, this.getName)
}

? 此外还有一些非常规的通信方法,主要就是获取Vue实例,并访问它的一些属性和方法

$root
$parent
$children
$refs
this.$children[0].eat(‘some message‘)

? 注意点是$children是不保证子组件顺序的,比如某个子组件是异步组件

事件总线的原理

class Bus {
  constructor() {
    this.callbacks = {}
  }
  $on(name, fn) {
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name, args) {
    if(this.callbacks[name]) {
      this.callbacks[name].forEach(cb => cb(args))
    }
  }
}

$attrs & $listeners

? $attrs是指如果父组件传给子组件了一些属性,如果子组件没有用props接收,则会存在$attrs里面,当然是除了class和style。$listeners与$attrs是类似的,比如在父组件中定义一个事件,可以在子组件中用$listeners展开监听,在做组件的时候会很有应用价值

// 父组件中声明点击事件
<cq-el-input @click="handleClick"/>
// $listeners里面就是父组件传过来的这些事件
// 假设我们要二次封装饿了么的ui库, 我们肯定要把事件都写到外面传入到elementUI中,就可以这么接收
<template>
  <el-input v-on="$listeners"></el-input>
  ...
</template>

事件

<div @click="fn"></div> // 使用fn的时候默认会传event参数
<div @click="fn(‘1‘)"></div> // 使用fn时event会被放到第二个参数位置
<div @click="fn(‘1‘, $event)"></div> // $event显示的传入事件

事件修饰符

  • .stop:阻止冒泡
  • .prevent:阻止默认行为
  • .capture:使用捕获
  • .self:只有本身触发才执行
  • .once:只绑一次
  • .passive:该修饰符表示就是设置{passive:true},表示处理事件函数中不会调用preventDefault函数,就会减少了额外的监听,从而提高了性能;所以不能和.prevent修饰符一同使用,否则浏览器会报错
  • .keyCode
  • .enter
  • .down
  • .exact
  • .native:指定监听原生事件,而不是组件自定义事件
@click.stop
@keyup.13 // 13某个keyCode,当按下键是13的时候

常用指令

v-pre

? 忽略这个组件以及它的子元素的编译,比如你就想输出{{}}这个东西

v-for

? v-for也可以遍历object,v-for优先级高于v-if,先循环,然后判断是否要渲染,就会做三次重复的判断,是不好的

v-text

? {{ }} 这个的问题,有时候内容比较多的话会先显示{{ title }}这种样子,就可以用v-text解决

v-cloak

? 也可以解决上面的问题,不过需要配合css属性选择器

[v-cloak]: {
  display: none;
}
// 当渲染完后会自动将选择器里面的值设置为 display: block

v-once

? 只渲染一次,后期的更新不再渲染

指令修饰符

.lazy // 懒
.trim	// 去除首尾空格
.number // 转数字

v-model.lazy="val"

自定义指令

? 两种形式,全局和局部的

全局指令

Vue.directive(‘test‘, { ... })

局部指令

? 只在当前组件中可以使用

new Vue({
  directive: {
    name: { ... }
  }
})

? v-name即可使用

自定义指令的生命周期

  • bind:第一次绑定到元素的时候调用,只调用一次,可以做一些初始化,但是这时候页面可能还没有插入这个元素
  • inserted:被绑定元素被插入父节点时使用
  • update:所在组件更新时调用
  • componentUpdated:所在组件更新完成时调用
  • unbind:解绑时调用,只调用一次
// 会传入el和binding
el就是所在的元素
binding是指令配置细节,如name,value,oldValue,expression等

// 此外还有vnode和oldVnode

实现获取焦点指令

Vue.directive(‘focus‘, {
  inserted(el, binding) {
    binding.isFocus && el.focus()
  }
});

<input v-focus="isFocus" />

watch

? 监听引用类型必须要深度监听,引用类型拿不到oldValue,因为是引用类型,指向同一个地址,值被修改了变成一样了

// 正常的watch,当name变化后会触发
watch: {
  name(newValue, oldValue) { 
  	this.a = newValue // 刚开始不会执行,以后变了才会执行
  }
}
// 带选项的watch
watch: {
  a: { 
    handler: function() {}, // 拿不到oldValue
    deep: true, // 深度监听这么写
    immediate: true // 表示一开始就监听,否则在第一次渲染时不会触发handler方法
  }
}

此外需要注意watch和计算属性的区别

  • 前者是一个值变化了影响多个值;后者多个值影响一个值
  • 计算属性有缓存性
  • 计算属性不接受异步

? 在数据变化后执行异步操作或者开销比较大的操作建议使用watch

$nextTick有什么用

  • Vue是异步渲染
  • data改变之后DOM不会立即渲染,就跟state一样做一个整合,多次修改只会渲染一次
  • $nextTick会在DOM渲染完后被触发,以获取最新的DOM节点
this.list.push(666)

this.$nextTick(() => {
  console.log(domList.length)
})
// list变化dom也变化,拿到最新的dom列表项的长度

动态组件

? 有的时候组件类型不确定

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

? 这样就会根据data里的componentName动态的渲染组件,其中is必须是动态的

异步组件

? 体积比较大的组件很影响体验,如果全部同步打包进来体积会非常大而且加载会非常慢,严重影响性能。所以如果首页不需要加载我们就先不加载它

<template>
	<Demo v-if="showDemo"/>
</template>

<script>
  import Cmp from ‘./xxx‘ // 同步加载, 打包的时候也是打一个包
  export default {
    compoennts: {
      Demo: () => import(‘./xxx‘) // 异步加载
    }
  }
</script>

? 你发现当showDemo为true时,开发工具的NetWork里才会出现一个文件,打开以后发现就是这个Demo的打包文件,刚开始的时候并没有加载这个Demo

keep-allive

? 其实就是缓存组件,一般就是频繁切换的时候用

<keep-alive>
	<TabA />
  <TabB />
  <TabC />
</keep-alive>

渲染函数

render: function(createElement) {
  return createElement(tag, data, children)
}

函数式组件

? 如果组件没有管理任何状态,也没有监听任何传递给它的状态(watch),也没有生命周期,可以标记为函数式组件,意味着它没有状态(没有响应式),也没有实例,意味着更加轻量,性能更好

Vue.component(‘xxx‘, {
  functional: true
})

vue自定义v-model

// 1. parent.vue
<MyInput v-model="name"/>
// 2. MyInput.vue
<template>
	<input 
    type="text" 
    :value="name" 
    @input="$emit(‘change‘, $event.target.value)">
</template>
  
<script>
  export default {
    props: [‘name‘]
    model: {
      prop: ‘name‘, // 从父到子,对应props里的value
      event: change  // 从子到父
    }
  }
</script>

? 总结,1里的name变化,则2里的model里的prop对应的name也会变化,而触发change,则1里面的name也会变化

.sync

? 它的意思就是说将来会把它展开成一个事件

<text-document v-bind:title.sync="doc.title"></text-document>
// 触发此事件时必须用 this.$emit(‘update:title‘, newTitle)

? 相当于

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

??


  • v-for比v-if优先级高,每次循环都要if判断影响效率,建议用计算属性代替

  • 根组件不能挂到html或者body上,因为他会替换

  • attribute是html标签上的属性,property是dom(对象)的属性

Vue.js基础

上一篇:WebRTC学习(二)Web服务器搭建


下一篇:获取js函数执行时间的装饰器