浅学Vue3
vue3初步认识
安装 vue项目
npm init vue@latest 回车
装包
npm install
路由
安装 Router
npm install vue-router@4 -S
项目根目录新建 router --> index.js
vue2中写法
index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: '/',
name: 'Home',
component: Home
}
]
});
export default router;
main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
new Vue({
router
}).$mount('#app');
vue3中写法
index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
main.js
import { createApp } from 'vue';
import App from './App.vue';
import { router } from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
路由缓存问题
问题展示
一级分类的切换正好满足上边的条件,组件示例服用,导致分类数据无法更新
解决思路:
1. 让组件实例不复用,强制销毁重建
<RouterView :key="$route.fullPath" />
2. 监听路由变化,变化之后执行数据更新操作 (可以精细化控制)
路由参数变化的时候 重新请求接口
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate(async (to, from) => {
console.log(to)
})
es6新特性:默认参数
useRoute 和 useRouter
useRoute
主要用于获取当前路由的信息,而 useRouter
主要用于获取整个应用中的路由器实例,方便进行全局的导航。
列表无限加载功能
实现逻辑:
监听是否满足触底条件,满足时让页数加1获取下一页数据,做新老数据拼接渲染,加载完毕结束监听
可借助 监听是否触底
指定路由切换时的滚动位置
借助 scrollBehavior
滚动到顶部
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 }
},
ES6中js的运算符 ?.
、?:
、??
、?=
const user = {
name: 'John',
address: {
city: 'New York',
},
};
// 在可选链上使用 ?. 防止空指针异常
const city = user?.address?.city; // 如果 address 或 city 为 null/undefined,不会抛出异常
console.log(city); // 输出: New York
const age = 20;
const status = age >= 18 ? 'Adult' : 'Minor';
console.log(status); // 如果 age 大于等于 18,输出: Adult;否则输出: Minor
const defaultValue = 'Default';
const userValue = null;
const result = userValue ?? defaultValue;
console.log(result); // 如果 userValue 为 null 或 undefined,输出: Default;否则输出 userValue 的值
let existingValue = 'Existing Value';
let newValue = null;
// 只有当 newValue 不为 null 或 undefined 时才进行赋值
existingValue ??= newValue;
console.log(existingValue); // 如果 newValue 为 null 或 undefined,existingValue 的值不变;否则将 existingValue 赋值为 newValue
通过小图切换大图显示
1.准备图片列表
2.左边是大图 右边是小图列表
3.给小图加上鼠标移入事件 当鼠标移入时,获取当前图片的下标值并给左边大图显示
4.当下标值和目标值相等时,加入激活类
<script setup>
import { ref } from "vue";
// 图片列表
const imageList = [
"https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
"https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
"https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
"https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
"https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg",
];
// 小图切换大图
const activeIndex = ref(0);
// 鼠标移入事件
const enterhandler = (i) => {
activeIndex.value = i;
};
</script>
<template>
<div class="goods-image">
<!-- 左侧大图-->
<div class="middle" ref="target">
<img :src="imageList[activeIndex]" alt="" style="width: 400px" />
<!-- 蒙层小滑块 -->
<div class="layer" :style="{ left: `0px`, top: `0px` }"></div>
</div>
<!-- 小图列表 -->
<ul class="small">
<li
v-for="(img, i) in imageList"
:key="i"
@mouseenter="enterhandler(i)"
:class="{ active: i === activeIndex }"
>
<img :src="img" alt="" style="height: 70px" />
</li>
</ul>
<!-- 放大镜大图 -->
<div
class="large"
:style="[
{
backgroundImage: `url(${imageList[0]})`,
backgroundPositionX: `0px`,
backgroundPositionY: `0px`,
},
]"
v-show="false"
></div>
</div>
</template>
<style scoped lang="scss">
.goods-image {
width: 480px;
height: 400px;
position: relative;
display: flex;
.middle {
width: 400px;
height: 400px;
background: #f5f5f5;
}
.large {
position: absolute;
top: 0;
left: 412px;
width: 400px;
height: 400px;
z-index: 500;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-repeat: no-repeat;
// 背景图:盒子的大小 = 2:1 将来控制背景图的移动来实现放大的效果查看 background-position
background-size: 800px 800px;
background-color: #f8f8f8;
}
.layer {
width: 200px;
height: 200px;
background: rgba(0, 0, 0, 0.2);
// 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来
left: 0;
top: 0;
position: absolute;
}
.small {
width: 80px;
li {
width: 68px;
height: 68px;
margin-left: -20px;
margin-bottom: 15px;
cursor: pointer;
&:hover,
&.active {
border: 2px solid #009bf5;
}
}
}
}
ul li {
list-style: none;
}
</style>
全局组件统一插件化
全局注册组件,页面使用时 无需再导入,直接使用即可
1. 新建 index.js
// 把components中的所组件都进行全局化注册
// 通过插件的方式
import Sku from "./XtxSku/index.vue";
export const componentPlugin = {
install(app) {
// app.component('组件名字',组件配置对象)
app.component("XtxSku", Sku);
},
};
2. main.js 注册
// 引入全局组件插件
import { componentPlugin } from '@/components'
app.use(componentPlugin)
3. 页面使用
放大镜效果实现
1. 获取鼠标在大图中的坐标
2. 放入滑块,并监听鼠标的位置,得出滑块的位置,得出放大镜大图的位置
3. 是否显示隐藏滑块 和 放大镜大图
<script setup>
import { ref, watch } from "vue";
import { useMouseInElement } from "@vueuse/core";
// 图片列表
const imageList = [
"https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
"https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
"https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
"https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
"https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg",
];
// 小图切换大图
const activeIndex = ref(0);
// 鼠标移入事件
const enterhandler = (i) => {
activeIndex.value = i;
};
// 获取鼠标相对位置
const target = ref(null);
const { elementX, elementY, isOutside } = useMouseInElement(target);
// 控制滑块跟随鼠标移动(监听elementX/Y变化,一旦变化 重新设置left/top)
const left = ref(0);
const top = ref(0);
// 大图坐标
const positionX = ref(0);
const positionY = ref(0);
watch([elementX, elementY, isOutside], () => {
if (isOutside.value) return;
// 有效范围内控制滑块距离
// 横向
if (elementX.value > 100 && elementX.value < 300) {
left.value = elementX.value - 100;
}
// 纵向
if (elementY.value > 100 && elementY.value < 300) {
top.value = elementY.value - 100;
}
// 处理边界
if (elementX.value > 300) {
left.value = 200;
}
if (elementX.value < 100) {
left.value = 0;
}
if (elementY.value > 300) {
top.value = 200;
}
if (elementY.value < 100) {
top.value = 0;
}
// 控制大图的显示
positionX.value = -left.value * 2;
positionY.value = -top.value * 2;
});
</script>
<template>
<div class="goods-image">
<!-- 左侧大图-->
<div class="middle" ref="target">
<img :src="imageList[activeIndex]" alt="" style="width: 400px" />
<!-- 蒙层小滑块 -->
<div
class="layer"
v-show="!isOutside"
:style="{ left: `${left}px`, top: `${top}px` }"
></div>
</div>
<!-- 小图列表 -->
<ul class="small">
<li
v-for="(img, i) in imageList"
:key="i"
@mouseenter="enterhandler(i)"
:class="{ active: i === activeIndex }"
>
<img :src="img" alt="" style="height: 70px" />
</li>
</ul>
<!-- 放大镜大图 -->
<div
class="large"
:style="[
{
backgroundImage: `url(${imageList[activeIndex]})`,
backgroundPositionX: `${positionX}px`,
backgroundPositionY: `${positionY}0px`,
},
]"
v-show="!isOutside"
></div>
</div>
</template>
<style scoped lang="scss">
.goods-image {
width: 480px;
height: 400px;
position: relative;
display: flex;
.middle {
width: 400px;
height: 400px;
background: #f5f5f5;
}
.large {
position: absolute;
top: 0;
left: 412px;
width: 400px;
height: 400px;
z-index: 500;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-repeat: no-repeat;
// 背景图:盒子的大小 = 2:1 将来控制背景图的移动来实现放大的效果查看 background-position
background-size: 800px 800px;
background-color: #f8f8f8;
}
.layer {
width: 200px;
height: 200px;
background: rgba(0, 0, 0, 0.2);
// 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来
left: 0;
top: 0;
position: absolute;
}
.small {
width: 80px;
li {
width: 68px;
height: 68px;
margin-left: -20px;
margin-bottom: 15px;
cursor: pointer;
&:hover,
&.active {
border: 2px solid #009bf5;
}
}
}
}
ul li {
list-style: none;
}
</style>
SKU组件
<script setup>
import { onMounted, ref } from "vue";
import axios from "axios";
import powerSet from "./power-set";
// 商品数据
const goods = ref({});
// 数据获取完毕生成路径字典
const getGoods = async () => {
// 1135076 初始化就有无库存的规格
// 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)
const res = await axios.get(
"http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1135076"
);
goods.value = res.data.result;
const pathMap = getPathMap(goods.value);
console.log("pathMap==", pathMap);
// 初始化更新按钮状态
initDisabledState(goods.value.specs, pathMap);
};
onMounted(() => getGoods());
// 切换选中状态
const changeSku = (item, val) => {
if (val.disabled) return;
// item 同一排的对象
// val 当前点击项
if (val.selected) {
// 如果当前是激活状态 则取消激活
val.selected = false;
} else {
// 如果当前是未激活状态 则取消同排的激活状态 并激活自己
item.values.forEach((val) => (val.selected = false));
val.selected = true;
}
};
// 创建生成路径字典对象函数
const getPathMap = (goods) => {
// console.log("goods.skus===", goods.skus);
const pathMap = {};
// 得到所有有效的sku集合
const effectiveSkus = goods.skus.filter((sku) => sku.inventory > 0);
// console.log("effectiveSkus===========", effectiveSkus);
// 根据有效的sku集合使用算法得到所有的子集 [1,2] => [[1], [2], [1,2]]
effectiveSkus.forEach((sku) => {
// 获取可选规格值数组
const selectedValArr = sku.specs.map((val) => val.valueName);
// console.log("selectedValArr=====", selectedValArr); // 多个数组 格式 ['黑色', '20cm', '中国']
// 获取可选值数组的子集
const valueArrPowerSet = powerSet(selectedValArr);
// console.log("valueArrPowerSet=====", valueArrPowerSet);
/* [
[],
['黑色'],
['20cm'],
['黑色', '20cm'],
['中国'],
['黑色', '中国'],
['20cm', '中国'],
['黑色', '20cm', '中国']
]
*/
// 根据子集生成路径字典对象
valueArrPowerSet.forEach((arr) => {
// 根据Arr得到字符串的key,约定使用-分割 ['蓝色','美国'] => '蓝色-美国'
const key = arr.join("-");
// 给pathMap设置数据
if (pathMap[key]) {
pathMap[key].push(sku.id);
} else {
pathMap[key] = [sku.id];
}
});
// console.log("pathMap===", pathMap);
});
return pathMap;
};
// 1. 定义初始化函数
// specs:商品源数据 pathMap:路径字典
const initDisabledState = (specs, pathMap) => {
// 约定:每一个按钮的状态由自身的disabled进行控制
specs.forEach((item) => {
item.values.forEach((val) => {
// 路径字典中查找是否有数据 有-可以点击 没有-禁用
val.disabled = !pathMap[val.name];
});
});
};
</script>
<template>
<div class="goods-sku">
<dl v-for="item in goods.specs" :key="item.id">
<dt>{{ item.name }}</dt>
<dd>
<template v-for="val in item.values" :key="val.name">
<!-- 图片类型规格 -->
<img
v-if="val.picture"
@click="changeSku(item, val)"
:class="{ selected: val.selected, disabled: val.disabled }"
:src="val.picture"
:title="val.name"
/>
<!-- 文字类型规格 -->
<span
v-else
:class="{ selected: val.selected, disabled: val.disabled }"
@click="changeSku(item, val)"
>{{ val.name }}</span
>
</template>
</dd>
</dl>
</div>
</template>
<style scoped lang="scss">
@mixin sku-state-mixin {
border: 1px solid #e4e4e4;
margin-right: 10px;
cursor: pointer;
&.selected {
border-color: #27ba9b;
}
&.disabled {
opacity: 0.6;
border-style: dashed;
cursor: not-allowed;
}
}
.goods-sku {
padding-left: 10px;
padding-top: 20px;
dl {
display: flex;
padding-bottom: 20px;
align-items: center;
dt {
width: 50px;
color: #999;
}
dd {
flex: 1;
color: #666;
> img {
width: 50px;
height: 50px;
margin-bottom: 4px;
@include sku-state-mixin;
}
> span {
display: inline-block;
height: 30px;
line-height: 28px;
padding: 0 20px;
margin-bottom: 4px;
@include sku-state-mixin;
}
}
}
}
</style>
<script setup>
import { onMounted, ref } from "vue";
import axios from "axios";
import powerSet from "./power-set";
// 商品数据
const goods = ref({});
let pathMap = {};
// 数据获取完毕生成路径字典
const getGoods = async () => {
// 1135076 初始化就有无库存的规格
// 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)
const res = await axios.get(
"http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1369155859933827074"
);
goods.value = res.data.result;
pathMap = getPathMap(goods.value);
console.log("pathMap==", pathMap);
// 初始化更新按钮状态
initDisabledState(goods.value.specs, pathMap);
};
onMounted(() => getGoods());
// 切换选中状态
const changeSku = (item, val) => {
if (val.disabled) return;
// item 同一排的对象
// val 当前点击项
if (val.selected) {
// 如果当前是激活状态 则取消激活
val.selected = false;
} else {
// 如果当前是未激活状态 则取消同排的激活状态 并激活自己
item.values.forEach((val) => (val.selected = false));
val.selected = true;
}
// 点击按钮时更新
updateDisabledState(goods.value.specs, pathMap);
// 产出SKU对象数据
const index = getSelectedValues(goods.value.specs).findIndex(
(item) => item === undefined
);
if (index > -1) {
console.log("找到了,信息不完整");
} else {
console.log("找到了,信息完整");
// 获取sku对象
const key = getSelectedValues(goods.value.specs).join("-");
console.log("key", key);
console.log("pathMap", pathMap);
const skuIds = pathMap[key];
console.log("skuIds", skuIds);
// 以 skuId 作为匹配项 去 goods.value.skus数组中找
const skuobj = goods.value.skus.find((item) => item.id === skuIds[0]);
console.log(skuobj);
}
};
// 创建生成路径字典对象函数
const getPathMap = (goods) => {
// console.log("goods.skus===", goods.skus);
const pathMap = {};
// 得到所有有效的sku集合
const effectiveSkus = goods.skus.filter((sku) => sku.inventory > 0);
// console.log("effectiveSkus===========", effectiveSkus);
// 根据有效的sku集合使用算法得到所有的子集 [1,2] => [[1], [2], [1,2]]
effectiveSkus.forEach((sku) => {
// 获取可选规格值数组
const selectedValArr = sku.specs.map((val) => val.valueName);
// console.log("selectedValArr=====", selectedValArr); // 多个数组 格式 ['黑色', '20cm', '中国']
// 获取可选值数组的子集
const valueArrPowerSet = powerSet(selectedValArr);
// console.log("valueArrPowerSet=====", valueArrPowerSet);
/* [
[],
['黑色'],
['20cm'],
['黑色', '20cm'],
['中国'],
['黑色', '中国'],
['20cm', '中国'],
['黑色', '20cm', '中国']
]
*/
// 根据子集生成路径字典对象
valueArrPowerSet.forEach((arr) => {
// 根据Arr得到字符串的key,约定使用-分割 ['蓝色','美国'] => '蓝色-美国'
const key = arr.join("-");
// 给pathMap设置数据
if (pathMap[key]) {
pathMap[key].push(sku.id);
} else {
pathMap[key] = [sku.id];
}
});
// console.log("pathMap===", pathMap);
});
return pathMap;
};
// 1. 定义初始化函数
// specs:商品源数据 pathMap:路径字典
const initDisabledState = (specs, pathMap) => {
// 约定:每一个按钮的状态由自身的disabled进行控制
specs.forEach((item) => {
item.values.forEach((val) => {
// 路径字典中查找是否有数据 有-可以点击 没有-禁用
val.disabled = !pathMap[val.name];
});
});
};
// 获取选中匹配数组 ['黑色',undefined,undefined]
const getSelectedValues = (specs) => {
// console.log("specs", specs);
const arr = [];
specs.forEach((spec) => {
const selectedVal = spec.values.find((value) => value.selected);
// console.log("selectedVal", selectedVal);
// 选中为 true 保存名字 未选中 则为false 保存 undefined
arr.push(selectedVal ? selectedVal.name : undefined);
});
return arr;
};
// 点击时 更新禁用状态
const updateDisabledState = (specs, pathMap) => {
specs.forEach((spec, index) => {
const selectedValues = getSelectedValues(specs);
spec.values.forEach((val) => {
selectedValues[index] = val.name;
const key = selectedValues.filter((value) => value).join("-");
console.log(pathMap[key]);
if (pathMap[key]) {
val.disabled = false;
} else {
val.disabled = true;
}
});
});
};
</script>
<template>
<div class="goods-sku">
<dl v-for="item in goods.specs" :key="item.id">
<dt>{{ item.name }}</dt>
<dd>
<template v-for="val in item.values" :key="val.name">
<!-- 图片类型规格 -->
<img
v-if="val.picture"
@click="changeSku(item, val)"
:class="{ selected: val.selected, disabled: val.disabled }"
:src="val.picture"
:title="val.name"
/>
<!-- 文字类型规格 -->
<span
v-else
:class="{ selected: val.selected, disabled: val.disabled }"
@click="changeSku(item, val)"
>{{ val.name }}</span
>
</template>
</dd>
</dl>
</div>
</template>
<style scoped lang="scss">
@mixin sku-state-mixin {
border: 1px solid #e4e4e4;
margin-right: 10px;
cursor: pointer;
&.selected {
border-color: #27ba9b;
}
&.disabled {
opacity: 0.6;
border-style: dashed;
cursor: not-allowed;
}
}
.goods-sku {
padding-left: 10px;
padding-top: 20px;
dl {
display: flex;
padding-bottom: 20px;
align-items: center;
dt {
width: 50px;
color: #999;
}
dd {
flex: 1;
color: #666;
> img {
width: 50px;
height: 50px;
margin-bottom: 4px;
@include sku-state-mixin;
}
> span {
display: inline-block;
height: 30px;
line-height: 28px;
padding: 0 20px;
margin-bottom: 4px;
@include sku-state-mixin;
}
}
}
}
</style>
别名路径联想设置
1. 根目录新建 jsconfig.json 文件
2. 添加配置项
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*":["src/*"]
}
}
}
3. 最终效果
axios基础配置
axios 拦截器
import axios from "axios";
// 创建实例
const http = axios.create({
baseURL: "http://pcapi-xiaotuxian-front-devtest.itheima.net", // 基地址
timeout: 5000, // 超时时间
});
// 添加请求拦截器
http.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
http.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
},
function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
}
);
export default http;
请求拦截器携带Token
具体使用
最终每个请求都会自动携带token
Token失效处理
vueuse
插槽
准备模板
<script setup>
// 接收参数
defineProps({
msg: {
type: String,
required: true,
},
});
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<!-- 主体内容区域 -->
<slot name="main" />
</div>
</template>
<style scoped>
</style>
渲染模板
<script setup>
import HelloWorld from "@/components/HelloWorld.vue";
</script>
<template>
<!-- 插槽 -->
<HelloWorld msg="You did it!">
<template #main>
<ul>
<li>111111</li>
<li>111111</li>
<li>111111</li>
<li>111111</li>
<li>111111</li>
</ul>
</template>
</HelloWorld>
</template>
<style scoped>
</style>
懒加载指令实现
图片进入视口区域,再发送请求 借助 useIntersectionObserver | VueUse中文文档
main.js中
import { useIntersectionObserver } from "@vueuse/core";
// 定义全局指令
// 懒加载指令逻辑
app.directive("img-lazy", {
mounted(el, binding) {
// el: 指令绑定的那个元素 img
// binding: binding.value 指令等于号后面绑定的表达式的值 图片url
console.log(el, binding.value);
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
console.log(isIntersecting);
if (isIntersecting) {
// 进入视口区域
el.src = binding.value;
// 手动停止监听
stop();
}
});
},
});
需要懒加载的图片
<img v-img-lazy="item.picture" alt="" />
最终效果
写法
vue2中
// 模板 必须有div包裹
<template>
<div>首页</div>
</template>
// 选项式API
data() {
return {
数据
}
},
methods: {
方法
}
Vite构建的Vue3项目中
<template>
首页
</template>
// 组合式API
数据
let sliderList = ref([]);
方法
const mouseOut = () => {
};
修改数据
vue2中
export default {
data() {
return {
message: 'Hello, Vue!'
};
},
methods: {
updateMessage() {
this.message = 'Updated message';
}
}
}
vue3中
import { ref, reactive } from 'vue';
// setup语法糖插件 解决引入问题(自动引入所需要的插件)
// 下载安装 npm i unplugin-auto-import -D 在 vite.config.js 中进行配置
export default {
setup() {
// 使用 ref---可以定义基本数据类型 对象
const messageRef = ref('Hello, Vue!');
// 使用 reactive--可以定义对象 数组
const data = reactive({
message: 'Hello, Vue!',
count: 0
});
// 使用ref定义的数据 通过 .value 访问
const updateMessage = () => {
messageRef.value = 'Updated message';
};
// 使用 reactive 定义的数据 可直接 访问
const updateCount = () => {
data.count++;
};
return {
messageRef,
data,
updateMessage,
updateCount
};
}
}
ref函数将一个普通的JavaScript值封装成一个包含.value属性的响应式对象。
而reactive函数将一个普通的JavaScript对象转换为一个完全响应式的代理对象。
toRefs
// 解构==》响应式数据
let obj = reactive({
name:"张三",
age:20
})
let { name, age } = toRefs( obj )
异步组件(按需加载) ------- 提升性能
Vuex
// store index.js
import { createStore } from "vuex";
export default createStore({
state:{
num:10,
str:'这是store数据'
},
getters:{},
mutations:{},
actions:{},
modules:{}
})
// .vue
<template>
<div>
{{ num }}
</div>
</template>
<script setup>
import { useStore } from 'vuex'
let store = useStore
{/* 即时更新 */}
let num = computed ( () => { store.state.num })
</script>
Vuex 持久化存储
Pinia
1. 支持选项式API和组合式API写法
2. Pinia 没有 mutations,只有 state getters actions
3. Pinia 分模块不需要 modules(Vuex需要)
4. TS 支持很好
5. Pinia 体积更小(性能更好)
6. Pinia 可以直接修改 state 数据
// store index.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/apis/user'
export const useUserStore = defineStore('user', () => {
// 1. 定义管理用户数据的state
const userInfo = ref({})
// 2. 定义获取接口数据的action函数
const getUserInfo = async ({ account, password }) => {
const res = await loginAPI({ account, password })
userInfo.value = res.result
}
// 3. 以对象的格式把state和action return
return {
getUserInfo,
userInfo
}
}, {
// 持久化
persist: true,
})
// .vue
<script setup>
import { useUserStore } from '../store'
useUserStore.getUserInfo({ account, password })
</script>
快速开始 | pinia-plugin-persistedstate
单选功能
全选功能
更多推荐
所有评论(0)