Vue3.0
Vue3中兼容Vue2中定义组件的写法,所以只需要将入口文件main.js中创建Vue实例的代码替换为使用和Vue3中新引入的createApp方法,来创建应用程序实例的方式即可。
Vue 2 中main.js:
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
Vue 3 main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Vue 3.0的新特性:
Composition API(组合式API):
组件变得越来越大时,逻辑关注点的列表也增长了(文件结构复杂)。导致组件难以阅读和理解,(在处理单个逻辑关注点时,需要不断地“跳转”相关代码的选项块)
Composition API:将与同一个逻辑关注点相关的代码配置在一起。
使用Composition API的位置被称为setup(在setup中使用Composition API)
使得组件的大部分内容可以通过
setup()方法进行配置
setup组件选项
setup组件选项在创建组件之前执行,一旦props被解析,并充当合成API的入口点。
注:由于在执行setup是尚未创建组件实例,因此在setup选项中没有this。即:除了props之外,将无法访问组件中声明的任何属性————本地状态、计算属性或方法等
// 子组件中
import { APIName } from '@/api'
import { ref, onMounted } from 'vue'
// in our component
export default {
setup (props) {
const repositories = ref([]) // 定义一个变量
const getUserRepositories = async () => { // 定义一个方法
repositories.value = await APIName(props.user)
}
onMounted(getUserRepositories) // 生命周期钩子 当实例mounted后调用getUserRepositories方法
return {
repositories, // 返回一个data
getUserRepositories // 返回一个method
}
}
}
单文件组件Composition API语法糖:
当组件可以使用组合API后,setup往往成为了唯一会用到的组件属性,因此利用语法糖简化setup的写法
<script setup>
import { ref } from 'vue'
export const count = ref(0)
export const inc = () => count.value++
</script>
使用场景:
vue 2.0中通过组件data,以及methods的方法来定义一些当前组件的数据;
vue 3.0中通过ref或者reactive(反应性的)创建响应式对象,methods的方法也写在setup中并return;
import {ref,reactive} from 'vue'
setup(){
const name = ref('test')
const state = reactive({
list: []
})
const md = () => { console.log('抛出子组件的方法') }
return {
name,
state,
md
}
}
ref将给定的值创建一个响应式的数据对象并赋初始值,reactive可以直接定义复杂响应式对象
无法使用EventBus:
vue 2.0中使用EventBus实现组件通信:
vue的四核事件方法 $on、$emit、$off、$once
var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
this.$EventBus.$on('eventName', (val) => { this.modifyNodeHandle(value); })
this.$EventBus.$emit('eventName', {
data: result,
type: 'root'
})
// 或者使用this.$root.$on()使用
this.$root.$off('eventName', cb) // cb可选,移除事件中心的某个事件,cb指移除时触发的方法
this.$root.$once('eventName', cb) // 同$on,但$once只触发一次,一旦触发,监听器就会被移除
注:vue处理边界
$root、$parent、$refs、$children:
Vue子组件可以通过$root属性访问父组件实例的属性和方法(和$parent的区别:如果存在多级子组件,$parent访问到的是最近一级的父组件,$root访问到的是根父组件),因此使用this.$root.$on可以直接监听vue实例的事件,而不用再创建新的vue实例
setup中使用mitt方案来代替,:
import mitt from 'mitt'
const emitter = mitt()
// 添加监听事件
emitter.on('foo', e => console.log('foo', e) )
// 使用监听事件
emitter.emit('foo', { a: 'b' })
vue 3.0不再支持prototype的方式给Vue绑定静态方法,可以通过app.config.globalProperties.mitt = () => {}方案
setup()中使用props和this:
在vue 2.0中,组件的方法中可以通过this获取到当前组件的实例,并执行data变量的修改,方法的调用,组件通信等,但在3.0中,setup()在beforeCreate和created时机就已经调用,无法使用2.0中的this,但是可以通过接收setup(props, ctx)的方法,获取当前组件的props和实例
props: {
name: String,
},
setup(props,ctx) {
console.log(props.name)
ctx.emit('event')
},
ctx和2.0的this不完全一样,ctx选择性的暴露了一些property(attrs,emit,slots)
子组件中watch来监听对象改变
2.0中可以采用watch来监听对象属性是否有改动;
3.0中在setup()中,可以使用watch来监听;
import {watch} from 'vue'
setup(){
let state = reactive({
name: 'a'
})
watch(
() => state.name,
(val, oldVal) => {
console.log(val)
}
)
state.name = 'b'
return {
state
}
}
如果watch的是一个数组对象,调用.push添加数据不会触发watch方法,必须重新给数组赋值
setup中使用computed计算属性:
const storeData = computed(() => store.state.storeData)
return { storeData }
总结
代码依据业务逻辑,完全分散成多个管理类,setup只需要负责加载和整合即可,setup里面也不会有很多代码。
如,在单独的js文件中定义:
import { ref } from 'vue'
// 帖子列表的管理类
export function manageArticleForm () {
const modelForm = ref({
title: '这是帖子标题',
content: '帖子内容',
sendTime: '2020-10-20'
})
return {
articleForm: modelForm,
}
}
然后在子组件中引用:
import { manageArticleForm } from './bbs-manageArticleForm.js'
setup() {
// 表单
const { articleForm } = manageArticleForm()
// 返回给view
return {
articleForm
}
}
单文件组件状态驱动的CSS变量:
组件状态驱动css变量(style vars)
可以在运行时根据组件状态来动态更新样式(向css传递参数)
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style vars="{ color }">
.text {
color: var(--color);
}
</style>
单文件组件(style scoped)包含全局规则或只针对插槽内容的规则:
带有scoped属性的style不在只能作用域当前单文件组件,可以通过深度选择器、插槽选择器、全局选择器拥有了更改其他范围样式的能力。
<style scoped>
/* deep selectors,样式将在子组件中生效 */
::v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
/* targeting slot content,用于替代那些被放在HTML模板中的元素 */
::v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
/* one-off global rule,全局生效 */
::v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>
Vue 3.0的重大改变
引入createApp
vue2中没有“app”的概念,我们定义的应用程序只是通过new Vue()创建的根Vue实例。从同一个Vue构造函数创建的每个根实例共享相同的全局配置,因此全局配置使得在测试期间很容易意外的污染其他测试用例。需要谨慎存储原始全局配置(如:每次测试后恢复,如重置Vue.config.errorHandler)。有些API像Vue.use以及Vue.mixin甚至连恢复效果都没有,使得涉及插件的测试很棘手。
// createApp:
import { createApp } from 'vue'
const app = createApp({})
调用createApp返回一个应用实例,应用程序实例暴露当前全局API的子集,任何全局改变Vue的行为API会移动到应用实例上。其他不全局改变行为的全局API被命名为exports。
全局和内部API已重构为可tree-shakable
Tree shaking是一个通常用于描述移除js上下文中的未引用代码行为的术语
依赖于es6中的import和export语句,用来检测代码模块是否被导出、导入,且被js文件使用
在现代js应用程序中,使用模块打包(webpack或Rollup)将多个js文件打包为单个文件时自动删除未引用的代码。是最终文件结构和最小化大小。
2.0语法
// 全局 API Vue.nextTick() 不能tree-shaking
import Vue from 'vue'
Vue.nextTick(() => {
// 一些和DOM有关的东西
})
3.0语法
全局API只能作为es模块构建的命名导出进行访问,如果模块绑定器支持tree-shaking,则Vue应用程序中未使用的全局api将从最终捆绑包中消除,从而获得最佳的文件大小。
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
受影响的API包括:
- Vue.nextTick
- Vue.observable (用Vue.reactive替换)
- Vue.version
- Vue.compile
- Vue.set
- Vue.delete
Vue.observable
Vue.observable:进行状态管理。随着组件的细化,出现多组件状态共享的情况,Vuex可以解决,但对于小规模引用,Vuex会导致代码范睢冗余。vue2.6增加的Observable API,通过使用这个api可以应对一些简单的跨组件数据状态共享的问题。
observable()方法,用于设置监控属性,用以监控viewModule中的属性值的变化,从而可以动态改变某个元素的值,监控属性的类型的不变量是一个函数,通过返回一个函数给viewModule对象中的属性,从而来监控该属性。
在src目录下建立
store.js,组件中使用提供的store和mutation方法,实现多个组件共享数据状态。
//store.js
import Vue from 'vue';
export let store = Vue.observable({count:0,name:'李四'});
export let mutations={
setCount(count){
store.count = count;
},
changeName(name){
store.name = name;
}
}
// 子组件中
import HomeHeader from '../components/HomeHeader'
import {store,mutations} from '@/store'
export default {
data () {
return {
name1:'主页的name'
}
},
computed:{
count(){
return store.count
},
name(){
return store.name
}
},
methods:{
// 方法的属性表达式写法,无大括号时可直接用':'
setCount: mutations.setCount,
changeName: mutations.changeName
}
}
// HTML中
<button @click="setCount(count+1)">+1</button> // 调用store中的mutation方法
<button @click="setCount(count-1)">-1</button>
<div>store中count:{{count}}</div> // 显示store中的count值
<button @click="changeName(name1)">父页面修改name</button> //调用store方法修改name
<div>store中name:{{name}}</div> // // 显示store中的name值
// 在其他子组件中可以共同对name或者count进行操作,所有组件共享store中Vue.observable的值
当前项目vue版本低于2.6,要使用Vue.observable(),就必须要升级,升级 vue 和 vue-template-compiler,两者的版本需要同步,如果不同步项目会报错。
`npm update vue -S 或者 yarn add vue -S
npm update vue-template-compiler -D 或者 yarn add vue-template-compiler -D`
Vue.version
提供字符串形式的Vue安装版本号。(对于社区的插件和组件来说很有用,可以根据不同的版本号采取不同策略)
var version = Number(Vue.version.split('.')[0])
if (version === 2) {
// Vue v2.x.x
} else if (version === 1) {
// Vue v1.x.x
} else {
// Unsupported versions of Vue
}
Vue.compile
将一个模板字符串变异成render函数。只在full build的时候可用
初始化函数,最后一步进行了vm.$mount(el)的操作,而这个$mount在这两个地方定义过,分别在entry-runtime-with-compiler.js(简称:eMount)和runtime/index.js(简称:rMount)。eMount最后还是调的rMount,不过eMount做了一定的操作,
Vue.compile(tempalte)
1、用法:
编译模板字符串并返回包含渲染函数的对象。只在完整版中才有效(并不是所有Vue.js的构建版本都存在Vue.compile)。
var res = Vue.compile('<div><span>{{msg}}</span></div>');
new Vue({
data:{
msg:'hello'
},
render:res.render
})
2、实现:
Vue.compile方法只需要调用编译器就能实现。Vue.compile = compileToFunctions;
compileToFunctions方法可以将模板编译成渲染函数
编译的三个步骤:
- parse(解析):需要将template转换成抽象语法树(AST)。
- optimizer(优化器):对这个抽象语法树进行静态节点的标记,就可以优化渲染过程。
- generateCode(生成代码):根据抽象语法树(AST)生成一个render函数字符串。
解析器
解析器中有个非常重要的概念(AST)
ASTNode分为几种不同类型,
解析以下代码:
<div id="demo">
<h1>Latest Vue.js Commits</h1>
<p>{{1 + 1}}</p>
</div>
将生成如下的AST:
在parse函数中,先是定义了非常多的全局属性及函数,然后调用了parseHTML,这个函数会不断解析模板,填充root,最后返回root(AST)。
parseHTML:最重要的是while循环中的代码,在while中,使用html.indexOf('<')去匹配:
- __等于0: __代表是注释、调换注释、doctype、开始标签,结束标签
- __大于等于0:__说明是文本、表达式
- __小于0:__表示html标签解析完了,可能会剩下一些文本、表达式
parse函数不断重复这个工作,将template转换成AST,解析过程中会对标签之间的空格做优化处理(有些元素之间的空格是没用的)
优化器
优化器的目的是去找出AST中纯静态的子树:
把纯静态的子树提升为常量,每次重新渲染的时候就不需要创建新的节点了,在patch的时候就可以跳过他们。
第一遍遍历:标记静态的节点;第二遍遍历:标记静态根节点(不能是叶子节点,需要有子节点且是静态的——如果一个静态节点只拥有一个半子节点并且这个子节点是文本节点,就不做静态处理,直接渲染)
代码生成器
将AST转换成render函数字符串。
最重要的数genElement这个函数,针对不同的指令、属性,会选择不同的代码生成函数。最后按照AST生成拼接成一个字符串。
vue的核心可分为三个大块:数据处理和双向绑定、模板编译、虚拟dom
vue.use
Vue.use(plugin);
Vue.use(VueLazyLoad, {
error: './static/error.png', // 包含install方法的对象
loading: './static/loading.png'
});
1、用法:安装Vue.js插件,如果插件是一个对象,必须提供install方法。如果插件是一个函数,他会被作为install方法,调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。
2、作用:注册插件,此时只需要调用install方法并将vue作为参数传入即可。
注:插件的类型,可以是install方法,也可以是一个包含install方法的对象;插件只能被安装一次,保证插件列表中不嫩有重复的插件;
3、实现:
Vue.use = function(plugin){ // 在Vue.js上新增了use方法,并接收一个参数plugin。
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if(installedPlugins.indexOf(plugin)>-1){
// 先判断插件是否已经被注册过(使用indexOf实现),被注册过则终止方法执行
return this;
}
<!-- 其他参数 -->
// 使用toArray方法得到arguments。除第一个参数外,其他的都赋值给arg
const args = toArray(arguments,1);
// 然后将Vue添加到args列表的最前面。目的是保证install方法被执行时第一个参数是Vue,其余参数是注册插件时传入的参数。
args.unshift(this);
// 由于plugin参数支持对象和函数类型(对象会提供install方法,函数会被作为install方法),所以通过判断plugin.install和plugin那个是函数,可知用户使用哪种方式注册的插件,然后执行用户编写的插件并将args最为参数传入
if(typeof plugin.install === 'function'){
plugin.install.apply(plugin,args);
}else if(typeof plugin === 'function'){
plugin.apply(null,plugin,args);
}
// 将插件添加到installedPlugins中,保证相同的插件不会被反复注册
installedPlugins.push(plugin);
return this;
}
Vue.mixin
Vue.mixin(mixin);
1、用法:
- 全局注册一个混入(mixin),影响之后创建的每个Vue.js实例
- 插件作者可以使用混入向组件注入自定义行为(如:监听生命周期钩子) 2、实现:
options
const vm = new Vue(options)。options包含了五类属性
数据: data, props, propsData, computed, Watch;
DOM: el, template, render, renderError;
声明周期钩子: beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed、errorCaptured;
资源: directives、filters、components;
组合: parent、mixins、extends、provide、inject;
// mergeOptions会将用户传入的mixin与this.options合并成一个新对象,将这个生成的新对象覆盖this.options属性,这里的this.options其实就是Vue.options.
// mixin方法修改了Vue.options属性,而之后创建的每个实例都会用该该属性,所以会影响创建的每个实例
import { mergeOptions } from '../util/index'
export function initMixin(Vue){
Vue.mixin = function(minxin){
this.options = mergeOptions(this.options,mixin);
return this;
}
}
Vue.delete
删除数组用delete和Vue.delete有什么区别?
delete:只是删除数组成员,元素变为empty/undefined,其他元素不变。
Vue.delete:直接删除了数组成员,并改变了数组的键值
用法:对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开Vue不能检测到属性被删除的限制
Vue.delete(this.namelist,'name');//vue方法
组件上使用v-model的用法已更改
- 自定义v-model时,prop和事件默认名称已更改
- prop:value -> modelValue
- event:input -> update: modelValue
- .sync和组件的model选项已移除,可用v-model作为替代
- 现在可以在同一组件上使用多个v-model进行双向绑定;
- 可以自定义v-model修饰符,如自定义v-model.capitalize,绑定为字符串第一个字母的大写
<template v-for> 和非 v-for 节点上 key 用法已更改
- Vue 2.0建议在v-if/v-else/v-else-if的分支中使用key,vue3.0中人能正常工作,但不在建议,因为没有为条件分支提供key时,也会自动生成唯一的key。
- Vue 2.0中
<template>标签不能拥有key,在Vue3.0中key则应该被设置在<template>标签上。
在同一元素上使用的 v-if 和 v-for 优先级已更改
- Vue3.0中v-if会拥有比v-for更高的优先级(由于语法上存在歧义,建议避免在同一元素上同事使用两者,可以使用计算属性筛选出列表)
v-bind="object" 现在排序敏感
- vue2.0如果一个元素同时定义了v-bind=“object”和一个相同的单独的prototype,那么这个单独的property总是会覆盖object中的绑定。
- Vue3.0声明绑定的顺序决定了他们如何合并
// 2.x中 id最终为red 3.x中 id为blue
<div id="red" v-bind="{ id: 'blue' }"></div>
v-for 中的 ref 不再注册 ref 数组
- Vue2.0中,在v-for里使用ref属性时,从$refs中获取的相应属性会是一个ref数组。
- vue3.0中则将ref绑定到一个更灵活的函数上(ele) => {...// 保存ele的操作}
<div v-for="item in list" :ref="setItemRef"></div>
import { ref, onBeforeUpdate, onUpdated } from 'vue'
export default {
setup() {
let itemRefs = []
const setItemRef = el => {
itemRefs.push(el)
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
itemRefs,
setItemRef
}
}
}