基础
在vue3中一般返回的数据是不响应的,如果需要响应式需要在定义时声明(ref/reactive );
这点和vue2的不同,在 Vue2 中,我们只需要把数据放入 data 函数即具备响应式,Vue2 会遍历 data 中的所有属性,使用 Object.defineProperty拦截把每个 property 全部转为 getter/setter,getter 用来收集依赖,setter 用来执行 notify,发布更新事件.
Vue3基于proxy拦截实现响应式的能力,解决了vue2所遗留下来的一些问题(对象新增,数组下标修改不响应),是真正的响应式.但还是存在一些弊端(也不能叫弊端,操作和vue2习惯不一样而已).
1、 原始值的响应式实现 必须将他包装为一个对象Ref,通过.value 的方式访问.或包装为reactive
2、 直接赋值和ES6解构都会失去响应式.
需要通过修改对象的属性的形式,实现修改数据. reactiveVal.arr=[1,2,3] refVal.value.arr=[1,2,3]
解构使用toRefs()代替ES6解构.
Vite(webpack和vite 都是现代化打包工具)
对非常基础的使用来说,使用 Vite 开发和使用一个静态文件服务器并没有太大区别。然而,Vite 还通过原生 ESM 导入提供了许多主要用于打包场景的增强功能.
在一个 Vite 项目中,index.html 在项目最外层而不是在 public 文件夹内。这是有意而为之的:在开发期间 Vite 是一个服务器,而 index.html 是该 Vite 项目的入口文件.
vite 以当前工作目录作为根目录启动开发服务器。你也可以通过 vite serve some/sub/dir 来指定一个替代的根目录。
Vue3+ts:
为了让 TypeScript 正确地推导出组件选项内的类型,我们需要通过 defineComponent() 这个全局 API 来定义组件.
import { defineComponent } from 'vue'
export default defineComponent({
props: {
name: String,
msg: { type: String, required: true }
},
data() {
return {
count: 1
}
},
mounted() {
this.name // 类型:string | undefined
this.msg // 类型:string
this.count // 类型:number
}
})
在使用了 <script lang="ts"> 或 <script setup lang="ts"> 后,所有的模板内表达式都将享受到更严格的类型检查,<template> 在绑定表达式中也支持 TypeScript。
Setup(组合式api)
vue3多了一种名为组合式api(composables api)的写法,相对应的式传统选项式api(options api),组合式api简单来说就是使用setup方式编写组件.
setup是vue3中的一个全新的配置项,setup是所有CompositionAPI(组合API)的基础,组件中所用到的数据、方法等都需要在setup中进行配置.
①、vue3支持向下兼容,vue2中的data、methods配置项在vue3中都能够使用,但是尽量不要将vue3中的配置项和vue2.x配置项混用;
②、vue2.x配置(data、methods、computed等)中可以访问setup中的属性、方法,但是在setup中不能访问vue2.x配置(data、methods、computed等);
③、如果vue2.x配置与vue3配置存在重名,则以setup优先;
④、setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性.(使用async需suspense包裹父组件)
5.setup 函数,它将接受两个参数:(props、context(包含attrs、slots、emit))
props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。如果需要解构 prop,可以通过使用 setup 函数中的toRefs
attrs: 包含组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。
父子组件通信过程中,父组件把数据传递过来,如果子组件没有用props进行接收,就会出现在attrs中,而vm中没有如果用props接收了,则会出现在vm上而attrs中没有.
- setup函数是去掉了的 beforeCreate 和 created 两个阶段,同样的新增了一个 setup的函数.执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了undefined)
<div ref="container"></div>
function useCount() {
let count = ref(10); //ref 会根据初始化时的值推导其类型,ref类型在底层会自动转换成reactive类型reactive({value: 10}),template里使用会自动添加.value
const book = reactive({ title: 'Vue ' }) //reactive() 也会隐式地从它的参数中推导类型,template里使用时不会自动添加.value
let double = computed(() => {
return count.value * 2;
});
const handleConut = () => {
count.value = count.value * 2;
};
return {
count,
double,
handleConut,
};
}
export default defineComponent({
setup() {
const { count, double, handleConut } = useCount();
onMounted(async () => {
console.log(container.value) // dom.
let AppProjectList = await apiStore.LBAPI({
url: "AppProject",
method: "get",
});
});
return {
count,
double,
handleConut,
container,
}
},
});
Ref toRef toRefs reactive proxy
ref、toRef、toRefs这三项在js中操作的时候都需要跟上‘.value’,页面当中正常使用无需‘.value’.
ref接受一个内部值并返回一个响应式且可变的 ref 对象,也就是ref可以接受一个普通类型的值,也可接受一个对象
let count = ref(10); //ref 会根据初始化时的值推导其类型,ref类型在底层会自动转换成reactive类型reactive({value: 10}),template里使用会自动添加.value
console.log(count.value)
reactive 用于为对象添加响应式状态,接收一个js对象作为参数
数组【[]】也是对象,但是对于数组来说,不能用【=】号赋值,这样会取消数据的响应式
const book = reactive({ title: 'Vue ' }) //reactive() 也会隐式地从它的参数中推导类型,template里使用时不会自动添加.value
console.log(book.title)
toRef:可以用来为源响应式对象上的某个 property 新创建一个 ref(响应式).一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
toRefs: 可以为一组源响应式属性添加响应式,接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行.
对于toRef/toRefs创建数据,数据改变UI视图不变,这是由于初始定义的数据是非响应式的.如(let state1 = {name: '张三'})
但是对于toRef/toRefs创建数据,如果原始数据是响应式的,则UI视图会变化(let state1 = reactive({name: '张三'}))
第一种
let state1 = ref({name: '张三'})
let state2 = toRef(state1.value, 'name')// 通过ref定义的值,需要通过.value获取,即state1.value
console.log(state2.value.name)
第二种
let state1 = reactive({name: '张三'})
let state2 = toRef(state1, 'name') // <=> toRefs(state1)
console.log(state2.value.name)
proxy对象如何取值.
1.JSON.parse(JSON.stringify(PDConfig.value.menuList)) //最有效
2.var list = toRaw(PDConfig.value.menuList)
Vite.config.ts
export default ({ mode }: ConfigEnv) => {
const dirRoot = process.cwd()
const env = loadEnv(mode, dirRoot)
return defineConfig({
base: '/', //等价于 vue3 的 publicPath. 配置打包出来的chunk.js等资源公共前缀路径
plugins: [
vue(),
ElementPlus(),
plainText(/\.hbs$/),
],
server: {
host: '0.0.0.0',
port: 8002
},
resolve: {
alias: {
'@': pathResolve('./src'),
'vue-i18n': pathResolve('./node_modules/vue-i18n/dist/vue-i18n.cjs.js'),
},
},
define: {
// setting vue-i18-next
// Suppress warning
__INTLIFY_PROD_DEVTOOLS__: false,
__DEV__: process.env.NODE_ENV !== 'production',
__PROD__: process.env.NODE_ENV === 'production',
},
optimizeDeps: {//默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包
include: [
'@monaco-editor/loader',
'accounting',
'axios',
'axios-mock-adapter',
'crypto-js',
'dayjs',
'echarts',
'echarts-wordcloud',
'element-plus',
'gsap',
'html2canvas',
'js-cookie',
'lodash-es',
'mockjs',
'monaco-editor',
'naive-ui',
'number-precision',
'particles.vue3',
'shortid',
'vue',
'vue-echarts',
'vue-i18n',
'vue-router',
],
exclude: [],
},
build: {
sourcemap: false,
outDir: 'dist',
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
}
},
},
esbuild: {
},
})
}
Watch
总结:vue3 wacth后是一个小括号,在小括号中操作,如果要监听ref单个的值,监听的值逗号箭头函数,箭头函数里面有两个形参,new,old,可以在对象中直接打印,监听多个值,可以在监听值的地方使用数组,多个值以逗号分隔
watch(pRef.value.obj, (newVal, oldVal)=> { //对象无需 ()=>处理
console.log(newVal, oldVal)
},{deep:true})
监听ractive复杂对象类型 :wacth小括号,监听的对象逗号箭头函数,箭头函数中有两个值,new,old,在箭头函数对象中可以直接打印,如果要开启深度监听,在箭头函数后面加逗号,逗号后面是对象,对象中开启deep:true
例如:watch(() =>pRef.value.width, (newVal, oldVal)=> { //基本类型变量需()=>处理 否则报错
console.log(newVal, oldVal)
},{deep:true})
如果要监听对象某一个值,例如person,在wacth小括号里以箭头函数形式监听对象某一个值
例如:watch(() =>person.name, (newVal, oldVal)=> {
console.log(newVal, oldVal)
})
如果监听person对象里的多个值:wacth小括号中以数组形式书写,在数组中用箭头函数
例如:watch([() =>person.name, ()=> person.age], (newVal, oldVal)=> {
console.log(newVal, oldVal)
})
监听多个参数执行不同的方法
不过以上只是一些简单的例子,对于vue2中,watch可以监听多个源,并且执行不同的函数
正在上传…重新上传取消
在vue3中同理也能实现相同的情景,通过多个watch来实现,但在vue2中,只能存在一个watch
watch(count, () => {
console.log("count改变了");
});
watch(
() => book.name,
() => {
console.log("书名改变了");
}
);
监听多个参数执行相同的方法
在vue2中可能存在需要执行同一事件的情况,解决方法通常是利用computed把他们存入一个对象中,监听这个对象的变化
正在上传…重新上传取消
对于vue3,可以免去computed的操作
watch([() => book.name, count], ([name, count], [preName, preCount]) => {
console.log("count或book.name改变了");
});
挂载全局变量
2种方式:
- 通过provide/inject
2.注册全局变量
const app = createApp(App)
app.config.globalProperties.$httpUrl = 'https://www.baidu.com'
使用
import { defineComponent, getCurrentInstance, onMounted } from "vue"
export default defineComponent({
onMounted(() => {
// console.log(this)
const { appContext : { config: { globalProperties } } } = getCurrentInstance()
console.log(globalProperties.$httpUrl)
})
})
进阶
Pinia(vuex5,更适合vue3)
(1)它没有mutation,他只有state,getters,action【同步、异步】,可以直接修改state数据.而使用Vuex的时候每次修改state的值都需要调用mutations里的修改函数,因为Vuex需要追踪数据的变化
(2)他默认也是存入内存中,如果需要使用本地存储,使用pinia-plugin-persist
(3)pinia没有modules配置,没一个独立的仓库都是definStore生成出来的
(4) pinia调用action,不需要在使用dispatch函数,直接调用store方法即可.一旦 store 被实例化,你就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性. 也支持vuex那样mapStores()、mapState() 或 mapActions()获取state的各种属性
import { defineStore } from "pinia";
export const storeA = defineStore("storeA", {
state: () => {
return {
piniaMsg: "hello pinia",
name: "xiao yue",
};
},
getters: { // 与计算属性一样,可以组合多个getter。通过this访问任何其他 getter. 有缓存
nameGetter(state) {// 自动将返回类型推断为字符串
return state.name;
},
},
actions: {
setName(data) {
this.name = data; //直接修改state.
},
},
});
组件App.vue中调用不需要再使用dispatch函数,直接调用store的方法即可
import { storeA } from '@/piniaStore/storeA'
export default {
setup() {
let piniaStoreA = storeA()
piniaStoreA.setName('daming') //action方法修改state <=> piniaStoreA.$patch({name:'daming'})
console.log(piniaStoreA.name) //state变量
console.log(piniaStoreA.nameGetter) // getter变量也是直接获取, 无需getter.xxx
let { piniaMsg, name } = storeToRefs(piniaStoreA) //state解构
}
}
也支持vuex那样mapStores()、mapState() 或 mapActions()获取state的各种属性.
export default {
computed: {
// gives access to this.counterStore and this.userStore
...mapStores(useCounterStore, useUserStore)
// gives read access to this.count and this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// gives access to this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
eventBus
Vue3不再提供$on与emit函数,Vue实例不再实现事件接口。官方推荐引入外部工具实现,或者自己手撸一个事件类
npm install --save mitt
main.ts中
import mitt from 'mitt'
const eventBus = mitt();
const app = createApp(AppRoot);
app.provide('eventBus', eventBus); // 注入provider
使用:
import { inject } from "vue";
const eventBus: any = inject("eventBus");
watch(
[() => canvasConfig.value.width, () => canvasConfig.value.height],
(resizeVal) => {
eventBus.emit("resize", resizeVal);
}
);
eventBus.on("resize", (arr) => { //所有操作统一通过eventBus监听事件处理
graph.value.resize(arr[0], arr[1]);
});
vue-router4
用法与vue-router3差不多,新增了部分函数
useRoute相当于以前的this.$route
useRouter相当于this.$router
hash模式使用createWebHashHistory
history模式使用createWebHistory
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const router = createRouter({
history: createWebHistory('/ecdesigner'), //base配置. /即/ecdesigner
routes,
})
获取param参数:
1.route.params.id获取
import { useRouter,useRoute } from 'vue-router' //useRoute相当于以前的this.$route,而useRouter相当于this.$router
setup(props) {
const route = useRoute();
console.log(route.params.id);
const router = useRouter();
router.push({ path: "/screen-editor/" + route.params.id });
}
2.通过props获取 (props设为true, route.params将会被设置为组件属性)
{
path: '/admin/screen/:id,
name: 'ScreenEditor',
props: true,
component: () => import('@/views/screen-editor/index.vue'),
}
setup(props) {
console.log(props.id); //
}