Compare commits

..

2 Commits

Author SHA1 Message Date
bunny 39b773f409 📇 初始化路由页面 2025-06-19 20:38:45 +08:00
bunny b397e00b36 Vuex 状态映射与模块化最佳实践 2025-06-19 19:41:57 +08:00
13 changed files with 374 additions and 9 deletions

View File

@ -10,6 +10,7 @@
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vue-router": "3",
"vuex": "3"
},
"devDependencies": {

View File

@ -14,6 +14,9 @@ importers:
vue:
specifier: ^2.6.14
version: 2.7.16
vue-router:
specifier: '3'
version: 3.6.5(vue@2.7.16)
vuex:
specifier: '3'
version: 3.6.2(vue@2.7.16)
@ -3575,6 +3578,11 @@ packages:
vue:
optional: true
vue-router@3.6.5:
resolution: {integrity: sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==}
peerDependencies:
vue: ^2
vue-style-loader@4.1.3:
resolution: {integrity: sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==}
@ -7700,6 +7708,10 @@ snapshots:
'@vue/compiler-sfc': 3.5.16
vue: 2.7.16
vue-router@3.6.5(vue@2.7.16):
dependencies:
vue: 2.7.16
vue-style-loader@4.1.3:
dependencies:
hash-sum: 1.0.2

View File

@ -1,15 +1,35 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr"
crossorigin="anonymous"
/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<script
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js"
integrity="sha384-7qAoOXltbVP82dhxHAUje59V5r2YsVfBafyUDxEdApLPmcdhBPg1DKg1ERo0BZlK"
crossorigin="anonymous"
></script>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

View File

@ -5,7 +5,8 @@
<Demo2 />
<hr />
<Demo3 /> -->
<Demo4 />
<!-- <Demo4 /> -->
<Demo6 />
</div>
</template>
@ -13,11 +14,13 @@
// import Demo1 from "@/views/demo1/index.vue";
// import Demo2 from "@/views/demo2/index.vue";
// import Demo3 from "@/views/demo3/index.vue";
import Demo4 from "@/views/demo4/index.vue";
// import Demo4 from "@/views/demo4/index.vue";
// import Demo5 from "@/views/demo5/index.vue";
import Demo6 from "@/views/demo6/index.vue";
export default {
name: "App",
// components: { Demo1, Demo2, Demo3 },
components: { Demo4 },
components: { Demo6 },
};
</script>

View File

@ -1,10 +1,15 @@
import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
Vue.use(VueRouter);
new Vue({
render: (h) => h(App),
store,
router,
}).$mount("#app");

View File

@ -0,0 +1,16 @@
import VueRouter from "vue-router";
const router = new VueRouter({
routes: [
{
path: "/about",
component: () => import("@/views/demo6/components/About.vue"),
},
{
path: "/home",
component: () => import("@/views/demo6/components/Home.vue"),
},
],
});
export default router;

View File

@ -1,13 +1,15 @@
import Vue from "vue";
import Vuex from "vuex";
import count from "./modules/count";
import schoolInfo from "./modules/schoolInfo";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: { count },
modules: { count, schoolInfo },
});
export default store;

View File

@ -0,0 +1,16 @@
const schoolInfo = {
namespaced: true,
state: {
schoolName: "BunnySchool",
schoolAddress: "昆山市印象欧洲",
},
getters: {
getSchoolName(state) {
return state.schoolName + "---";
},
},
actions: {},
mutations: {},
};
export default schoolInfo;

View File

@ -0,0 +1,199 @@
# Vuex 状态映射与模块化最佳实践
## 一、状态访问方式对比
### 1. 直接访问方式
```javascript
// 原始访问方式(不推荐)
this.$store.state.schoolInfo.schoolName
this.$store.getters['schoolInfo/getSchoolName']
```
### 2. 映射辅助函数
```javascript
// 推荐使用mapState/mapGetters
import { mapState, mapGetters } from 'vuex'
computed: {
// 数组写法(同名映射)
...mapState('schoolInfo', ['schoolName']),
...mapGetters('schoolInfo', ['getSchoolName']),
// 对象写法(重命名)
...mapState('schoolInfo', {
mySchoolName: 'schoolName'
}),
...mapGetters('schoolInfo', {
formattedName: 'getSchoolName'
})
}
```
## 二、模块化配置详解
### 1. 模块定义规范
```javascript
const schoolInfo = {
namespaced: true, // 必须开启命名空间
state: () => ({ // 使用函数返回状态对象
schoolName: "BunnySchool",
schoolAddress: "昆山市印象欧洲"
}),
getters: {
getSchoolName(state) {
// 可组合其他getters
return `${state.schoolName}---${state.schoolAddress}`
}
},
mutations: {
// 使用常量类型
UPDATE_NAME(state, payload) {
state.schoolName = payload
}
},
actions: {
async fetchSchoolInfo({ commit }) {
const res = await api.getSchoolInfo()
commit('UPDATE_NAME', res.data.name)
}
}
}
```
### 2. 模块注册方式
```javascript
// store/index.js
import schoolInfo from './modules/schoolInfo'
export default new Vuex.Store({
modules: {
schoolInfo // 键名将作为命名空间前缀
}
})
```
## 三、最佳实践指南
### 1. 命名规范建议
| 类型 | 命名规则 | 示例 |
| -------- | ----------- | ----------------- |
| 模块文件 | kebab-case | `school-info.js` |
| state | camelCase | `studentCount` |
| mutation | 大写+下划线 | `UPDATE_INFO` |
| getter | 动词短语 | `getFilteredList` |
| action | 业务动作 | `fetchSchoolData` |
### 2. 组件中使用建议
```javascript
export default {
computed: {
// 优先使用映射辅助函数
...mapState('schoolInfo', ['schoolName']),
// 复杂计算使用本地计算属性
formattedAddress() {
return this.$store.state.schoolInfo.schoolAddress.replace('市', '')
}
},
methods: {
...mapMutations('schoolInfo', ['UPDATE_NAME']),
// 组合多个action
async refreshData() {
await this.$store.dispatch('schoolInfo/fetchSchoolInfo')
this.loadComplete()
}
}
}
```
### 3. 代码组织技巧
```
store/
├── index.js # 主入口
├── modules/
│ ├── school-info.js # 学校模块
│ └── user.js # 用户模块
└── types.js # mutation类型常量
```
## 四、高级应用场景
### 1. 动态模块注册
```javascript
// 按需加载模块
export default {
created() {
import('./store/modules/school-info').then(module => {
this.$store.registerModule('schoolInfo', module.default)
})
},
destroyed() {
this.$store.unregisterModule('schoolInfo')
}
}
```
### 2. 模块复用
```javascript
// 创建可复用模块工厂
function createSchoolModule(initialName) {
return {
namespaced: true,
state: () => ({
schoolName: initialName
}),
// ...其他配置
}
}
```
### 3. 插件开发
```javascript
// 状态持久化插件
const persistPlugin = store => {
// 初始化时读取
const savedState = localStorage.getItem('vuex_state')
if (savedState) {
store.replaceState(JSON.parse(savedState))
}
// 订阅mutation变化
store.subscribe((mutation, state) => {
localStorage.setItem('vuex_state', JSON.stringify(state))
})
}
```
## 五、常见问题解决方案
1. **模块热更新**
```javascript
if (module.hot) {
module.hot.accept(['./modules/school-info'], () => {
store.hotUpdate({
modules: {
schoolInfo: require('./modules/school-info').default
}
})
})
}
```
2. **循环依赖处理**
- 使用全局getter解决跨模块访问
- 通过rootState参数访问根状态
3. **TypeScript支持**
```typescript
// 定义模块类型
interface SchoolState {
schoolName: string
schoolAddress: string
}
const schoolInfo: Module<SchoolState, RootState> = {
namespaced: true,
state: () => ({ /* 初始状态 */ })
}
```

View File

@ -0,0 +1,30 @@
<template>
<div>
<h3>{{ $store.state.schoolInfo.schoolName }}</h3>
<h3>{{ schoolName }}</h3>
<h3>{{ $store.getters["schoolInfo/getSchoolName"] }}</h3>
<h3>{{ getSchoolName }}</h3>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "Demo-5",
data() {
return {};
},
computed: {
// mapStatemapGetters
// ...mapState("schoolInfo", { schoolName: "schoolName" }),
// mapStatemapGetters
...mapState("schoolInfo", ["schoolName"]),
...mapGetters("schoolInfo", ["getSchoolName"]),
},
mounted() {
const data = mapState("schoolInfo", { schoolName: "schoolName" });
console.log(data);
},
};
</script>

View File

@ -0,0 +1,11 @@
<template>
<div>
<h2>About内容</h2>
</div>
</template>
<script>
export default {
name: "AboutPage",
};
</script>

View File

@ -0,0 +1,11 @@
<template>
<div>
<h3>Home内容</h3>
</div>
</template>
<script>
export default {
name: "HomePage",
};
</script>

View File

@ -0,0 +1,39 @@
<template>
<div class="container mt-3">
<div class="row">
<div class="col-sm-2 col-xs-offset-2 text-center">
<!-- 路由跳转内容 -->
<div class="list-group">
<router-link
class="list-group-item"
active-class="active"
to="/about"
>
about
</router-link>
<router-link class="list-group-item" active-class="active" to="/home"
>home</router-link
>
</div>
</div>
<!-- 内容显示区域 -->
<div class="col-sm-6">
<div class="panel">
<div class="panel-body">
<router-view />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Demo-6",
data() {
return {};
},
};
</script>