Vue 基础

144 阅读16分钟

特性

DD3D37AA-9F94-43DE-872F-E084F2FB2500

Vue 是一套构建用户界面的渐进式、自底向上增量开发的MVVM 框架。核心是关注视图层,vue 的核心为了解决数据的绑定问题,为了开发大型单页面应用和组件化,所以vue 的核心思想是数据驱动和组件化。

MVC

MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。

其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。

Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。

MVVM

MVVM 分为 Model、View、ViewModel:

  • Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;
  • View 代表 UI 视图,负责数据的展示;
  • ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,ViewModel 层通过双向数据绑定将 View 层和 Model 层连接了起来。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。

这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作 DOM。

MVP

MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。

在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。

MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的 Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。

模板语法

插值

  • 文本

    // “Mustache”语法 (双大括号) 
    <span>Message: {{ msg }}</span>
    
    <span>Message: {{ msg }}</span>
    
    <span>Message: { this.msg }</span>
    
    // 两者本质上是语法糖,最终都会编译为:
    createElement('span', `Message: ${this.msg}`)
    
  • 原始HTML

    双大括号会将数据解释为普通文本,而非HTML 代码。输出真正的HTML,需使用v-html指令。

    <p>Using mustaches: {{ rawHtml }}</p>
    <p>Using v-html directive: <span v-html="rawHtml"></span></p>
    

    Using mustaches:<span style="color: red">This should be red.</span> Using v-html directive: This should be red.

  • Attribute

    对于布尔 attribute (它们只要存在就意味着值为 true),但结合v-bind 指令使用略有不同。

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

    如果 isButtonDisabled 的值是 nullundefinedfalse,则 disabled attribute 甚至不会被包含在渲染出来的 <button> 元素中。

  • JS 表达式

Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。

  • 模版语法(HTML 的扩展)
  • 数据绑定使用Mustache 语法

JSX

  • JavaScript 的语法扩展
  • 数据绑定使用单引号

指令

语法糖或者标志位。

v-if/v-show

v-if

  • 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;
  • 惰性:如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。
  • 适用于在运行时很少改变条件,不需要频繁切换条件的场景;

v-show

  • 不管初始条件是什么,元素总是会被渲染,并且只是简单的基于 CSS 的 “display” 属性进行切换。
  • 适用于需要非常频繁切换条件的场景。

v-show 相比于 v-if 的性能优势是在组件的更新阶段,如果仅仅是在初始化阶段,v-if 性能还要高于 v-show,原因是在于它仅仅会渲染一个分支,而 v-show 把两个分支都渲染了,通过 style.display 来控制对应 DOM 的显隐。

在使用 v-show 的时候,所有分支内部的组件都会渲染,对应的生命周期钩子函数都会执行,而使用 v-if 的时候,没有命中的分支内部的组件是不会渲染的,对应的生命周期钩子函数都不会执行。

v-model

v-model 它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 就是Vue 的双向绑定指令,能将页面上控件输入的值同步更新到相关绑定的data 属性;也会在更新data 绑定属性时,更新页面上输入控件的值。

【原理】

  • 一方面modal 层通过defineProperty 来劫持每个属性,一旦监听到变化通过相关的页面元素更新
  • 另一方面通过编译模板文件,为控件的v-model 绑定input 事件,从而页面输入能实时更新相关data 属性值
<input type="text" v-model="value"></input>

<!-- 等同于 -->
<input type="text" :value="value" @input="value = $event.target.value"></input>

// $event 指代当前触发的事件对象
// $event.target 指代当前触发的事件对象的dom
// $event.target.value 当前dom 的value

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

  • text 和textarea 元素使用value 属性和input 事件
  • Checkbox 和radio 使用checked 属性和change 事件
  • select 字段将value 作为prop 并将change 作为事件
v-text/{{}}/v-html

{{}} 将数据解析为纯文本,不能显示输出html,在渲染的数据比较多的时候,可能会把大括号显示出来,俗称屏幕闪动

v-htm 可以渲染输出html

V-text 将数据解析为纯文本,不能输出真正的html,与花括号的区别是在页面加载时不显示双花括号

动态参数
<!-- 使用动态参数为一个动态的事件名绑定处理函数 -->
<a v-on:[eventName]="doSomething"> ... </a>

当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus。

<!-- 绑定多个方法,用键值对的形式 -->
<button v-on="{ click: onClick, focus: onFocus, blur: onBlur }"></button>

动态参数表达式有语法约束:空格和引号,放在 HTML attribute 名里是无效的。

<!-- 这会触发一个编译警告 -->
<!-- 用计算属性替代复杂表达式 -->
<a v-bind:['foo' + bar]="value"> ... </a>

避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写。

事件处理

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法。

<button :click="warn('Form cannot be submitted yet.', $event)">Submit</button>

methods: {
	warn: function(message, event) {
		// 现在可以访问原生事件对象
	}
}
修饰符
// 表单修饰符
.lazy // 改变后触发,光标离开input 输入框的时候值才会改变
.number // 将输出字符串转为number 类型
.trim // 自动过滤用户输入的首尾空格
// 事件修饰符
<!-- 阻止单击事件冒泡,相当于原生JS 中的 event.stopPropagation() -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面,.prevent 相当于原生JS 中的 event.preventDefault() -->
<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>
<!-- 只当事件在该元素本身(不包括子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>

<!-- click 事件只执行一次,2.1.4版本新增 -->
<a v-on:click.once="doThis"></a>
// 键盘修饰符
.enter .tab .esc .space .up .down .left .right

// 系统修饰符
.ctrl .alt .shift .meta
自定义指令

自定义指令中传递的3 个参数:

  • el:指令所绑定的元素,可以用来直接操作DOM
  • binding:一个对象,包含指令的很多信息
  • vnode:vue 编译生成的虚拟节点

自定义指令的5 个钩子函数:

  • bind 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作
  • inserted 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document 中)
  • update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
  • componentUpdated:被绑定元素所在模板完成一次更新周期时调用
  • unbind:只调用一次,指令与元素解绑时调用
<button v-append-text="`hello ${number}`"></button>

export default {
    directives: {
        appendText: {
            bind() {
                console.log("bind")
            },
            inserted(el, binding) {
                el.appendChild(document.createTextNode(binding.value))
                console.log("inserted", el, binding)
            },
            update() {
                console.log("update")
            },
            componentUpdated() {
                el.removeChild(el.childNodes[el.childNodes.length - 1])
                el.appendChild(document.createTextNode(binding.value))
                console.log("componentUpdated")
            },
            unbind() {
                console.log("unbind")
            }
        }
    }
}
<template>
	<input type="text" v-model="text" placeholder="仅可填入正整数数字" v-my-text=“{ key: 'text', maxVal: '1000'}”></input>
</template>
<script>
	export default {
        data() {
            return {
                text: ''
            }
        },
        directives: {
            myText: (el, binding, vnode) => {
                el.handler = function() {
                    el.value = el.value.replace(/\D+/g, '')
                    // 根据设置的规则,进行判断处理
                    if (binding.value.maxVal && el.value > parseInt(binding.value.maxVal)) {
                        el.value = parseInt(binding.value.maxVal)
                    }
                    // 根据指令调取位置设置的规则key,进行全局上下文赋值
                    vnode['context'][binding.value.key] = el.value
                }
                el.addEventListener('input', el.handler)
            }
        }
    }
</script>
// ./directive/auth.js

// 做成可以自己去注册引用的指令(如权限控制指令)
function install(Vue, options = {}) {
    Vue.directive(options.name || 'auth', {
        inserted(el, binding) {
            if (!check(binding.value)) {
                el.parentNode && el.parentNode.removeChild(el)
            }
        }
    })
}

export default { install }
// 全局引入
import Auth form "./directive/auth.js"
Vue.use(Auth)


<button v-auth="['admin']"></button>

computed

在使用计算属性的时,函数名和data 数据源中的数据不能同名:

因为不管是计算属性、data、props 都会被挂载在vm 实例上,因此这三个都不能同名。

// computed 属性默认只有 getter ,也可以提供一个 setter

let vm = new Vue({
  el: '#app',
  data: {
    name: 'Google',
    url: '<http://www.google.com>'
  },
  computed: {
    site: {
      // getter
      get: function () {
        return this.name + ' ' + this.url
      },
      // setter
      set: function (newValue) {
        const names = newValue.split(' ')
        this.name = names[0]
        this.url = names[names.length - 1]
      }
    }
  }
})
// 调用 setter, vm.name 和 vm.url 也会被对应更新
vm.site = '菜鸟教程 <http://www.runoob.com>';
document.write('name: ' + vm.name);
document.write('<br>');
document.write('url: ' + vm.url);

// 在运行 vm.site = '菜鸟教程 [<http://www.runoob.com>](<http://www.runoob.com/>)'时,setter 会被调用, [vm.name](<http://vm.name/>) 和 vm.url 也会被对应更新。

watch

每当监听的数据变化时都会执行回调进行后续操作。

watch 中可以执行任何逻辑,如函数节流,Ajax 异步获取数据,甚至操作DOM

【运用场景】

需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作。

样式绑定

class 与 style 是 HTML 元素的属性,用于设置元素的样式,可以用 v-bind 来设置样式属性。

表达式的结果类型除了字符串之外,还可以是对象数组

class

<!-- 1 传对象 -->
<div :class="{ active: isActive }"></div>

<!-- 2 在对象中传入更多属性,可以与普通的 class 共存 -->
<!-- 如果 hasError 的值为 true,class 列表将变为 "static text-danger" -->
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>

<!-- 3 绑定的数据对象不必内联定义在模板里,可以在data 中定义或者使用计算属性 -->
<div v-bind:class="classObject"></div>

<!-- 4 数组 -->
<div v-bind:class="[activeClass, errorClass]"></div>

data: {
	activeClass: 'active',
    errorClass: 'text-danger'
}

=> <div class="active text-danger"></div>


<!-- 5 三元表达式 -->
<!-- errorClass 是始终存在的,isActive 为 true 时添加 activeClass 类 -->
<div v-bind:class="[errorClass, isActive ? activeClass : '']"></div>

data: {
	isActive: true,
	activeClass: 'active',
	errorClass: 'text-danger'
}

style

:style 的对象是一个 JavaScript 对象。

<!-- 1 CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名 -->
<div :style="{ backgroundColor: 'red', 'font-size': '14px' }"></div>

<!-- 2 直接绑定到一个样式对象,在data 中定义或者使用计算属性 -->
<div :style="styleObject"></div>

<!-- 3 数组语法 -->
<div v-bind:style="[baseStyles, overridingStyles]"></div>

组件

组件是可复用的Vue 实例。

组件是对一棵DOM 树的抽象,一个组件可以包含若干个原生DOM 标签。

prop

prop 定义了这个组件有哪些可配置的属性,可通过父组件传递进来。

如果是对象类型,比如对象或数组,它的默认值必须从一个工厂函数获取。

props: {
    total: {
        type: Number,
        default: 0
    },
    columns: {
        type: Array,
        default() {
            return []
        }
    },
    value: {
        type: [String, Number, Boolean],
        default: false
    }
}

// 对象
// 后面不加括号的话是一个空函数体,没有返回值。加了才是一个有意义的函数体,返回值是 {}。
default: () => ({})
// 数组
default: () => []
单向数据流

父级 prop 的更新会向下流动到子组件中,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。

子组件内部不能直接修改父组件传递过来的数据,可以使用data 和computed 解决。

// 场景1:定义本地的data 属性接收prop 初始值
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

// 场景2:需要对prop 进行转换,使用计算属性。
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

【注意】

JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

插槽

Vue 实现了一套内容分发的API,这套API 的设计灵感源自Web Components 规范草案,将元素作为承载分发内容的出口。插槽是传递复杂内容的一种方式。

在 2.6.0 中,v-slot 指令取代了 slotslot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。

插槽的作用是子组件提供了可替换模板,父组件可以更换模板的内容。

具名插槽
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

<base-layout>
  <template v-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 v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

<!-- 任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。 -->
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

<!-- 两者渲染效果相同 -->
<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

具名插槽的缩写(2.6.0 新增)

把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

如果希望使用缩写,必须始终明确插槽名。

<current-user #default="{ user }">
	{{ user.firstName }}
</current-user>
作用域插槽

作用域插槽给了子组件将数据返给父组件的能力,子组件一样可以复用,同时父组件也可以重新组织内容和样式。

<!-- 子组件 -->
<!-- 为了让 user 在父级的插槽内容中可用,将 user 作为 <slot> 元素的一个 attribute 绑定上去 -->
<!-- 绑定在 <slot> 元素上的 attribute 被称为插槽 prop -->
<span>
    <slot :user="user">
        {{ user.lastName }}
    </slot>
</span>

<!-- 父组件 -->
<!-- 在父级作用域中,使用带值的 v-slot 来自定义插槽 prop 的名字 -->
<current-user>
    <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
    </template>
</current-user>

当被提供的内容只有默认插槽时,组件标签才可以被当作插槽的模板来使用。

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

<!-- 或者,更简单的写法 -->
<current-user v-slot="slotProps">
	{{ slotProps.user.firstName }}
</current-user>

注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。

解构插槽Prop

作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:

 function(slotProps) {
     // 插槽内容
 }

这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。

所以在支持的环境下 (单文件组件现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop:

 <current-user v-slot="{ user }">{{ user.firstName }}</current-user>

它同样开启了 prop 重命名等其它可能,例如将 user 重命名为 person

 <current-user v-slot="{ user: person }">{{ user.firstName }}</current-user>

动态组件

运用场景:在不同组件之间进行动态切换,比如在一个多标签的界面中。

 <!-- 组件会在 `currentTabComponent` 改变时改变 -->
 <component v-bind:is="currentTabComponent"></component>

currentTabComponent 可以包括:已注册组件的名字或一个组件的选项对象。

自定义事件

 <my-component :my-event="doSomething"></my-component>

v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。

推荐使用kebab-case 事件名。

函数式组件

如权限组件,仅提供权限控制的功能,可以使用函数式组件提高渲染性能。

 export defalut {
     functional: true,
     render: (h, ctx) => {
         return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props || {})
     }
 }
 // 可以在全局注册
 Vue.component('AAA', 'BBB')
 /* 页面使用,包裹需要做权限控制的组件 */
 /* 除了函数式组件这种写法,可以通过自定义指令方式(不过只会在第一次的时候触发,如果权限是动态更改的则不适合)*/
 <Auth :AAA="...">
     <SettingDrawer />
 </Auth>

递归组件

1、如果某个组件通过组件名称引用它自己,这种情况就是递归组件。

2、实际开发中类似Tree、Menu 这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的,这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景。

3、使用递归组件时,由于开发者并未也不能在组件内部导入它自己,所以设置组件name 属性,用来查找组件定义,如果使用SFC,则可以通过SFC 文件名推断。组件内部通常也要有递归结束条件,如model.children 这样的判断。

4、查看生成渲染函数可知,递归组件查找时会传递一个布尔值给resolveComponent,这样实际获取的组件就是当前组件本身。

【源码 - v3】

github1s.com/vuejs/core/…

github1s.com/vuejs/core/…

递归组件编译结果中,获取组件时会传递一个标识符_resovleComponent("Comp, true")

const _component_Comp = _resovleComponent("Comp, true")

就是在传递maybeSelfReference

export function resolveComponent(
    name: string,
    maybeSelfReference?: boolean
): ConcreteComponent | string {
    return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
}

resolveAsset 中最终返回的是组件本身。

异步组件

import { defineAsyncComponent } from 'vue'

// defineAsyncComponent 定义异步组件
const AsyncComp = defineAsyncComponent(() => {
    // 加载函数返回Promise
    return new Promise((resolve, reject) => {
        // ...可以从服务器加载组件
        resolve()
    })
})

// 借助打包工具实现ES模块动态导入
const AsyncComp = defineAsyncComponent(() => import('./componnets/MyComponent.vue'))

1、在大型应用中,需要分割应用为更小的块,并且在需要组件时再加载它们。

2、不仅可以在路由切换时懒加载组件,还可以在页面组件中继续使用异步组件,从而实现更细的分割力度。

3、使用异步组件最简单的方式是直接给defineAsyncComponent 指定一个loader 函数,结合ES 模块动态导入函数import 可以快速实现。甚至可以指定loadingComponent 和errorCompnent 选项从而给用户一个很好的加载反馈。另外vue 3中还可以结合Suspense 组件使用异步组件。

4、异步组件容易和路由懒加载混淆,实际上两者不是一个东西。异步组件不能被用于定义懒加载路由上,处理它的是vue 框架,处理路由组件加载的是vue-router。但是可以再懒加载的路由组件中使用异步组件。

【源码】

defineAsyncComponent 是一个工厂函数,返回一个包装组件。包装组件根据加载器的状态决定渲染什么内容。接收一个加载器或者组件定义,返回一个全新组件。

github1s.com/vuejs/core/…

过滤器

Vue.js 允许自定义过滤器,用作文本格式化。

过滤器可以用在两个地方:双花括号插值和 v-bind 表达式

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

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

<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | formatId"></div>
// 在一个组件的选项中定义本地的过滤器
filter: {
	capitalize: function() {}
}

// 在创建 Vue 实例之前全局定义过滤器
Vue.filter('capitalize', function (value) {})

// 当全局过滤器和局部过滤器重名时,会采用局部过滤器。
<!-- 过滤器可以串联 -->
{{ message | filterA | filterB }}
<!-- 过滤器是 JavaScript 函数,可以接受参数 -->
<!-- filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。 -->
{{ message | filterA('arg1', arg2) }}

mixins 混入


// utils/mixins.js
const mixins = {
    filters: {
        // 日期格式化
        dateFormat(val) {
            ...
        }
    }
}

export default mixins
import mixins from '@utils/mixins'
<template>
	<div>{{1000 | dateFormat}}</div>
</template>

<script>
	export default {
        name: 'xxx',
        mixins: [mixins]
    }
</script>

属性顺序

1 副作用(触发组件外的影响)

  • el

2 全局感知(要求组件以外的知识)

  • name
  • parent

3 组件类型(更改组件的类型)

  • functional

4 模板修改器(改变模板的编译方式)

  • delimiters
  • comments

5 模板依赖(模板内使用的资源)

  • components
  • directives
  • filters

6 组合(向选项里合并属性)

  • extends
  • mixins

7 接口(组件的接口)

  • inheritAttrs
  • model
  • props/propsData

8 本地状态(本地的响应式属性)

  • data:如果是引用类型,当多个组件共用一个数据源时,一处数据改变,所有的组件数据都会改变,所以要利用函数通过return 返回对象的拷贝,让每个组件实例都有自己的作用域,不相互影响
  • computed

9 事件(通过响应式事件触发的回调)

  • watch

  • 生命周期钩子 (按照它们被调用的顺序)

    • beforeCreate
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • activated
    • deactivated
    • beforeDestroy
    • destroyed

10 非响应式的属性(不依赖响应系统的实例属性)

  • methods

11 渲染(组件输出的声明式描述)

  • template/render
  • renderError

scoped

vue 文件中的 style 标签上,有一个特殊的属性:scoped。当一个 style 标签拥有 scoped 属性时,它的 CSS 样式就只能作用于当前的组件。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有 style 标签全部加上了 scoped,相当于实现了样式的模块化。

【scoped 的实现原理】

vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现的。PostCSS 给一个组件中的所有 DOM 添加了一个独一无二的动态属性,然后,给 CSS 选择器额外添加一个对应的属性选择器来选择该组件中 DOM,这种做法使得样式只作用于含有该属性的 DOM,即组件内部 DOM

<template>
    <div class="example">hi</div>
</template>

<style scoped>
.example {
    color: red;
}
</style>
// 转译后
<template>
    <div class="example" data-v-5558831a>hi</div>
</template>

<style>
.example[data-v-5558831a] {
    color: red;
}
</style>

样式穿透

方法一

使用 ::v-deep 操作符( >>> 的别名)

如果希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,可以使用 >>> 操作符:

上述代码将会编译成:

后面的类名没有 data 属性,所以能选到子组件里面的类名。

有些像 Sass 之类的预处理器无法正确解析 >>>,所以需要使用 ::v-deep 操作符来代替。

方法二

定义一个含有 scoped 属性的 style 标签之外,再定义一个不含有 scoped 属性的 style 标签,即在一个 vue 组件中定义一个全局的 style 标签,一个含有作用域的 style 标签:

此时,只需要将修改第三方样式的 css 写在第一个 style 中即可。

方法三

在组件的外层 DOM 上添加唯一的 class 来区分不同组件,在书写样式时就可以正常针对针对这部分 DOM 书写样式。

ref

ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。

【特点】

1、如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素

2、如果用在子组件上,引用就指向组件实例

【常见的使用场景】

1、基本用法,本页面获取 DOM 元素

2、获取子组件中的 data

3、调用子组件中的方法

name

1、可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )

2、可以通过 name 属性实现缓存功能(keep-alive

3、可以通过 name 来识别组件(跨级组件通信时非常重要)

4、使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的

mixin、extends 覆盖逻辑

(1)mixin 和 extends mixin 和 extends 均是用于合并、拓展组件的,两者均通过 mergeOptions 方法实现合并。

  • mixins 接收一个混入对象的数组,其中混入对象可以像正常的实例对象一样包含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
  • extends 主要是为了便于扩展单文件组件,接收一个对象或构造函数。

(2)mergeOptions 的执行过程

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
  • 对未合并的选项,进行判断
 if (!child._base) {
     if (child.extends) {
         parent = mergeOptions(parent, child.extends, vm)
     }
     if (child.mixins) {
         for (let i = 0, l = child.mixins.length; i < l; i++) {
             parent = mergeOptions(parent, child.mixins[i], vm)
         }
     }
 }
  • 合并处理。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里。
  • 返回合并结果 options。

assets、static

相同点: assets 和 static 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点

不相同点: assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。

static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。

建议: 将项目中 template 需要的样式文件 js 文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如 iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。

template、jsx

对于 runtime 来说,只需要保证组件存在 render 函数即可,而有了预编译之后,只需要保证构建过程中生成 render 函数就可以。

在 webpack 中,使用 vue-loader 编译.vue 文件,内部依赖的 vue-template-compiler 模块,在 webpack 构建过程中,将 template 预编译成 render 函数。与 react 类似,在添加了 jsx 的语法糖解析器 babel-plugin-transform-vue-jsx 之后,就可以直接手写 render 函数。

所以,template 和 jsx 的都是 render 的一种表现形式,不同的是:JSX 相对于 template 而言,具有更高的灵活性,在复杂的组件中,更具有优势,而 template 虽然显得有些呆滞。但是 template 在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。