## 安装或者升级
npm install -g @vue/cli (相比vue2 多了个@)
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project
npm init vite-app
cd
npm install
npm run dev
npm init @vitejs/app // 如果还要安装ts的话,推荐这个 自己 选择安装配置
1.更小,----包的体积更小;全局API,内置组件功能支持tree-shaking,打包的时候只打包了核心代码,核心代码
控制在10kb内
2.更快,------基于proxy的变动侦测,性能整体优于vue2的getter/setter(defineProoperty监听劫持);
proxy监听的是一个对象,(当一个对象内部很多变量变化时候都能监听到,包括数据的变
化)defineProoperty递归去遍历每个字段,只有遍历每个字段添加监听之后才知道这个字段有没有变化,
3.加强API一致性,
optionsApi
可能是一个功能,他就是要把你这个功能分来,data,methods,computed等;
5.提高自身可维护性;
6.v-dom重构优化
vue2-这里有A组件修改一个变量,A组件里setter劫持,通知Watcher,Watcher更新DOM;
Watcher对比diff算法的颗粒度是组件级别的,他不会吧整个组件的根数,大树diff一遍,他只会你那个组件变
化了他diff一遍,生成patch文件,Watcher拿着这个patch文件更新视图;
vue3
1.VDOM优化(增加静态标记)
2.静态提升
3.事件缓存(vue2里面只要变化就重新生成VDom树)
vite不去编译es6 ,把vue文件编译render函数返回让浏览器去识别
vite可以设置
__dirname这是node的一个全局模块,叫当前项目的名称
resolve: {
alias:{
‘@’: path.resolve( __dirname, ‘./src’ )
}
},
新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
API 自动导入
setup语法让我们不用再一个一个的把变量和方法都return出去就能在模板上使用,大大的解放了我们的双手。然而对于一些常用的VueAPI,比如ref、computed、watch等,还是每次都需要我们在页面上手动进行import
我们可以通过unplugin-auto-import实现自动导入,无需import即可在文件里使用Vue的API。
作用: 定义一个数据的响应式
语法: const xxx = ref(initValue):
创建一个包含响应式数据的引用(reference)对象
js中操作数据: xxx.value
模板中操作数据: 不需要.value
一般用来定义一个基本类型的响应式数据
作用: 定义多个数据的响应式
const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
核心:
对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, ‘count’, {
get () {},
set () {}
})
问题
对象直接新添加的属性或删除已有属性, 界面不会自动更新
直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
核心:
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
setup执行的时机
在beforeCreate之前执行(一次), 此时组件对象还没有创建
this是undefined, 不能通过this来访问data/computed/methods / props
其实所有的composition API相关回调函数中也都不可以
setup的返回值
一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
返回对象中的方法会与methods中的方法合并成功组件对象的方法
如果有重名, setup优先
注意:
一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
setup的参数
setup(props, context) / setup(props, {attrs, slots, emit})
props: 包含props配置声明且传入了的所有属性的对象
attrs: 包含没有在props配置中声明的属性的对象, 相当于 this. a t t r s s l o t s : 包 含 所 有 传 入 的 插 槽 内 容 的 对 象 , 相 当 于 t h i s . attrs slots: 包含所有传入的插槽内容的对象, 相当于 this. attrsslots:包含所有传入的插槽内容的对象,相当于this.slots
emit: 用来分发自定义事件的函数, 相当于 this.$emit
是Vue3的 composition API中2个最重要的响应式API
ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
computed函数:
与computed配置功能一致
只有getter
有getter和setter
watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
vue2.x的生命周期–vue3的生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
onRenderTracked
onRenderTriggered
使用Vue3的组合API封装的可复用的功能函数
自定义hook的作用类似于vue2中的mixin技术
自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive 对象取出的所有属性值都是非响应式的
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
在传统的Vue OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 ,滚动条反复上下移动
使用Compisition API,我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
aaaa
aaaa
I'm a teleported modal!
(My parent is "body")
先整个路由 4.0版本以上否则引入不了你需要的组件
大概是这个样子
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import Container from '../components/container/src/index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
component: Container,
children: [
{
path: '/',
component: Home
}
]
}
]
const router = createRouter({
routes,
history: createWebHistory()
})
export default router
main引入 然后 app.vue 加入口
就到指定的 组件去了
引入element-plus 三兄弟 图标 样式 组件
import * as Icons from '@element-plus/icons'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
slot
子组件 :
<slot v-if="slots.default"></slot>
<div v-else>{{ text }}</div>
先读slot 然后读text
父组件
<child text="我是父组件text"></child>
我是父组件text
<child >我是父组件html</child>
我是父组件html
<child text="我是父组件text">我是父组件html</child>
我是父组件html
defineProps
子组件接受参数
<child text ></child>
子组件 通过 defineProps接受参数
let props = defineProps({
text: {
type:Boolean,
default:false
},
})
当父组件传text没有定义时候 默认就是 :text="text"
因为 这个type:boolean强制类型转换 成了 true 就不用 :text="true"这样传值了
父子传值
父:
<child
v-model:editRowIndex="editRowIndex"
@confirm="confirm"
@size-change="handleSizeChange">
</child>
子:
let emits = defineEmits(['confirm', 'update:editRowIndex', 'size-change'])
emits('confirm', scope)
emits('update:editRowIndex', '')
emits('size-change', val)
例子
实现点击按钮出现 弹窗
这个弹窗是个封装好的子组件
在子组件写 给父组件留个坑这是按钮
然后再 子组件的 外面包个按钮样式
弹窗部分 通过handleClick 事件显示弹窗
----------parent
<m-choose-icon title="选择图标" v-model:visible="visible">选择图标</m-choose-icon>
let visible = ref<boolean>(false)
------------child
<el-button @click="handleClick" type="primary">
<slot></slot>//父级元素在这里
</el-button>
<div class="m-choose-icon-dialog-body-height">
<el-dialog :title="title" v-model="dialogVisible">
</el-dialog>
</div>
let dialogVisible = ref<boolean>(props.visible)
//点击触发 开关
let handleClick = () => {
// 需要修改父组件的数据
emits('update:visible', !props.visible)
}
// 监听visible的变化 只能监听第一次的变化
watch(() => props.visible, val => {
dialogVisible.value = val
})
// 监听组件内部的dialogVisible变化
watch(() => dialogVisible.value, val => {
emits('update:visible', val)
})
在vue2中这样使用
<template>
<div>
{{ count }}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['count'])
}
}
</script>
在vue3中这样使用
造轮子
import { computed } from 'vue'
import { useStore } from 'vuex'
const mapState = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store.state).map(
key => [key, computed(() => store.state[key])]
)
)
}
const mapGetters = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store.getters).map(
getter => [getter, computed(() => store.getters[getter])]
)
)
}
const mapMutations = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store._mutations).map(
mutation => [mutation, value => store.commit(mutation, value)]
)
)
}
const mapActions = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store._actions).map(
action => [action, value => store.dispatch(action, value)]
)
)
}
export { mapState, mapGetters, mapMutations, mapActions }
使用方法
重点注意 :需要在setup函数中使用 否则 这些const store = useStore();未定义
引入文件
import { mapState, mapGetters, mapMutations, mapActions } from '../store/mapstate'
直接使用
// const { count } = mapState()
// const { countIsOdd } = mapGetters()
// const { countUp } = mapMutations()
// const { getRemoteCount } = mapActions()
分了模块的话就要修改点内容
goods模块下 这个CHANGE_ORDERLIST 之后就可以直接用了
const { 'goods/CHANGE_ORDERLIST': CHANGE_ORDERLIST } = mapMutations()
这个可能之后传多个参数 还是带个参数
CHANGE_ORDERLIST({ 'orderList': orderLists })
mutaion接受这个参数
CHANGE_ORDERLIST: (state, res) => {
state.orderList = [...res.orderList];
}
在 store下建不同模块modules每个模块里面放state,mutation,action;
在store下建getters读取每个模块的 state;
在store下建index 引入getters与模块
引入这玩意固化缓存
import createPersistedState from ‘vuex-persistedstate’
这样 刷新 在vuex的 数据就不会丢失
购物车的数据就不会因为刷新而丢失
.env.dev 环境配置
NODE_ENV=development
VITE_Name=jack
console.log(“环境变量=>”,import.meta.env)
添加.d.ts文件
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
}
})
npm i vite-plugin-vue-images -D
import { defineConfig } from 'vite'
import ViteImages from 'vite-plugin-vue-images'
export default defineConfig({
plugins: [
ViteImages({
dirs: ['src/assets/image'] // 指明图片存放目录
})
]
})
<!-- 直接使用 -->
<img :src="Logo" />
setup语法让我们不用再一个一个的把变量和方法都return出去就能在模板上使用,大大的解放了我们的双手。然而对于一些常用的VueAPI,比如ref、computed、watch等,还是每次都需要我们在页面上手动进行import。
我们可以通过unplugin-auto-import实现自动导入,无需import即可在文件里使用Vue的API。
npm i unplugin-auto-import -D
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
// dts: 'src/auto-imports.d.ts', // 可以自定义文件生成的位置,默认是根目录下
imports: ['vue']
})
]
})
使用
解决eslint报错问题
// 安装依赖
npm i vue-global-api -D
// eslintrc.js
module.exports = {
extends: [
'vue-global-api'
]
}
vite.config.ts
plugins: [
vue({
refTransform: true // 开启ref转换
})
]
let count = $ref(1)
const addCount = () => {
count++
}
解决ts提示$ref报错
env.d.ts
///
ref vs.响应式变量
//$ref()方法是一个编译时的宏命令:它不是一个真实的、在运行时会调用的方法
//这些宏函数都是全局可用的,它们的类型需要被显式地引用(例如,在env.d.ts文件中)
每一个会返回ref的响应性 API 都有一个相对应的、以$为前缀的宏函数。包括以下这些 API:
ref→$ref
computed→$computed
shallowRef→$shallowRef
customRef→$customRef
toRef→$toRef
虽然响应式变量使我们可以不再受.value的困扰,但它也使得我们在函数间传递响应式变量时可能造成“响应性丢失”的问题。这可能在以下两种场景中出现
1.假设有一个期望接收一个 ref 对象为参数的函数:
function trackChange(x: Ref) {
watch(x, (x) => {
console.log('x 改变了!')
})
}
let count = $ref(0)
trackChange(count)
解决方案
trackChange($$(count))//$$就像是一个转义标识
2.作为函数返回值
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// 监听 mousemove 事件
// 不起效!
return {
x,
y
}
}
// 监听 mousemove 事件
// 修改后起效
return $$({
x,
y
})
Composition API 就是在组件配置对象中声明 setup函数,我们可以将所有的逻辑封装在setup函数中,然后在配合vue3中提供的响应式API 钩子函数、计算属性API等,我们就能达到和常规的选项式同样的效果,但是却拥有更清晰的代码以及逻辑层面的复用
<template>
<div ref="composition">测试compositionApi</div>
</template>
<script>
import { inject, ref, onMounted, computed, watch } from "vue";
export default {
// setup起手
setup(props, { attrs, emit, slots, expose }) {
// 获取页面元素
const composition = ref(null)
// 依赖注入
const count = inject('foo', '1')
// 响应式结合
const num = ref(0)
//钩子函数
onMounted(() => {
console.log('这是个钩子')
})
// 计算属性
computed(() => num.value + 1)
// 监听值的变化
watch(count, (count, prevCount) => {
console.log('这个值变了')
})
return {
num,
count
}
}
}
</script>
一个setup函数我们干出了在传统选项式中的所有事情
实现单点登录 (以往的sessionId不能实现,因为生产一个sessionId通过cookie传给后台后,后台可能存一个服务器,之后登陆传到另一个集群,这个集群没有吧sessionId写入内存,可能检验后你需要重新登录)