vue3和vue2在API使用上有哪些差别?

一.概要

文章内容有点多,加不了样式了,你们将就看一下。javascript

上周,vue3 one piece正式版发布了(话说尤大这个用动漫名定义版本号的习俗何时轮到火影啊啊啊),在给尤大疯狂打call的同时,也是我们开始爆肝学习的时候了。html

我对比了vue2vue3,在这里列举出使用方法,功能或者已经废弃的一些api。供一些vue2的同窗快速的了解和上手vue3。在这篇文章里,只列出一些使用上有所差别的内容,不涉及到源码方面。vue

除了安装以及vue3一些全新的方法外,API方面我会按照vue2的官方api文档的目录顺序来列举。java

  • 安装
  • vue3新内容
  • 应用配置(原全局配置和全局API)
  • 全局API
  • 选项/数据
  • 选项/DOM
  • 选项/生命周期钩子
  • 选项/资源
  • 选项/组合
  • 选项/其它
  • 实例property
  • 实例方法/数据
  • 实例方法/事件
  • 实例方法/生命周期
  • 指令
  • 特殊attribute
  • 内置的组件

本人技术有限,这里有些api可能我也不经常使用到或者没怎么使用过,因此若是有所错误或者遗漏,请在下面评论指出,我会及时进行修改。react

这里是我我的的理解和记录,如今vue3官方文档已出,你们也能够去官网查看vue2的迁移内容。程序员

二.安装

项目的安装如今有三种方式。vuex

  • 经过script引入
<script src="https://unpkg.com/vue@next"></script>
复制代码
  • 脚手架vue-cli
// vue-cli在4.5.0的版本才开始支持建立vue3项目
npm install -g @vue/cli
vue create hello-vue3
复制代码
  • 脚手架vite
npm init vite-app hello-vue3
复制代码

vite是尤大新的开源工具,借用尤大的原话vue-cli

一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,彻底跳过了打包这个概念,服务器随起随用。同时不只有 Vue 文件支持,还搞定了热更新,并且热更新的速度不会随着模块增多而变慢。针对生产环境则能够把同一份代码用 rollup 打包。虽然如今还比较粗糙,但这个方向我以为是有潜力的,作得好能够完全解决改一行代码等半天热更新的问题。npm

总的来讲,就是js的引入使用了浏览器自身的es imports方法。开发模式下不须要打包,只须要编译修改过的文件便可。生产环境下使用rollup打包。编程

这是一个新的方向,虽然目前还很稚嫩,并且对于ES imports浏览器的兼容仍是颇有问题,不过再过几年,这可能也会变成一个主流的工具。

三.vue3新内容

1.Composition API

将数据、方法、computed、生命周期函数,集中写在一个地方。

1.1 setup()

setup()做为在组件内使用Composition API的入口点。执行时机是在beforeCreatecreated之间,不能使用this获取组件的其余变量,并且不能是异步。setup返回的对象和方法,均可以在模版中使用。

setup有两个参数,props,context

<script lang="js">
import {toRefs} from 'vue'
export default {
    name: 'demo',
    props:{
      name: String,
    },
    setup(props, context){
      // 这里须要使用toRefs来进行解构
      // 这里的props与vue2基本一致,固然这里的name也能够直接在template中使用
      const { name }=toRefs(props);
      console.log(name.value);
      // 只能获取到这三个属性,也是不能使用ES6的解构
      // 属性,同vue2的$attrs
      console.log(context.attrs);
      // 插槽
      console.log(context.slots);
      // 事件,同vue2的$emit
      console.log(context.emit);
  }
}
</script>
复制代码

1.2 reactive

vue3将响应式的功能和vue代码解耦,单独做为一个js库来调用。(在其余任何js运行的框架当中,均可以引入和使用vue3的这个响应式功能)。

<template>
  <div>{{ state.text }}</div>
  <button @click="test">测试一下?</button>
</template>
<script> import { reactive } from 'vue'; export default { setup() { // 经过reactive声明一个可响应式的对象 const state = reactive({ text: "我帅吗" }); // 经过reactive声明一个可响应式的数组 const state1 = reactive([ text: "我帅吗" ]); // 声明一个修改方法 const test = () => state.text = '你很丑'; // 返回state和方法 return { state,test }; } }; </script>
复制代码

这是一个跟vue2比较大的区别,使用上面的方法,才能实现数据的响应式。而后这里的对象能够随意赋值,不会出现vue2中深层未定义的对象值赋值不能响应式的问题。由于如今vue的底层是用proxy实现的数据监听。

注意:这里的元素只能是对象或者数组,基本数据类型须要使用ref

1.3 ref

声明响应式的基本数据类型

import { ref } from 'vue';
export default {
  setup(){
    const a = ref(1);
    const b = ref('1');
    const c = ref(true);
    // 声明一个修改方法
    const test = () => {
      // 注意:在setup里面修改ref的值是须要经过.value的,template作了解套,因此不须要。
      // ref 做为 reactive的对象的值时不须要.value
      // ref 做为 reactive的数组的值时须要.value
      a.value = 2;
      b.value = '1';
      c.value = false;
    };
    return { a, b, c, test };
  }
}
复制代码

获取DOM元素

<template>
  <div id="test" ref="testDom"></div>
</template>

<script> import { ref, onMounted } from 'vue'; export default { setup(){ const testDom = ref(null); //使用onMounted钩子,相似在vue2的mounted生命周期中操做元素 onMounted(() => { // 记得要加.value console.log(testDom.value); }) return { testDom }; } } </script>
复制代码

1.4 readonly

能够传入一个对象或ref,返回一个只读的对象,深层次的只读

const state = reactive({ name: 0 })
const readOnlyState = readonly(state)
// 能够修改
state.name = '帅';
// 没法修改并警告
readOnlyState.name = '帅';
复制代码

2 reactive类工具API

2.1 isProxy, isReactive,isReadonly

isProxy是检查一个对象是不是由 reactive 或者 readonly 方法建立的代理。后面两个是单独的检测。 注意:只要检测的对象里有reactive,就算被readonly处理过了,isReactive也为true。

2.2 toRaw

把被代理或者响应式的对象转换成普通对象

const state = {}
const stateReactive = reactive(state)
console.log(toRaw(stateReactive) === state) // true
复制代码

2.3 markRaw

禁止这个对象成为响应式

const state = markRaw({ name: '帅' })
console.log(isReactive(reactive(state))) // false
复制代码

注意,markRaw只做用于根元素,若是使用子元素去赋值,仍是能够转成响应式代理

const state = markRaw({
  data: { id: 123 },
})
const newState = reactive({
  data: state.data,
})
console.log(isReactive(newState.data)) // true
复制代码

2.4 shallowReactive, shallowReadonly

跟上面reactive,readonly的做用同样,区别是这两个方法是浅层的监听。 只会对第一层响应式,深层次的数据不会响应

const state = shallowReactive({ name: '123', test: { id: 1 } })
const test = () => {
  // 深层次数据的变化不会触发响应式
  state.test.id = 2;
};
复制代码

3 ref类工具API

3.1 isRef

检查一个值是否为一个ref对象

3.2 toRef

用来做为一个reactive对象的属性,保持响应式

const state = reactive({
  a: 0
})
// 做为一个reactive对象的属性
const aRef = toRef(state, 'a')
// 在这里修改读取Ref要使用.value
aRef.value++
console.log(state.a) // 1

state.a++
console.log(aRef.value) // 2
复制代码

3.3 toRefs

将响应式对象转换为普通对象(相似解构),可是还能够保留响应式。能够用来对一些响应式变量屡次复用。

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })
  return toRefs(state)
}
export default {
  setup() {
    // 能够在不失去响应式的状况下复用
    const { foo, bar } = useFeatureX()
    return {
      foo,
      bar
    }
  }
}
复制代码

3.4 unref

获取ref值的语法糖

val = isRef(val) ? val.value : val
复制代码

3.5 customRef

建立一个自定义的ref,对它的读写进行手动控制。这是文档上的一个防抖的例子

<input v-model="text" />
复制代码
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        // 读值的时候,触发依赖
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 写值的时候,进行防抖,触发依赖
          trigger()
        }, delay)
      }
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}
复制代码

3.6 shallowRef, triggerRef

shallowRef数据是没有响应式的,可是可使用triggerRef来手动触发

const shallow = shallowRef({
  greet: 'Hello, world'
})
shallow.value.greet = 'Hello, universe'
triggerRef(shallow) //手动触发响应式
复制代码

4. computed and watch

4.1 computed

  • 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象
  • 具备 get 和 set 函数的对象来建立可写的 ref 对象
const count = ref(1)
// 只读的
const plusOne = computed(() => count.value + 1)
// 可更改的
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  },
})

plusOne.value = 1
console.log(count.value) // 0
复制代码

4.2 watchEffect

当即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变动时从新运行该函数。

const count = ref(0)
watchEffect(() => console.log(count.value))
// 打印出 0,初始化的时候就会打印
setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)
复制代码

中止观察

卸载组件的时候会自动中止,也能够手动中止监听

const stop = watchEffect(() => {
  /* ... */
})
// 中止监听程序
stop()
复制代码

反作用

若是一个函数内外有依赖于外部变量或者环境时,经常咱们称之为其有反作用,若是咱们仅经过函数签名不打开内部代码检查并不能知道该函数在干什么,做为一个独立函数咱们指望有明确的输入和输出,反作用是bug的发源地,做为程序员开发者应尽可能少的开发有反作用的函数或方法,反作用也使得方法通用性降低不适合扩展和可重用性

运行时机

// 初始化运行须要读取dom
onMounted(() => {
  watchEffect(() => {
  })
})
//设置运行时机
watchEffect(
  () => {/* ... */},
  {flush: 'pre'}
)
// pre是默认,组件更新前执行
// post, 组件更新后执行
// sync, 同步运行
复制代码

4.3 watch

跟vue2的watch一致

// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
复制代码

监听多个数据源

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
复制代码

与 watchEffect 共享的行为

中止监听,清除反作用,反作用刷新时机,侦听器调试方面跟watchEffect参数是同样的。

四.应用配置(原全局配置和全局API)

注意:全局配置的Vue都用如今的createApp生成的实例来替代

createApp(新增)

// vue2挂载dom方法
new Vue({
  render: (h) => h(App)
}).$mount("#app");
// vue3
createApp(App).mount('#app')
复制代码

注意,以前用到的全局配置的Vue都用如今的createApp生成的实例来替代

import { createApp } from 'vue'
const app = createApp({})

// 好比注册全局组件
app.component('my-component', {
  /* ... */
})
// 是否容许devtools检查代码
app.config.devtools = true  
复制代码

globalProperties(新增)

app.config.globalProperties.foo = 'bar'
app.component('child-component', {
  mounted() {
    console.log(this.foo) // 'bar'
  }
})
复制代码
  • 添加可在程序内的任何组件实例中访问的全局属性。当存在键冲突时,组件属性将优先
  • 替代掉Vue2.x的 Vue.prototype属性放到原型上的写法

unmount(新增)

卸载DOM元素上应用程序实例的根组件

const app = createApp(App)
app.mount('#app')
// 5秒以后,卸载APP组件
setTimeout(() => app.unmount('#app'), 5000)
复制代码

五. 全局API(新增)

createApp

相似以前全局的Vue变量,整个组件树共享的上下文,第二个参数做为props值传入

h

就是以前的createElement。

jsx偶尔在用,可是这个底层的相对用的比较少,就不细讲了

render() {
  return Vue.h('h1', {}, 'Some title')
}
复制代码

defineComponent(组件)

建立一个组件

import { defineComponent } from 'vue'
const MyComponent = defineComponent({
  data() {
    return { count: 1 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})
复制代码

defineAsyncComponent(异步组件)

建立一个异步组件

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
   /*或者*/
  import('./components/AsyncComponent.vue')
   /*或者*/
  new Promise((resolve, reject) => {
  /*能够reject*/
      resolve({
        template: '<div>I am async!</div>'
      })
    })
)

app.component('async-component', AsyncComp)
复制代码

resolveComponent

只能在rendersetup中使用,经过名称来寻找组件

app.component('MyComponent', {
  /* ... */
})
const MyComponent = resolveComponent('MyComponent')
复制代码

resolveDynamicComponent

只能在rendersetup中使用,解析活动的组件active

resolveDirective

只能在rendersetup中使用,容许经过名称解析指令

withDirectives

只能在rendersetup中使用,容许应用指令到VNode。返回一个带有应用指令的VNode

const bar = resolveDirective('bar')

return withDirectives(h('div'), [
  [bar, this.y]
])
复制代码

createRenderer

接受两个泛型参数:HostNodeHostElement,对应于宿主环境中的NodeElement类型。

这个东西不太清楚具体的使用场景

六.选项/数据

emits(新增)

在$emit触发事件以前验证参数

const app = Vue.createApp({})

// 对象语法
app.component('reply-form', {
  created() {
    this.$emit('check')
  },
  emits: {
    // 没有验证函数
    click: null,

    // 带有验证函数
    submit: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
})
复制代码

七.选项/DOM(不变)

八.选项/生命周期钩子

  • beforeDestroy -> beforeUnmount
  • destroyed -> unmounted
  • renderTracked(新增)

告诉你哪一个操做跟踪了组件以及该操做的目标对象和键

renderTracked({ key, target, type }) {
  // 这里的target为更新以前的值,type是get
  console.log({ key, target, type })
}
复制代码
  • renderTriggered(新增)

告诉你是什么操做触发了从新渲染,以及该操做的目标对象和键。 target为更新以后的值,type是set

这些生命周期还能够经过API在setup()里使用

  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

九.选项/资源

filters(废弃)

过滤器仍是有时候在用的

十.选项/组合

parent(废弃)

这个属性平时开发基本不会用到

setup(详情见上面)

十一.选项/其它

delimiters(废弃)

functional(废弃)

使用了新的异步组件的生成方法

model(废弃)

v-model修改了,不须要固定的属性名和事件名了,手动去处理

comments(废弃)

十二.实例property

vm.$attrs(修改)

如今$attrs不但会获取父做用域中不做为组件props的值,也能够获取到自定义事件(包含了$listeners的功能)。

vm.$children(废弃)

vm.$scopedSlots(废弃)

vm.$isServer(废弃)

vm.$listeners(废弃)

十三.实例方法/数据

vue3底层使用了proxy进行数据监听,因此不须要这两个方法了。

vm.$set(废弃)

vm.$delete(废弃)

十四.实例方法/事件

由于如今能够直接在setup调用生命周期api,并且能够引入别的js里的方法来使用,因此这些方法也能够不须要了。

vm.$on(废弃)

vm.$once(废弃)

vm.$off(废弃)

十五.实例方法/生命周期

vm.$mount(废弃)

统一使用createApp的mount方法来挂载

vm.$destroy(废弃)

统一使用createApp的unmount方法来卸载

十六.指令

v-bind(修改)

  • .prop 去除
  • .sync 去除(如今须要手动去同步)
  • .camel 将 kebab-case attribute 名转换为 camelCase

v-model(修改)

对于组件能够绑定多个属性值

<template>
  <!--在vue3.0中,v-model后面须要跟一个modelValue,即要双向绑定的属性名-->
  <!-- 在Vue3.0中也能够继续使用`Vue2.0`的写法 -->
  <a-input v-model:value="value" placeholder="Basic usage" />
</template>
复制代码

自定义一个组件

<template>
  <div class="custom-input"> <input :value="value" @input="_handleChangeValue" /> </div>
</template>
<script> export default { props: { value: { type: String, default: "" } }, name: "CustomInput", setup(props, { emit }) { function _handleChangeValue(e) { // vue3.0 是经过emit事件名为 update:modelValue来更新v-model的 emit("update:value", e.target.value); } return { _handleChangeValue }; } }; </script>
复制代码

v-on(修改)

.{keyAlias} - 仅当事件是从特定键触发时才触发回调。再也不经过键码的修饰符来触发。

v-is(新增)

相似vue2中的:is绑定,可让组件在某些特定的html标签中渲染,好比table,ul。

<!-- 不正确,不会渲染任何内容 -->
<tr v-is="blog-post-row"></tr>
<!-- 正确 -->
<tr v-is="'blog-post-row'"></tr>
复制代码

十七.特殊attribute

key(修改)

循环的时候,key要设置在template上。

<template v-for="item in list" :key="item.id">
  <div>...</div>
  <span>...</span>
</template>
复制代码

ref(修改)

v-for里使用的ref将不会再自动建立数组

解决方式:

<div v-for="item in list" :ref="setItemRef"></div>
复制代码
export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      this.itemRefs.push(el)
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}
复制代码

十八.内置的组件

transition(修改)

  • Props新增:

persisted - boolean 若是为true,则表示这是一个转换,实际上不会插入/删除元素,而是切换显示/隐藏状态。 transition 过渡挂钩被注入,但会被渲染器跳过。 相反,自定义指令能够经过调用注入的钩子(例如v-show)来控制过渡

enter-class ->enter-from-class

leave-class ->leave-from-class

  • 事件

before appear

teleport(新增)

将内容插入到目标元素中

<teleport to="#popup" :disabled="displayVideoInline">
  <h1>999999</h1>
</teleport>
复制代码

to必填属性,必须是一个有效的query选择器,或者是元素(若是在浏览器环境中使用)。中的内容将会被放置到指定的目标元素中

disabled这是一个可选项 ,作一个是能够用来禁用的功能,这意味着它的插槽内容不会移动到任何地方,而是按没有teleport组件通常来呈现【默认为false】

适合场景,全局的loading,多个内容合并等等等。

十九.总结

整体来讲,vue3在尽量的兼容vue2的同时,又引入了全新的组合式API的编程方式,这种新的模式有点相似react的思想,解决了以前版本对于业务代码难复用的问题,并且对一个页面来讲,不一样的功能代码能够更好的去区分,不会有之前各类变量和方法挤在一堆,后期难于维护的问题。加上良好的ts支持,很好很强大。

周边的一些组件vue Router4.0, vuex4.0也都提供了vue3支持。组件库这块,ant design vuevant已经支持了vue3

不过正如官方文档中建议的,目前还不建议把一些重要的项目迁移到vue3当中,由于vue3还有不少须要完善的地方,并且目前还不支持ie,等兼容的工做完成,仍是只能兼容到ie11。

后面就是一些开发中的比较好的实践了,等过段时间,用vue3作具体项目以后再作总结吧。

二十.感谢

感谢您的耐心阅读,写文章不易,但愿能给我点个赞,谢谢各位。

相关文章
相关标签/搜索