computed&watch
2.x和3.x的用法比较
kotlin
### // 选项式api 用法
export default {
name:"argeement-dialog",
props:{
top:{
value:String,
default:() => '50%' // 距离顶部 %/px
},
},
data() {
return {
showDialog: false, //隐私弹窗
Interval: null,
countDown: 5, //弹窗倒计时
envConfig: this.$config,
agree: false, //勾选协议
isChoice: true //更新时是否去掉勾选
};
},
computed:{
currentPricacy () {
const currentPricacy = this.$store.state.privacy.currentPricacy
console.log("currentPricacy:>>>>>",currentPricacy)
return currentPricacy
},
},
// 监听从缓存获取的数据变化
watch: {
'currentPricacy.isMatch': {
handler: function(newVal,oldVal) {
console.log(newVal,oldVal)
if (newVal == 0) {
uni.hideTabBar()
this.showDialog = true
this.startCountDown()
// this.isChoice= false
console.log('数据变化了啊',oldVal)
if(this.updatePricacy && !this.updateDataPricacyVersion) { //隐私协议更新时且数据协议未更新时,去掉勾选
this.isChoice= false
} else{
this.isChoice= true
}
} else if (newVal == 1){
uni.showTabBar()
this.showDialog = false
this.isChoice= true
}
},
immediate: true
},
}
}
升级过渡用法
xml
<script type="text/javascript">
export default{
setup(){
let msg = ref('这是一个数据'); //响应式基本数据类型
let str = ref('这是str');
let obj = reactive({
a:1,
arr:['a','b','c']
})
watchEffect(()=>{
console.log( msg.value )
})
watch( ()=>obj.arr , (newVal,oldVal)=>{
console.log( newVal,oldVal )
})
watch( msg , (newVal,oldVal)=>{
console.log( newVal,oldVal )
},{
immediate:true
})
watch( str , (newVal,oldVal)=>{
console.log( newVal,oldVal )
},{
immediate:true
})
watch([msg,str],(newVal,oldVal)=>{
console.log( newVal,oldVal )
},{
immediate:true
})
return {
msg,
str,
obj
}
}
}
</script>
组合式api 用法
xml
<script setup>
let msg = ref('这是数据');
let str = ref('数据2');
let obj = reactive({
a:1,
arr:['a','b','c']
})
const currentPricacy = computed(() => {
let currentPricacy = JSON.parse(window.localStorage.getItem('currentPricacy'))
return currentPricacy
})
watch( currentPricacy, (newVal,oldVal)=>{
console.log( 33333,newVal)
if (newVal.isMatch == 0) {
alert('数据改变了',newVal.isMatch)
} else if (newVal.isMatch == 1){
console.log('数据未改变',newVal.isMatch)
}
},{
immediate:true
})
</script>
小结:
vue2.x :
javascript
watch:{
obj:{
handler function(newVal , oldVal){
console.log( newVal , oldVal )
},
immediate:true,
// deep:true
}
}
vue3.x :
javascript
1> 监听数据数据「初始化监听」
//
watch( msg , (newVal,oldVal)=>{
console.log( newVal,oldVal )
},{
immediate:true
})
2> 监听多个数据「一起监听」
watch([msg,str],(newVal,oldVal)=>{
console.log( newVal,oldVal )
},{
immediate:true
})
3> 监听"对象"中某个对象
watch( ()=>obj.arr , (newVal,oldVal)=>{
console.log( newVal,oldVal )
})
4> 立即执行监听函数
watchEffect(()=>{
console.log( msg.value )
})
参考链接:v3.cn.vuejs.org/api/compute...
provid&inject
2.0文档:cn.vuejs.org/api/reactiv...
kotlin
// provid&inject&Mixins一起使用
import mixins from './mixins/parentMixins'
export default {
data () {
return {
defaultData: {
searchSeo: {
keyword: '',
inKeyword: '',
outTitle: '',
outKeyword: '',
outDesc: '',
outSeo: '',
pictureLabel: ''
},
productDesc: {
productName: ''
}
},
isEdit: false,
prevTabIdx: '', // 從哪一個tab跳轉過來的 選填項
activeName: '1', // 當前所處選項卡位置
isGroup: false, // 是否是組合產品
isSaving: false, // 是否處於保存狀態
activeTab: [{ active: true }, { active: true }, { active: true }, { active: false }, { active: false }, { active: false }, { active: false }, { active: false }, { active: false }], // tab嬾加載處理
steps: [{ required: false }, { required: false }, { required: true }, { required: true }, { required: true }, { required: true }, { required: true }] // 決定是否必填
}
},
mixins: [mixins],
//provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。
provide () {
return {
addProduct: this
}
},
mounted () {
// 修改頁面標題
if (this.$route.params.type === '2') {
this.isGroup = true
this.$store.dispatch('editVisitedViews', { name: '新增組合產品', path: this.$route.fullPath })
}
this.$store.commit('ADDRELEASEPRODUCTBASEINFO')
},
methods: {
productDecCallback (data) {
this.bindDescAndSeoData()
},
changeTable () {
this.bindDescAndSeoData()
}
}
}
</script>
批量子组件
javascript
export default {
data() {
const validateCode = (rule, val, callback) => {
if (val === '' || val == null) {
callback(new Error('產品編號不能為空!'))
}
if (val.length < 6) {
callback(new Error('產品編號最少輸入6位!'))
}
if (val.length > 10) {
callback(new Error('產品編號最多隻能輸入10位!'))
}
callback()
}
// 中文名稱校驗
const validateChinese = (rule, value, callback) => {
if (value === '' || value == null) {
callback(new Error('中文名稱不能為空!'))
} else {
/* eslint no-control-regex: off */
if (value.replace(/[^\u0000-\u00ff]/g, 'aa').length > 200) {
// 正則表達式最多支持80英文字母或40漢字
callback(new Error('中文名稱不能超過200個字符!'))
} else {
callback()
}
}
}
// 外包裝材料
const validatePackingMaterial = (rule, value, callback) => {
if (value === '' || value == null) {
callback()
} else if (value.replace(/[^\u0000-\u00ff]/g, 'aa').length > 100) {
/* eslint no-control-regex: off */
// 正則表達式最多支持80英文字母或40漢字
callback(new Error('外包裝材料不能超過100個字符!'))
} else {
callback()
}
}
return {
disabledNum: false,
productCodeLabel: '產品編號',
baseInfo: {
id: '',
type: '2',
isHasWarehouse: 1, // 是否有实物库存
code: '', // 产品编号 必填
itemType: 1, // 产品分类 必填
name: '', // 产品中文名称 必填
alias: '', // 产品英文名称 必填
stockUnit: '', // 计量单位
selfPick: 1, // 是否可以自提
packingMaterial: '', // 外包装材料
length: undefined, // 包装尺寸 - 长
width: undefined, // 包装尺寸 - 宽
height: undefined, // 包装尺寸 - 高
volume: undefined, // 体积
weight: undefined, // 产品质量
isPresell: 0, // 是否支持預售
alertStock: undefined, // 警戒库存
isDirectSale: 1, // 是否直销
// isNeedFreight: 1, // 是否需要運費
validDate: '',
minBox: '', // Min Box 必填
freeFreightChannel: [],
excludeFreightChannel: [],
taxRate: '', // 稅率 必填
kit: '', // kit 必填
isInvoice: 0, // 是否開局發票
whetherSupermarket: 1, // 是否支持超商取貨
isPeriod: 0, // 是否支持分期
// isFreightFreeCalc: 0, // 是否免运费
isDisplayCatalog: 1, // 是否类目显示
allowMix: 1, // 是否允许混单
isShipment: 1, // 是否出货
shipmentScan: 1, // 出货扫码
returnScan: 0, // 退货扫码
tagLabel: '', // tag小标签
plpTag: '', // PLP小標籤
tagColor: 1, // tag底框颜色1:紫色2:黑色
suitCompos: [{ label: '套裝組成:', value: '' }], // 因为后端数据结构设计,套装组成和使用介绍不存入baseInfo对象中
useIntrs: [{ label: '使用介紹:', value: '' }]
},
isCheckedCode: false,
/* 表單校驗規則 */
rules: {
length: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
width: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
height: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
weight: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
alertStock: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
code: [{ required: true, validator: validateCode, trigger: 'blur' }],
itemType: [{ required: true, message: '請選擇產品分類', trigger: 'change' }],
type: [{ required: true, message: '請選擇組合產品類型', trigger: 'change' }],
name: [{ required: true, validator: validateChinese, trigger: 'change' }],
packingMaterial: [{ validator: validatePackingMaterial, trigger: 'change' }],
minBox: [{ required: true, message: '請輸入minBox', trigger: 'change' }],
taxRate: [{ required: true, message: '請選擇稅率', trigger: 'change' }],
kit: [{ required: true, message: '請選擇 kit 類型', trigger: 'change' }],
isFreightFreeCalc: [{ required: true, message: '請選擇是否參與免運費計算', trigger: 'change' }],
isInvoice: [{ required: true, message: '請選擇是否開具發票', trigger: 'change' }],
isPeriod: [{ required: true, message: '請選擇是否支持分期', trigger: 'change' }],
tagLabel: [{ max: 10, message: '長度限制為10個字符', trigger: 'blur' }],
plpTag: [{ max: 10, message: '長度限制為10個字符', trigger: 'blur' }]
},
/* 類型列表 */
typeList: [
{ value: 1, label: 'Nuskin' },
{ value: 2, label: 'Scion' },
{ value: 3, label: 'Her Shades' },
{ value: 4, label: 'Pharmanex' },
{ value: 5, label: 'Miscellanea' }
],
/* 單位列表 */
unitList: [
{ value: 1, label: '只' },
{ value: 2, label: '袋' },
{ value: 3, label: '件' },
{ value: 4, label: '套' },
{ value: 5, label: '個' },
{ value: 6, label: '包' },
{ value: 7, label: '盒' },
{ value: 8, label: '箱' }
],
/* 稅率列表 */
taxRateList: [
{ value: 0.05, label: '應稅(5%)' },
{ value: 0, label: '零稅' }
],
/* kit 列表 */
kitList: [
{ value: 1, label: '單品' },
{ value: 2, label: '一般Kit' },
{ value: 3, label: 'Crop Kit' }
],
freeChannelList: [...freeChannelMap.keys()].map(item => ({
label: freeChannelMap.get(item) + freeChannelExtra,
value: item
})),
moneyChannelList: [...moneyChannelMap.keys()].map(item => ({
label: moneyChannelMap.get(item) + moneyChannelExtra,
value: item
}))
}
},
inject: ['addProduct'], //注入
mixins: [mixins],
watch: {
defaultData(val) {
val.itemType = val.itemType - 0
try {
val.freeFreightChannel = val.freeFreightChannel ? val.freeFreightChannel.split(',') : []
val.excludeFreightChannel = val.excludeFreightChannel ? val.excludeFreightChannel.split(',') : []
val.freeFreightChannel = val.freeFreightChannel.map(item => Number(item))
val.excludeFreightChannel = val.excludeFreightChannel.map(item => Number(item))
} catch (error) {
val.freeFreightChannel = []
val.excludeFreightChannel = []
}
this.baseInfo = Object.assign({}, this.baseInfo, val)
this.commit('RELEASEPRODUCTBASEINFO', val)
}
},
created() {
if (this.addProduct.isEdit) {
this.disabledNum = true
}
this.type = this.$route.params.type
},
methods: {
addUserIntr() {
this.baseInfo.useIntrs.push({ value: '' })
},
addCompos() {
this.baseInfo.suitCompos.push({ value: '' })
},
// 刪除產品介紹
deleteUserIntr(i) {
this.$confirm(`確認要刪除嗎?`, '提示', {
confirmButtonText: '確認',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.baseInfo.useIntrs.splice(i, 1)
})
.catch(() => {})
},
// 刪除套裝組成
deleteCompos(i) {
this.$confirm(`確認要刪除嗎?`, '提示', {
confirmButtonText: '確認',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.baseInfo.suitCompos.splice(i, 1)
})
.catch(() => {})
},
// 是否有實物庫
checkedInvoiceHandle(boo) {
if (boo) {
this.productCodeLabel = '產品編號'
} else {
this.productCodeLabel = '產品虛擬編號'
}
},
// 校驗編號
validateProductCode(e) {
let val = e.replace(/\D/g, '')
this.baseInfo.code = val
this.$refs.code.currentValue = val
this.$refs.productInfoRule.validateField('code')
},
// 後台校驗編號的重復性
checkedCode() {
if (!this.baseInfo.code) {
return
}
releaseProduct.getUniqueCode({ code: this.baseInfo.code }).then(res => {
if (!res.data.data) {
this.$message({
message: '編號重複',
type: 'warning'
})
this.baseInfo.code = ''
}
})
},
// 提交數據進入store
submit() {
let obj = Object.assign({}, this.baseInfo)
for (let k in obj) {
if (obj[k] == null) {
obj[k] = ''
}
}
obj.freeFreightChannel = obj.freeFreightChannel.join(',')
obj.excludeFreightChannel = obj.excludeFreightChannel.join(',')
// 產品類型(1 普通產品 2 組合產品 3 智芯產品)
// 這里先寫死
if (!this.addProduct.isGroup) {
obj.type = '1'
obj.typeName = '普通產品'
}
// 我們不能把baseItem中的其他tab添加的字段刪除掉
let baseItem = this.$store.state.releaseProduct.productInfo.baseItem
if (this.addProduct.isEdit) {
let storeBaseItem = this.$store.state.editReleaseProduct.productInfo.baseItem
if (storeBaseItem.id) {
obj.id = storeBaseItem.id
}
// 因為產品價格tab雖然將價格置入store,但是並沒有更改基本信息中的retailPrice,所以需要在切換
// 為baseItem的時候重置此價格
// 未實現(解決此bug更好的的方式,價格在保存的時候從產品價格中去取,就不會出現值被覆蓋的情況)
if (storeBaseItem.retailPrice) {
obj.retailPrice = storeBaseItem.retailPrice
}
}
baseItem = Object.assign({}, baseItem, obj)
this.commit('RELEASEPRODUCTBASEINFO', baseItem)
}
}
}
</script>
效果展示:
3.0用法
xml
// 父组件
<template>
<base-info>
<template>
<script setup>
import baseInfo from '../components/baseInfo'
let num = ref(100)
provide('addProduct',num)
</script>
// 子组件
<template>
<div>
</div>
<template>
<script setup>
// 注入
const addProduct = inject('addProduct')
console.log(addProduct)
</script>
v-if 与 v-for 的优先级对比
2.x 版本中 v-for > v-if
3.x 版本中 v-if > v-for
v-for 中的 Ref 数组
xml
vue2.x 会自动把ref填充内容
vue3.x 需要手动添加
<ul>
<li v-for='item in 5' :key='item' :ref="setItemRef">
{{ item }}
</li>
</ul>
<script setup>
let itemRefs:[];
methods:{
setItemRef(el){
this.arr.push( el );
}
}
</script>
$children
perl
vue2.x : 访问当前实例的子组件
vue3.x : 在 3.x 中,$children 已被移除,且不再支持。
设置:<HelloWorld msg="Welcome" ref='hw'/>
访问:this.$refs.hw
关于生命周期的区别
setup 组合式API
注意:没有beforeCreate和created
其他生命周期要使用前面加"on" 例如:onMounted
参考链接:v3.cn.vuejs.org/guide/compo...
Teleport
基本用法:
xml
<teleport to="某元素选择器">
<!-- 子节点内容 -->
</teleport>
- 将组件html渲染到父组件外的指定元素
xml
html
<!-- 弹出框组件 -->
<teleport to="#modal">
<div>I'm a modal!</div>
</teleport>
<!-- 指定渲染到的节点 -->
<div id="modal"></div>
- 避免组件过深层级嵌套问题, 简单理解为可以将dom放在任意想要的位置
xml
html
<!-- Navbar.vue -->
<teleport to="#navbar">
<nav>...</nav>
</teleport>
<!-- App.vue -->
<div id="navbar"></div>
<router-view />
ref & reactive
官网解释:cn.vuejs.org/api/reactiv...
ref的使用场景
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
接受基本类型和引用类型,简单易懂,操作便捷
reactive的使用场景:
- 使用 Proxy 代理对象,拦截读取操作进行依赖收集。- 对对象属性递归调用 reactive() 方法,实现深层代理。- 设置属性时自动触发依赖,使其更新。
创建响应式数据对象:reactive可以用于将普通JavaScript对象转换为响应式数据对象,以便在模板中实现数据的双向绑定。
复杂数据结构:reactive适用于包含嵌套对象和数组的复杂数据结构,可以自动追踪其内部属性的变化。
typescript
<script setup>
import { userManager } from '@/store/userManager';
import { storeToRefs } from 'pinia';
const store = userManager()
console.log(store)
const { nickName, age, changeAge } = storeToRefs(store)
let msg = ref('这是ref响应数据');
let state = reactive({
user: {
name: 'lzx',
getName() {
return this.name
},
address: {
city: 'hangzhou',
getCity() {
return this.city
}
street: {
number: 123
}
}
}
})
// 直接改变深层属性
state.user.address.street.number = 456
// 使用computed观察深层属性变化
const number = computed(() => {
let number = state.user.address.street.number)
return number
})
onMounted(()=>{
console.log('onMounted');
})
</script>
toRefs
将响应式对象转换为普通对象,同时保持响应性
解构响应式对象时保持响应性
将响应式对象传递给函数式组件时保持响应性
xml
<script setup>
import { userManager } from '@/store/userManager';
import { storeToRefs } from 'pinia';
const store = userManager()
console.log(store)
const { nickName, age, changeAge } = storeToRefs(store)
let msg = ref('这是ref响应数据');
let state = reactive({
user: {
name: 'lzx',
age:19,
getName() {
return this.name
},
address: {
city: 'hangzhou',
getCity() {
return this.city
}
street: {
number: 123
}
}
}
})
const stateAsObject = toRefs(state)
// stateAsObject 是普通对象,但具有响应性
stateAsObject.age.value++
const { age } = toRefs(state)
age.value++ // 响应式
<MyComponent v-bind="toRefs(state)" /> //传递给组件时
</script>
响应式区别
markdown
vue2.x : Object.defineProperty()
vue3.x : Proxy
1. Object.defineProperty()存在的问题
1. 不能监听数组的变化
2. 必须遍历对象的每一个属性
2. Proxy 通过代理对象实现属性拦截。
状态管理 vuex & pinia
vuex官网:vuex.vuejs.org/zh/
vuex中定义store的目录划分
mutation-types.js
arduino
// 用戶管理
export const USERLIST = 'USERLIST' // 用戶管理列表
modules>userMages>index.js
ini
import userApi from '../../../api/userManageApi'
import * as types from '../../mutation-types'
const state = {
userList: {}, // 用户列表
}
const getters = {
userList: state => state.userList,
}
const actions = {
getUserList ({ commit }, params) {
userApi.userList(params).then(res => {
let rec = res.data.data
commit(types.USERLIST, {
rec
})
})
},
}
const mutations = {
[types.USERLIST] (state, { rec }) {
state.userList = rec
},
}
export default {
state,
getters,
actions,
mutations
}
store> index.js, 将所有模块集合
javascript
import Vue from 'vue'
import Vuex from 'vuex'
import userManager from './modules/userManager/index'
Vue.use(Vuex)
// const debug =process.env.NOOE_ENV !== 'production'
export default new Vuex.Store({
modules: {
userManager,
}
})
mian.js 引入
javascript
import 'es6-promise/auto'
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
new Vue({
el: '#app',
router,
// store挂载到vue 中
store,
components: {
App
},
template: '<App/>'
})
view文件中使用
xml
< template>
<div class="forms">
<template>
<el-tabs v-model="activeName" type="card" @tab-click="tabSearch" class="tabel-tags">
<el-tab-pane label="所有用戶" name="all"></el-tab-pane>
<el-tab-pane label="待審核" name="inProgress"></el-tab-pane>
<el-tab-pane label="已審核" name="forbidden"></el-tab-pane>
</el-tabs>
</template>
<el-table :data="userList.list" ref="userTabel" @selection-change="selectionChange" highlight-current-row border style="width: 100%">
<!-- <el-table-column type="selection"></el-table-column> -->
<el-table-column type="index" :index="indexMethod" width=50 align="center" label="序號">
</el-table-column>
<el-table-column prop="userName" align="center" label="會員編號">
</el-table-column>
<el-table-column align="center" prop="realName" label="稱謂">
</el-table-column>
<el-table-column prop="typeName" align="center" label="使用者類型">
</el-table-column>
<el-table-column align="center" label="聯繫手機">
<template slot-scope="scope">
{{scope.row.mobile || '-'}}
</template>
</el-table-column>
<el-table-column align="center" label="綁定手機">
<template slot-scope="scope">
{{scope.row.mobile || '-'}}
</template>
</el-table-column>
<el-table-column align="center" label="綁定電子信箱">
<template slot-scope="scope">
{{scope.row.email || '-'}}
</template>
</el-table-column>
<!-- <el-table-column align="center" label="ageloc me 導入帳號">
<template slot-scope="scope">
{{ '-'}}
</template>
</el-table-column> -->
<el-table-column align="center" label="註冊時間">
<template slot-scope="scope">
<div>
{{scope.row.registerTime}}
</div>
</template>
</el-table-column>
<el-table-column align="center" label="保薦人訊息">
<template slot-scope="scope">
<div>{{scope.row.refereeName || '-'}}</div>
<div>{{scope.row.refereeCode || '-'}}</div>
</template>
</el-table-column>
<el-table-column align="center" label="活動狀態">
<template slot-scope="scope">
{{statusArr[scope.row.status]}}
</template>
</el-table-column>
<el-table-column align="center" label="代購授權">
<template slot-scope="scope">
<span v-if="scope.row.remark === '0'">已授權</span>
<span v-else>未授權</span>
</template>
</el-table-column>
<el-table-column align="center" label="鎖定" width="100">
<template slot-scope="scope">
<span v-if="scope.row.lockStatus===1">未鎖定</span>
<span v-else>已鎖定</span>
<!-- <el-button @click="unLock(scope.row)" type="text" size="small" v-if="scope.row.lockStatus===1"><i class="iconfont icon-unlock"></i>未鎖定</el-button> -->
<!-- <el-button @click="unLock(scope.row)" type="text" size="small" v-else><i class="iconfont icon-lock"></i>已鎖定</el-button> -->
</template>
</el-table-column>
<el-table-column align="center" label="獎金賬號當前狀態">
<template slot-scope="scope">
<span v-if="scope.row.fundAccountAuditStatus == 0">待審核</span>
<span v-else-if="scope.row.fundAccountAuditStatus == 1">審核通過</span>
<span v-else-if="scope.row.fundAccountAuditStatus == 2">審核不通過</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button v-permission:check v-if="scope.row.fundAccountAuditStatus == 0" @click="getAuditInfo(scope.row)" type="text" size="small" icon="el-icon-view">審核</el-button>
<el-button v-permission:check @click="searchDetail(scope.row)" type="text" size="small" icon="el-icon-view">查看</el-button>
<el-button v-permission:check v-if="scope.row.fundAccountAuditStatus == 0 || scope.row.fundAccountAuditStatus == 1 || scope.row.fundAccountAuditStatus == 2" @click="goRecords(scope.row)" type="text" size="small" icon="el-icon-view">歷史記錄</el-button>
<el-button v-if="scope.row.shipment === true" @click="onRecover(scope.row)" type="text">連續出貨中斷恢復</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="userList.list && userList.list.length && userList.total > 10" @size-change="listSizeChange" @current-change="pager" :current-page="pageNum" :page-sizes="[10, 20, 30, 40,50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="userList.total">
</el-pagination>
</div>
{{name }}
<template>
<script>
import { mapGetters, mapState,mapActions} from 'vuex'
export default {
data () {
return {
activeName: 'all',
total: 0,
pageNum: 1,
pageSize: 10,
searchParams: {}
}
},
computed: {
...mapGetters(['userList'])
},
/* computed: mapGetters({
orders: 'orderManagerList'
}),
*/
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
}),
...mapState(['name'])
// name2 和 age2 都是别名
...mapState({ name2: 'name', age2: 'age'}])
...mapGetters('some/nested/module', [
'someGetter', // -> this.someGetter
'someOtherGetter', // -> this.someOtherGetter
])
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
vuex持久化存储
php
npm install --save vuex-persistedstate
store>index.js 文件配置
import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
modules: {
app,
order,
userManager,
},
plugins: [
// veux持久化配置
createPersistedState({
key: 'rabbitstore-client',
paths: ['userManager', 'order']
})
]
})
// 注意:
// ===> 默认是存储在localStorage中
// ===> key是存储数据的键名
// ===> paths是存储state中的数据,如果是模块下具体的数据需要加上模块名称,userManager.xxx
// ===> 修改state后触发才可以看到本地存储数据的的变化。
pinia状态管理
和vuex 相比较。
- Pinia 不需要像 Vuex 那样显式地提交 mutation,更新状态可以直接通过 actions 进行修改。
- Pinia 的 state 是一个函数
- Pinia 中的 state 是 reactive 的,不需要深层嵌套数据也可以自动响应。
- Pinia 提供的 actions、getters 更简洁,只需要添加在 store 实例上。
- Pinia 可以轻松复用 store,只需要导出一个 install 方法。
pinia模块划分及配置
和view 文件名呼应,采用命名空间的方式
store> index.js
引入pinia 及持久化缓存插件,统一抛出store供main.js 全局引用
javascript
import { createPinia } from "pinia";
import piniaPluginPersist from 'pinia-plugin-persist';
const store = createPinia();
store.use(piniaPluginPersist)
export default store
main.js
注入口配置
javascript
import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
createApp(App).use(store).use(router).mount('#app');
持久化缓存配置
默认为sessionStotage存储, 可配置单个属性的储存
javascript
import { defineStore } from 'pinia'
export const userManager = defineStore({
id: 'userId',
state: () => {
return {
nickName: 'lzx',
age: 18,
userData:{}
}
},
getters:{ // 计算属性,可缓存
changeAge() {
return this.age + 10;
}
},
actions: {
// 异步
// async fetchUser() {
// const response = await axios.get('/api/user')
// this.userData = response.data
// }
updateAge(val) { // pinia 中actions 可直接修改数据
this.age += val
}
},
// 默认储存为sessionStotage
persist: {
enabled:true,
strategies :[{
key: 'userId', // 修改存储的键名,默认为当前 Store 的 id
storage: window.localStorage, // 存储位置修改为 sessionStorage
paths:['age'] // 配置需要缓存的数据
}]
},
})
小结:
看看 Vuex 和 Pinia 的整体设计以及它们之间的区别是什么。
Vuex
下面是Vuex工作原理的官方图示。
在 Vuex store(仓库)中,有4个主要组件。
1、State
这只是一个包含实际状态的对象。可以在开发工具中看到这个状态,如果想保留这个状态用于缓存或其他目的,也可以保存这个对象。
2、Actions
Actions 是执行异步任务的函数。它们是由关键字dispatch发起的。
Actions 通常会请求一个外部 API 或做一些其他的异步工作。它还负责调用适当的 mutation 来实际改变状态。这说明 actions 本身并没有改变状态,而是 commit 变化,让 mutation 来改变状态。
3、Mutations
Mutation 是唯一会真正同步改变状态的函数。Mutations 使用关键字commit。
4、Getters
Getters可以被认为是计算过的属性,应该被用来从状态中获得一个修改过的响应。
eg:
php
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
使用 store
在处理上述问题时,一个组件通常会调用dispatch
来启动异步任务(比如从外部API中获取)。如果需要改变状态,比如一个简单的计数器,可以调用 commit
。
这意味着一个组件可以通过调用dispatch
或commit
来与 store 进行交互。这增加了一些复杂度。
在使用Vuex之前,对 "commit" 和 "dispatch" 这两个术语并不熟悉。由于这个原因,用它们来改变状态并不直观。对于一些人来说,这可能是不同的,但这使用 action 或 mutation 都有点不舒服。
另外值得注意的是,使用Vuex,一个组件可以访问整个 store,尽管在逻辑上将 Vuex store 分成不同的文件。
Pinia
与Vuex相比,Pinia的工作原理图如下:
整体架构比 Vuex 更简单,更容易理解。一个Pinia store 有3个主要组成部分:
1、State
与Vuex的定义一样。
2、Actions
这里的 Actions 与Vuex中的 Actions 和 mutations 的工作相同。这些函数是改变状态的唯一方式。如果想从外部API获取数据并更新状态,也可以使用 actions 。
与Vuex设置的另一个区别是,Pinia actions 是普通函数,心智负担比 vuex 小很多。
3、Getters
getter 完全等同于 Store 状态的计算属性
一个简单的Pinia store 的例子如下所示:
javascript
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
}
},
})
使用
如果有多个模板, Vuex 一般采用 modules 方式,这就需要在 store/index.ts中将所有的 modules通过 creaeStore 注册到 store 中,
那么Pinia 就省去了这些麻烦,createPinia()
即可,不需要注册 modules,
没有任何参数,所以连 store/index.ts都可以不用了,直接在main.ts 中添加即可,
采用命名空间的方式,一般对应view模块名 ,这一点会比Vuex简洁很多
javascript
import { createPinia } from 'pinia'
app.use(createPinia());
# main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
就目前而言,Pinia更容易理解和使用,Pinia还支持Vue 2和3的开箱即用,这使得迁移变得更加容易。
总结
Pinia 优势:
Vue2 和 Vue3 都支持
更小,只有1KB
不需要嵌套模块,符合Vue3的Composition api,让代码更加扁平化
抛弃了Mutations的操作,只有state、getters和actions.极大简化了状态管理库的使用完整的TypeScript支持
代码更加简洁,可以实现很好的代码自动分割
Pinia还有很多的用户和细节,请前往官网官方文档Home | Pinia (vuejs.org)
可以自行搭建或参考下面推荐的的开源项目,有助你更好的理解
Soybean Admin 是一个基于 Vue3 / Vite3 / TypeScript / NaiveUI / Pinia 和 UnoCSS 的中后台模版,它使用了最新流行的前端技术栈,内置丰富的主题配置,有着极高的代码规范 ,基于文件的路由系统以及基于 Mock 的动态权限路由,开箱即用的中后台前端解决方案,也可用于学习参考。
Soybean Admin 的技术特性
- 使用最新流行的前端技术栈 :使用 Vue3 / Vite 等前端前沿技术开发, 使用高效率的 npm 包管理器 pnpm
- 支持 TypeScript
- 丰富可配置的主题、暗黑模式,基于原子 css 框架 -- UnoCss 的动态主题颜色
- 优良的代码规范:丰富的规范插件及极高要求的代码规范,学习价值也很大
- 文件路由系统:基于文件的路由系统,根据页面文件自动生成路由声明、路由导入和路由模块
- 权限路由 :提供前端静态和后端动态两种路由模式,基于 mock 的动态路由能快速实现后端动态路由
- 网络请求函数 :基于 axios 的完善的请求函数封装,提供 Promise 和 hooks 两种请求函数,加入请求结果数据转换的适配器
Github: github.com/honghuangdc...
体验地址:admin.soybeanjs.cn/#/dashboard...
vue-pure-admin
vue-pure-admin 是一款开源免费且开箱即用的中后台管理系统模版(兼容移动端)。使用 Vue3 + Vite + Element Plus、TypeScript + Pinia + Tailwindcss 等主流技术开发。
体验地址:yiming_chang.gitee.io/vue-pure-ad...
Github :github.com/pure-admin/...
更多案例请前往:www.yuque.com/donsoft/qkn...