vue3和vue2不同点总结

前段时间用vue3搭建完成了一个小项目,可喜的是领导在设计之初同意使用vue3进行新项目的开发,在此从技术角度记录一下vue3与vue2不同的点。

在main.js中的全局挂载方式

vue2:$mount

import Vue from "vue";
import App from "./App.vue";
import store from "./store/";
import router from "./router";

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

vue3:createApp:返回一个提供应用上下文的应用实例,应用实例挂载的整个组件树共享同一个上下文

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

createApp方法扩展

  • component:注册或检索全局组件
// 注册一个名为my-component的组件
app.component('my-component', {
  /* ... */
})

// 检索注册的组件(始终返回构造函数)
const MyComponent = app.component('my-component')
  • config:包含应用配置的对象。以下是config的可配置项:
    应用配置对象
    globalProperties:添加一个可以再应用的任何组件实例中访问的全局property
    相当于vue2中的: Vue.prototype.$aa = '....'
    在vue3中的使用: app.config.globalProperties.$aa = '...'
    关于globalProperties的用法在下文有扩展~

  • directive:注册或检索全局指令

  • mixin:将一个mixin应用在整个应用范围内。

  • mount:所提供 DOM 元素的 innerHTML 将被替换为应用根组件的模板渲染结果。

  • provide:设置一个可以被注入到应用范围内所有组件中的值

  • unmount:卸载用用实例的根组件

  • use:安装vue.js插件。如果插件是一个对象,它必须报漏一个 install 方法;若它本身是一个函数,将被视为安装方法

  • version:以字符串形式提供已安装的vue的版本号

组合式API-setup

vue2中,我们书写逻辑部分,需要这样写:

export default {
  components: {...},
  props: {...},
  data () {
    return {
    	num: ''
    }
  },
  computed: { },
  watch: {},
  mounted () {},
  methods: {}
}

当组件开始变大时,逻辑关注点的列表也会增长,后期不便于维护。
vue3使用setup:

  1. setup在组件创建之前执行(即在beforeCreated钩子之前)
  2. setup中不再使用this, 因为它不会找到组件实例
  3. setup接收两个参数:props & context的函数, context包含三个参数{attrs / slots / emit}
export default {
  components: {...},
  props: {...},
  setup(props, {attrs, slots, emit}) {
    console.log(props)

	//在这里可以写生命周期钩子函数
	onMounted(() => {....})

    //  在return返回所有用于DOM的变量和方法
    return {}
  }
}

结构简单了许多,写起来特别香~

生命周期钩子

如图是vue2和vue3的生命周期钩子对比:
生命周期
vue3中给钩子函数都加上了“on”来访问

在单页面的引入

import {onMounted, onUpdated...} from 'vue'

这些函数接收一个回调函数,使用方法如上面例子
注意:钩子函数在使用之前必须要在单页面引入

响应式引用 ref & 响应式状态reactive

vue2中,双向绑定是基于Object.defineProperty()方法实现,且用this可以指向当前实例,所以一些简单的变量赋值就很容易的渲染到DOM上,完成响应。
vue3中,双向绑定是通过ES6的proxy方法实现,且页面不再使用this,响应式变量的定义就发生了变化:

ref

通过 ref 函数为变量创建了一个响应式引用。 使任何响应式变量在任何地方起作用

import { ref } from 'vue'

const counter = ref(0)  // 括号里面是给counter赋初始值

因绑定方式发生变化,若打印counter 是如下结果:
counter
所以,如果要对counter进行操作,需要用 counter.value。但是在DOM中双向绑定的时候不用.value,因为它会自动浅层次解包内部值,直接绑定就行。
ref一般用来定义基本数据类型的变量(Number,String,Boolean,Null, Undefined)

reactive

官方文档上对reactive的解释是:返回对象的响应式副本为 JavaScript 对象创建响应式状态;该响应式转换是“深度转换”——它会影响传递对象的所有嵌套 property。

所以,reactive一般用来定义引用数据类型的变量(Object,Array)

const obj = reactive({ count: 0 })

打印obj的结果如下:
obj

操作count值: obj.count

以下关于解包:

  • reactive 将解包所有深层的 refs,同时维持 ref 的响应性
    ex1:
const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

ex2:当将 ref 分配给 reactive property 时,ref 将被自动解包。

const count = ref(1)
const obj = reactive({})

obj.count = count

console.log(obj.count) // 1
console.log(obj.count === count.value) // true
  • reactive定义变量可以使用 for…of 进行循环

响应式状态解构

当定义了一个响应式对象,我们想要采用ES6解构的方式获取其中的参数进行操作时:

import { reactive } from 'vue'

const obj = reactive({
      name: 'wang',
      age: 18,
      sex: '女',
      height: 160
})

// 解构
const { name, age } = obj

console.log('name: ', name, name.value)
console.log('age: ', age, age.value)

解构
结果如上,解构出的两个property失去响应性。
所以,vue3里引入了api: toRefs,保留与源对象的响应式关联

import { reactive, toRefs } from 'vue'

const obj = reactive({
      name: 'wang',
      age: 18,
      sex: '女',
      height: 160
})

// 解构
const { name, age } = toRefs(obj)

console.log('name: ', name, name.value)
console.log('age: ', age, age.value)

结果如下:
jiegou
我们做进一步的测试,修改解构之后的name值,是否会反应到源对象obj上呢?

name.value = '小王'
console.log('name: ', name.value, obj)

obj
如上,修改name值之后,源对象obj的值会同步更新,完美~

一个小栗子

基于上面的生命周期和响应式引用,我们来看一个较完整的小栗子:

<template>
  <div>
    <p>{{ num }}</p>
    <p v-for="item in arr" :key="item">{{ item }}</p>
    <button @click="getData">点击事件</button>
  </div>
</template>

<script>
import { ref, reactive, onMounted } from 'vue'

export default {
  setup() {
  
  	// 响应式
    const num = ref(1)
    const arr = reactive(['red', 'yellow'])
    
    // 不具备响应式,可用于中间逻辑操作,但无法在dom中使用
    let testNum = 0

    // methods:
    const getData = () => {
      console.log('getData')
    }
    
	// 生命周期
    onMounted(() => {
      getData()
    })

    // DOM中使用到的响应式引用和方法需要return
    return {
      num,
      arr,

      getData
    }
  }
}
</script>

计算属性和侦听器

computed

在vue2中,computed是这么使用的:

computed: {
	variable() {
		const path = this.$route.path
		return path 
	}
}

计算属性内部存在缓存,当返回值发生变化才会触发
在vue3中,computed这样使用:

import { computed } from 'vue'

setup() {
	const variable = computed(() => localStorage.getItem('userid'))
}

写法简化
注意,vue3中的承载计算属性的变量 variable 同样具备响应式引用,使用时:variable.value

watch

在vue2中, watch这样使用:

 watch: {
   // 监听变量  
    watcherVal(newValue,oldValue) {
   	    //  处理逻辑
    }
 }

watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生变化时被调用。

vue3中,这么用:
侦听单一源:

import { watch } from 'vue'

setup(){
    const count = ref(0)
	watch(count, (newValue, oldValue) => {
	  /* ... */
	})
}

同时侦听多个源,采用数组形式:

import { watch } from 'vue'

setup(){
	const var1 = ref(0)
	const var2 = ref(1)
	watch([var1, var2], ([newVar1, newVar2], [oldVar1, oldVar2]) => {
	  /* ... */
	})
}

ref获取DOM

在vue使用ref获取DOM之前,我们一般使用js的原生api,用ref获取就方便了许多。尤其是在画echarts图时
而ref不止是可以获取DOM,同时可以担任起父子组件的传参~
我们先看vue2中ref的使用:

<template>
  // 子组件
  <test-component ref="testComp" />
  <div ref="divDom"></div>
</template>

// methods:
methods:{
	someFunction() {
	    // this.$refs.testComp 即可得到子组件DOM, initFunc:子组件方法
		this.$refs.testComp.initFunc(params)
	    // 获取div的DOM
	    console.log(this.$refs.divDom)
	}
}

在vue3中,使用组合式api(setup)时,响应式引用和模板引用的概念是统一的,即上面提到的定义响应式引用的ref和DOM中的ref是一个概念,那么ref获取DOM如下使用:

<template> 
  <div ref="root">hello world</div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // DOM 元素将在初始渲染后分配给 ref
        console.log(root.value) // <div>hello world</div>
      })

      return {
        root
      }
    }
  }
</script>

如上,定义一个与ref同名的变量,return之后,就可以使用 root.value 来获取DOM。
同时,若ref定义在子组件上,可以调用子组件方法进行传参,甚至修改子组件的变量值:

补充上面例子:

<template> 
  <div ref="root">This is a root element</div>
  // 子组件
  <test-component ref="testComp" />
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)
      const testComp = ref(null)

	  const someFunction = () => {
	  	  // 执行子组件的initFunc方法,并传参params
		  testComp.value.initFunc(params)
	  }

      onMounted(() => {
        // DOM 元素将在初始渲染后分配给 ref
        console.log(root.value) // <div>This is a root element</div>
        console.log(testComp.value)
        someFunction()
      })

      return {
        root,
        testComp
      }
    }
  }
</script>

globalProperties方法扩展

上文也简单提到了globalProperties,可以定义全局property,在DOM中的使用和vue2一样没有变化:

app.config.globalProperties.$aa = '...'
$aa  // DOM中用

但是在setup-methods中,因为缺少this指向,vue3文档中提到一个新的api:getCurrentInstance
getCurrentInstance:支持访问内部组件实例
warning
不能滥用!

博主当时遇到的情况是:全局引入并定义了echarts之后,在方法中使用echarts来绘图,发现无法拿到全局的echarts,然后查到了该api

使用如下:

import { getCurrentInstance } from 'vue'
export default {
  setup() {
    const internalInstance = getCurrentInstance()

    internalInstance.appContext.config.globalProperties // 访问 globalProperties
  }
}

getCurrentInstance 只能在setup或生命周期钩子中调用
我们可以看一下,在我的项目中,internalInstance.appContext.config.globalProperties的打印:
this

nextTick

nextTick: 等待DOM更新之后执行
vue2:

this.$nextTick(() => {
	// do something
})

vue3:

setup() {
   const someFunc= async () => {
   await nextTick()
   console.log('DOM is updated')
}

prop&emit

来到父子组件传值啦~
先回想下vue2中的prop&emit的使用:

// 子组件:

<template> 
  <div>
	   <p> {{ vari1 }} </p>
	   <button @click="handleClick">触发</button>
  </div>
</template>

<script>
export default {
	props: {
	  vari1: {
	    type: String,
		default: 'hi'
	  }
	},
	data(){
	  return{}
	},
	methods: {
	  handleClick() {
	    this.$emit('handleClickFunc')
	  }
	}
}
</script>
// 父组件

<template> 
  <div>
	  // 子组件
	  <test-component :vari1="variable"  @handleClickFunc="handleClickFunc" />
  </div>
</template>

<script>
export default {
	data() {
	  return {
	    variable: 'hello'
	  }
	},
	methods: {
	  handleClickFunc() {
	     console.log('hello world')
	  }
	}
}
</script>

如上,是不是非常熟悉~
那么,vue3有什么改变呢?
我们通过上文也知道了api setup的两个参数setup(props, {attrs, slots, emit }){}。
props 对象将仅包含显性声明的 prop,并且所有声明了的prop,不论父组件是否向其传递,都会出现在props对象。
emit作用没变,不过vue3提供了一个emits选项,emits可以用来定义一个组件可以向其父组件触发的事件。
栗子如下:

// 子组件:

<template> 
  <div>
	   <p> {{ vari1 }} </p>
	   <button @click="handleClick">触发</button>
  </div>
</template>

<script>
export default {
	props: {
	  vari1: {
	    type: String,
		default: 'hi'
	  }
	},
	// 定义组件可触发的事件
	emits: ['handleClickFunc'],
	setup(props, { emit }) {
		console.log(props.vari1)

		const handleClick = () => {
			emit('handleClickFunc')
		}
		return {
			handleClick
		}
	}
}
</script>
// 父组件

<template> 
  <div>
	  // 子组件
	  <test-component :vari1="variable"  @handleClickFunc="handleClickFunc" />
  </div>
</template>

<script>
export default {
	setup() {
		const variable = 'hello'
		
		const handleClickFunc = () =>  {
		     console.log('hello world')
		 }
		
		return {
			variable,
			handleClickFunc
		}
	}
}
</script>

父组件中除了新api之外没有特殊变化,还是子组件里增加了变化props&emits

vuex

创建一个store

想想vue2是怎么创建store的呢?

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {}
});

如上,创建成功之后在main.js中进行挂载
那么,vue3呢?
vue3引入了一个新api:createStore
如下:

import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state () {},
  mutations: {},
  actions: {}
})

创建成功之后在main.js中将store实例作为插件安装(.use)
这样就创建成功了。

useStore()

vue3中,通过调用函数useStore来在setup中访问store,相当于vue2中使用的this.$store
调用方式如下:

import { useStore } from 'vuex' // 引入

export default {
  setup () {
    const store = useStore()
  }
}

如上,获取到store之后,就可以访问State、Getter、Mutation和Action
为了访问state和getter,需要computed引用来保留响应式:
要使用 mutation 和 action 时,只需要在 setup 钩子函数中调用 commit 和 dispatch 函数:

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double)

      // 使用 mutation
      increment: () => store.commit('increment'),

      // 使用 action
      asyncIncrement: () => store.dispatch('asyncIncrement')
    }
  }
}

题外话:
vue2中有些辅助函数:mapState、mapGetters、mapMutations、mapActions,但是vue3里面是没有这些辅助函数的。目前只有useStore函数。

router

this.$router & useRouter()

vue2中,我们通过this.$router来访问路由器,同时作用于路由跳转

this.$router.push('page')
this.$router.push({name:'Page', params: { name:'xiaowang' }})

vue3中, setup 里面没有访问 this,使用useRouter函数来代替this.$router

import { useRouter } from 'vue-router'

export default {
  setup() {
    const router = useRouter()

    const someFunction = () => {
      router.push({ name: 'Page', params: {  name: 'xiaowang'  } })
    }
	
	return {
		someFunction
	}
  }
}

this.$route & useRoute()

vue2中,我们通过this.$route来访问当前的路由,并作用于接收从this.$router跳转页面携带的参数

// 接上面this.$router代码
this.$route.params.name // 'xiaowang'

vue3中,采用useRoute函数代替this.$route

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    const userData = ref()

    // 当参数更改时获取用户信息
    watch(
      () => route.params,
      (newParams) => {
        userData.value = newParams.name
      }
    )
  }
}

小结: 在模板中我们仍然可以访问 $router 和 $route,在 setup 中就需要使用函数。所以不必在setup中返回 router 和 route

总结

用vue3完成了一个历时近两月的小项目,一开始写有很多不理解的地方,但是随着熟练加深,发现vue3写起来是真香~代码逻辑结构更加清晰了,而且新的响应式原理,比2版本强大很多。有幸看到最后的朋友们可以尝试一波。
目前对于vue2和vue3不同点的总结就先更新到这,以后如果再遇到会持续更新。
最后。vue3不支持IE浏览器! 在实际项目中请谨慎!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值