最近公司需要搭建一个新项目,用于做官网。因为作为官网,首先项目不算大,一共只有十来个页面,并且想要用户体验感很好,所以最终选定以 Vue 作为技术栈。
虽然 Vue3 (中文官网)刚问世不久,但是如果采用VUE2 搭建项目,以后可能会面临 Vue 版本升级问题,从 Vue2 跨度到 Vue3 有很多不兼容的变更,并且时代在发展,使用新技术会更加具有 可维护可扩展性。
Vue.js devtools 没办法调试vue3的项目,官方也提供了升级版的devtools:vue devtools beta。
安装一些常用依赖,值得注意的是Vue3的某些插件与Vue2并不相同。
特别说明:
这是基于 Vue 3.0 的桌面端组件库。
如何全局使用element-plus:
// plugins/element.js
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn'
export default (app) => {
app.use(ElementPlus, { locale })
}
// main.js
import installElementPlus from './plugins/element'
const app = createApp(App)
installElementPlus(app)
// 使用
<template>
<el-button>按钮</el-button>
</template>
值得注意的是,在Vue2中通过 new Vue()创建Vue实例,并没有app的概念,但是在Vue3中,引入了createApp。所以axios的y引用方式有所差异。具体内容参照:Vue3 安装axios使用报错:Uncaught TypeError: Cannot read property ‘use‘ of undefined。
如何使用axios:
// 方式一:全局挂载 此方式不合适于 将项目里的不同模块的请求都放在相应文件里,
// 因为我们在发请求时可能无法获取到当前实例this
// plugins/axios.js
export const plugin = {
install: function (app, options) {
console.log(options)
// 添加全局的方法
app.config.globalProperties.axios = _axios;
}
}
// main.js
import { plugin as axios } from './plugins/axios'
const app = createApp(App)
app.use(axios)
//使用:
this.$axios.post('api/Login',{card:111}).then(res => console.log(res))
//方式二:直接使用
// plugins/axios.js
const _axios = axios.create(config)
export default _axios
// 使用:
import axios from '@/plugins/axios'
// 获取岗位类别
export function getJobCategories() {
return axios.post('api/Login',{card:111})
}
Vue router4 也提供了hook钩子函数供使用。
在 setup里使用 router
import { useRouter } from "vue-router"
const router = useRouter()
router.push('/')
什么是组合式API?
简单来说就是 将之前散布在vue文件里 的各个功能函数 组合 在一个函数里
这样会使我们的代码更加类聚,如果使用过react 函数式组件的同学应该会发现,他们及其类似。
setup作为组合式api的入口点,所有的函数都可以被放置在setup里进行书写,需要被setup外部使用的参数,通过 return 暴露出去
setup接收两个参数:props、context。
props接受父组件传递而来的参数,当然,这些参数也是响应式的。值得注意的是,在setup里使用 props时,必须在props里定义的变量 才可被setup的props所接收到,在setup里获取props的变量时,不可使用es6的解构方法,因为它会消除 prop 的响应性,如果需要解构 prop并保留响应式,可以使用 setup 函数中的 toRefs 函数。
context 暴露三个组件的 property: attrs(Attribute)、slots(卡槽)、emit(触发事件),context是一个普通的 JavaScript 对象,不具有响应式,可以使用es6解构语法。
注意⚠️
setup是在解析其它组件选项之前被调用的,所以在setup里无法准确的访问到当前组件实例:this。
想要在setup里获取当前组件实例,官方提供了一个函数:getCurrentInstance,但是getCurrentInstance只能在setup和生命钩子函数里使用
// getCurrentInstance代表全局上下文,ctx相当于Vue2的this,
// 但是特别注意ctx代替this只适用于开发阶段,等你放到服务器上运行就会出错,
// 后来查阅资料说的得用proxy替代ctx,才能在你项目正式上线版本正常运行
const { ctx, proxy } = getCurrentInstance()
// 父组件
setup () {
// readonly包裹后可以在组件内引用时不被改变值。
// 否则在组件内可以直接通过applyPositionId.value=***将值改变
provide('applyPositionId', readonly(applyPositionId))
// 函数也可传递
provide('hideApplyModal', hideApplyModal)
}
//子组件接收
setup() {
const applyPositionId = inject('applyPositionId')
const hideApplyModal = inject('hideApplyModal')
// 函数调用
hideApplyModal()
}
在Vue2里,我们定义在data里的数据,会被 Object.defineProperty() 数据劫持,通过观察者模式 最终成为响应式数据。
在Vue3里,实现响应式的 方式改为了 es6 新增的语法 proxy,通过对象拦截方式实现响应式,解决了vue2中的一些漏区。
关于这两点的相关知识有很多,我会新写一篇文章做相应的说明。这里主要介绍Vue3的一些新特性的使用。
reactive用于为对象创建响应式状态,其作用相当于Vue 2.x 中的 Vue.observable()。为对象创建响应式状态后,就不会存在 Vue2中 修改 某个对象在data定义时没有被定义到的属性,不会存在响应式状态 的问题。
setup() {
const obj = { count: 0}
const state = reactive(obj)
console.log(state.count) // 0
}
官网有一句话:
响应式转换是“深层”的——它影响所有嵌套 property。这是什么意思呢?
当我们使用 proxy (不了解 proxy 的可以阅读:阮一峰 es6入门)做 对象拦截 时,他会劫持此对象的所有property 属性。
vue2使用 Vue.observable() 使一个对象可响应,被传入的对象会直接被 Vue.observable 变更为响应式。而 reactive 是为传入对象创建一个响应式的副本,不改变原始对象,当我们直接改变原始对象时,原始对象不会是响应式的。
readonly获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理
const state = reactive({ count: 0 })
const copy = readonly(state)
// error
copy.count++
此ref非彼ref,在Vue和react里,都有ref的概念。当其作用域HTML文件时,ref是React/Vue提供的用来操纵React/Vue组件实例或者DOM元素的接口。
而 响应性 API 里 ref 是 用来创建一个具有响应式且可变的 ref 对象,ref 对象具有指向 其所创建变量的.value属性。在setup里被return 出去时,会自动绑定其value值。
setup() {
const count = ref(0)
console.log(count.value) // 0
return {
count
}
}
mounted() {
console.log(count) // 0
}
官网有这么一句话:
看了半天我也不是特别明白它是什么意思。将 ref 与 reactive 使用下来,发现他们两除了在使用方式上有所不同以外,没有什么太大的区别。高度响应式 的感念还是没有体会到。通过源码我可以看见,在使用ref为 对象创建 响应式数据时,其内部调用的也是 reactive 方法。
如果大家有什么理解,可以评论告诉我。
computed的使用很简单,本质上和vue2没有什么太大的区别
等同于vue2的this.$watch,可监听一个或多个源。当 被依赖的值 发生改变时,执行。
// 侦听一个getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(newcount, prevCount) => {
console.log('新值:', newcount)
console.log('旧值:', prevCount)
}
)
// 直接侦听一个ref
const count = ref(0)
watch(count, (newcount, prevCount) => {
console.log('新值:', newcount)
console.log('旧值:', prevCount)
})
// 监听多个源,使用数组形式
const fooRef = ref(0)
const barRef = ref(1)
watch([fooRef, barRef], ([newFoo, newBar], [prevFoo, prevBar]) => {
console.log('新Foo值:', newFoo)
console.log('旧Foo值:', prevFoo)
console.log('新Bar值:', newBar)
console.log('旧Bar值:', prevBar)
})
watchEffect响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它:意思就是页面初始化时会被立即执行一次,并在依赖项发生改变时,再次执行。熟悉React Hook 的同学会非常熟悉,他与useEffect 所实现的功能是一样的,唯一不同的是,useEffect需要我们手动传入依赖,而 watchEffect 会自动收集依赖。
const count = ref(0)
watchEffect(() => console.log('value:', count.value))
// value:0
setTimeout(() => {
count.value++
// value:1
}, 100)
Vue3 在 Vue2上,有许多的非兼容变更,这里列举一些比较常用的属性。
在Vue2中的 v-model 等同于:value + input 的语法糖。
在Vue3中的 v-model 等同于:modelValue + update:modelValue。
// Vue2
<ChildComponent v-model="pageTitle" />
<!-- 等同于: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
// Vue3
<ChildComponent v-model="pageTitle" />
<!-- 等同于: -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
在Vue2中,我们想要改变v-model传下来的值,需要model选项去改变。
// parentComponent
<ChildComponent v-model="pageTitle" />
// ChildComponent
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
// 这将允许 `value` 属性用于其他用途
value: String,
// 使用 `title` 代替 `value` 作为 model 的 prop
title: {
type: String,
default: 'Default title'
}
}
}
// 或者
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
// 或者
<ChildComponent :title.sync="pageTitle" />
// 子组件
this.$emit('update:title', '新title')
在Vue3中,移除了v-bind .sync 修饰符,以上功能简写为:
<ChildComponent v-model:title="pageTitle" />
// 等同于
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
// 子组件:
setup(props, context) {
const { emit } = context || {}
const onClose =() => {
emit('update:pageTitle', '新title')
}
return {
onClose,
}
}
在Vue2中,v-if 与 v-for 同时作用于同一个DOM节点时,v-for 的优先级 高于 v-if 。
在Vue3中,v-if 与 v-for 同时作用于同一个DOM节点时,v-if 的优先级 高于 v-for 。
且 v-if/v-else/v-else-if 的分支中继续使用 key attribute,因为没有为条件分支提供 key 时,也会自动生成唯一的 key。
Vue3中filters已被彻底移除,建议使用computed代替。
在Vue2中,template 里只支持一个根结点,否则会报错。
在Vue3中,template 里只支持多个根结点。
在Vue2中,我们需要在 prop 里访问data里的字段,可以使用 this 。
在Vue3中,prop 里不再支持this。
import { reactive, toRefs } from 'vue'
export default function useFetch(api, params, transformer) {
if (typeof api !== 'function') {
throw new TypeError('api should be type of fuction')
}
// 定义初始状态
const state = reactive({
data: null,
loading: false,
})
// 定义查询函数
const doFetch = (otherParams) => {
const finalParams = {
...(params || {}),
...(otherParams || {})
}
state.loading = true
return api(finalParams).then((data) => {
state.data = typeof transformer === 'function' ? transformer(data) : data
state.loading = false
return data
})
.catch((err) => {
console.log(err && err.message)
state.loading = false
})
}
// 返回状态refs和查询函数
return [toRefs(state), doFetch]
}
// 使用
import useListFetch from '@/hooks/useListFetch'
setup() {
const getdataFunc = (params) => {
const { id } = params || {}
return axios.get(`/xxx?id=${id}`)
}
const [
{ data, loading: isLoading},
getData,
] = useListFetch(getdataFunc, {
id: 10,
})
getData()
}
import { reactive, toRefs } from 'vue'
export default function useListFetch(api, params, transformer) {
if (typeof api !== 'function') {
throw new TypeError('api should be type of fuction')
}
// 定义list初始状态
const state = reactive({
items: [],
loading: false,
isLastPage: false,
})
const { pageSize = 10, ...otherParams } = params || {}
// 定义查询函数
const doFetch = () => {
const preSize = state.items.length
const finalParams = {
...otherParams,
offset: preSize,
limit: pageSize,
}
state.loading = true
return api(finalParams).then((data) => {
if (data && Array.isArray(data)) {
const newData = typeof transformer === 'function' ? transformer(data) : data
const newItems = [...state.items, ...newData]
state.items = newItems
if (newItems.length !== preSize + pageSize) {
state.isLastPage = true
}
}
state.loading = false
return data
})
.catch((err) => {
console.log(err && err.message)
state.loading = false
})
}
// 返回状态refs和查询函数
return [toRefs(state), doFetch]
}
// 使用
import useListFetch from '@/hooks/useListFetch'
setup() {
const getListFunc = (params) => {
const { offset = 0, limit = 10 } = params || {}
return axios.get(`/xxx?offset=${offset}&limit=${limit}`)
}
const [
{ items: videoList, loading: isLoading, isLastPage: isLastPage },
getList,
] = useListFetch(getListFunc, {
pageSize: 10,
})
getList()
}
参考文档:https://cli.vuejs.org/zh/guide/
以上就是本人在使用Vue3时一些相对变化比较重要的地方。