VUEX

vue 里可以使用 vuex 管理应用的数据,使用这个包提供的功能可以创建一个 store,这个 store 你可以想象成是应用的一个数据仓库,你在里面可以定义应用的数据还有方法等

安装 vuex
package.json 里添加 “vuex”: “^4.0.0-0” ,然后 npm install 一下

创建与使用 store
新建 app.store.ts 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { createStore } from 'vuex';
/**
* 创建store
*/
const store = createStore({
state:{
appName:'cxn'
}
});

/**
* 默认导出
*/
export default store;

main.ts 中导入

1
2
3
4
5
import appStore from './app/app.store';
/**
* 应用store
*/
app.use(appStore);

定义使用 store 模块
使用 store 的时候可以定义 store 模块,比如在内容里定义内容的 store,在 user 里定义 user 的 store。
app 下新建 post/post.store.ts

1
2
3
4
5
6
7
8
9
import { postCreateStoreModule } from './create/post-create.store';

export const postStoreModule = {
namespaced: true, //使用命名空间

modules: {
create: postCreateStoreModule, //设置其他的store模块
},
};

在 post 目录下新建 create/post-create.store.ts:

1
2
3
4
5
6
7
export const postCreateStoreModule = {
namespaced: true, //使用命名空间

state: {
loading: false,
},
};

打开 app.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createStore } from 'vuex';
import { postStoreModule } from './post/post.store';
/**
* 创建store
*/
const store = createStore({
state: {
appName: 'cxn',
},
modules: {
post: postStoreModule,
},
});

/**
* 默认导出
*/
export default store;

然后在 app.vue 中输出看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<h3>{{ name }}</h3>
</template>

<script>
export default {
data() {
return {
name: 'nihao~',
};
},
created() {
console.log(this.$store.state);
},
};
</script>

<style>
@import './styles/app.css';
</style>

解决 store 模块的类型问题
app.store.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { createStore } from 'vuex';
import { postStoreModule, PostStoreState } from './post/post.store';

export interface RootState {
appName: string;
post: PostStoreState;
}
/**
* 创建store
*/
const store = createStore({
state: {
appName: 'cxn',
} as RootState,
modules: {
post: postStoreModule,
},
});

/**
* 默认导出
*/
export default store;

post.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { RootState } from '../app.store';
import { Module } from 'vuex';
import {
postCreateStoreModule,
PostCreateStoreState,
} from './create/post-create.store';

export interface PostStoreState {
create: PostCreateStoreState;
}

export const postStoreModule: Module<PostStoreState, RootState> = {
namespaced: true, //使用命名空间

modules: {
create: postCreateStoreModule, //设置其他的store模块
},
};

post/create/post-create.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { RootState } from '@/app/app.store';
import { Module } from 'vuex';
export interface PostCreateStoreState {
loading: boolean;
}

export const postCreateStoreModule: Module<PostCreateStoreState, RootState> = {
namespaced: true, //使用命名空间

state: {
loading: false,
} as PostCreateStoreState,

getters: {
loading(state) {
return state.loading;
},
},
};

应用的路由

安装:在 package.json 里添加 “vue-router”: “^4.0.0-0”,然后 npm install

创建与使用路由
app/app.router.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { createRouter, createWebHistory } from 'vue-router';
import appRoutes from './app.routes';
/**
* 创建路由器
*/
const router = createRouter({
history: createWebHistory(),
routes: [...appRoutes],
});

/**
* 默认导出
*/
export default router;

新建 app.routes.ts 在这里可以定义应用整体相关的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { RouteRecordRaw } from 'vue-router';
import AppHome from './components/app-home.vue';

/**
* 定义路由
*/
const routes: Array<RouteRecordRaw> = [
{
name: 'home',
path: '/',
component: AppHome,
},
];

/**
* 默认导出
*/
export default routes;

新建组件 AppHome:

1
2
3
<template>
<div>你好啊啊啊</div>
</template>

定义内容模块的路由:
post/post.routes.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { RouteRecordRaw } from 'vue-router';
import PostIndex from './index/post-index.vue';

/**
* 定义路由
*/
const routes: Array<RouteRecordRaw> = [
{
name: 'postIndex',
path: '/posts',
component: PostIndex,
},
];

/**
* 默认导出
*/
export default routes;

新建组件 post/index/PostIndex:

1
2
3
<template>
<div>PostIndex</div>
</template>

 在 app.router.ts 导入;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createRouter, createWebHistory } from 'vue-router';
import appRoutes from './app.routes';
import postRoutes from '@/app/post/post.routes';
/**
* 创建路由器
*/
const router = createRouter({
history: createWebHistory(),
routes: [...appRoutes, ...postRoutes],
});

/**
* 默认导出
*/
export default router;

axios 请求服务端接口

安装:package.json 中添加 vue “axios”: “0.20.0” ,然后 npm install

创建 app/app.service.ts:

1
2
3
4
5
6
7
//在这里可以定义导出一些应用需要的方法

import axios from 'axios';

export const apiHttpClient = axios.create({
baseURL: 'http://localhost:3000',
});

使用的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>PostIndex</div>
</template>

<script>
import { apiHttpClient } from '@/app/app.service';
export default {
async created() {
const response = await apiHttpClient.get('/posts');

console.log(response);
},
};
</script>

vue 应用的环境变量配置
有时候你可能希望在开发环境和生产环境上使用不同的配置,比如 axios 里设置了

1
2
3
export const apiHttpClient = axios.create({
baseURL: 'http://localhost:3000',
});

这个地址在不同环境下可能是不一样的,我们可以在项目里给不同的环境创建各自的环境变量文件

创建开发环境的配置文件 .env.development:

1
VUE_APP_API_BASE_URL = http://localhost:3000

创建生成环境的配置文件 .env.production:

1
VUE_APP_API_BASE_URL = https://ravent-node-api.ninghao.net

创建项目配置文件 app/app.config.ts:

1
2
3
4
/**
* 服务接口基本地址
*/
export const API_BASE_URL = process.env.VUE_APP_API_BASE_URL;

在 app.service.ts 中使用:

1
2
3
4
5
6
7
8
//在这里可以定义导出一些应用需要的方法

import axios from 'axios';
import { API_BASE_URL } from './app.config';

export const apiHttpClient = axios.create({
baseURL: API_BASE_URL,
});

在 vue 应用里定义与使用组件

如果在项目里使用了 ts,可以使用 vue 提供的 defineComponent 去返回这个组件对象。

新建组件 post/index/components/post-list-item.vue:

1
2
3
4
5
6
7
8
<template>
<div>PostListItem</div>
</template>

<script>
import { defineComponent } from 'vue';
export default defineComponent({});
</script>

新建组件 post/index/components/post-list.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<PostListItem />
</template>

<script>
import { defineComponent } from 'vue';
import PostListItem from './post-list-item';
export default defineComponent({
components: {
PostListItem,
},
});
</script>

在 post/post-index.vue 中导入

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<PostList />
</template>

<script>
import { defineComponent } from 'vue';
import PostList from './components/post-list';
export default defineComponent({
components: {
PostList,
},
});
</script>

在 Vue 组件内部请求服务接口获取组件需要的数据
post/index/components/post-list.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<PostListItem v-for="post in posts" :item="post" :key="post.id" /> //使用
</template>

<script>
import { defineComponent } from 'vue';
import PostListItem from './post-list-item';
import { apiHttpClient } from '@/app/app.service'; //引入axios
export default defineComponent({
data() {
return {
posts: [],
};
},
async created() {
const response = await apiHttpClient.get('/posts'); //获取数据
this.posts = response.data;
},
components: {
PostListItem,
},
});
</script>

post/index/components/post-list-item.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h3>
{{ item.title }} - <small>{{ item.user.name }}</small>
</h3>
</template>

<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
item: Object,
},
});
</script>

在 Vue 组件里使用 Store 里的动作与数据
post-index.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { PostItem } from '../post.store';
import { Module } from 'vuex';
import { RootState } from '@/app/app.store';
import { apiHttpClient } from '@/app/app.service';

export interface PostIndexStoreState {
loading: boolean;
posts: Array<PostItem>;
}

export const PostIndexStoreModule: Module<PostIndexStoreState, RootState> = {
namespaced: true,

state: {
loading: false,
posts: [],
} as PostIndexStoreState,

getters: {
loading(state) {
return state.loading;
},
posts(state) {
return state.posts;
},
},

mutations: {
setLoading(state, data) {
state.loading = data;
},
setPosts(state, data) {
state.posts = data;
},
},

actions: {
async getPosts({ commit }) {
commit('setLoading', true);

try {
const response = await apiHttpClient.get('/posts');
commit('setPosts', response.data);
commit('setLoading', false);
return false;
} catch (error) {
commit('setloading', false);
throw error.response;
}
},
},
};

post.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { RootState } from '../app.store';
import { Module } from 'vuex';
import {
postCreateStoreModule,
PostCreateStoreState,
} from './create/post-create.store';
import { PostIndexStoreModule } from './index/post-index.store';

export interface PostItem {
id: number;
title: string;
content: string;
}

export interface PostStoreState {
create: PostCreateStoreState;
}

export const postStoreModule: Module<PostStoreState, RootState> = {
namespaced: true, //使用命名空间

modules: {
create: postCreateStoreModule, //设置其他的store模块
index: PostIndexStoreModule,
},
};

post-list.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div v-if="loading">加载中...</div>
<PostListItem v-for="post in posts" :item="post" :key="post.id" />
</template>

<script>
import { defineComponent } from 'vue';
import { mapGetters, mapActions } from 'vuex';
import PostListItem from './post-list-item';
export default defineComponent({
async created() {
this.getPosts();
},
computed: {
...mapGetters({
loading: 'post/index/loading',
posts: 'post/index/posts',
}),
},
components: {
PostListItem,
},
methods: {
...mapActions({
getPosts: 'post/index/getPosts',
}),
},
});
</script>

内容页面:定义 Store 模块
新建 post/show/post.show.store.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { apiHttpClient } from '@/app/app.service';
import { RootState } from '@/app/app.store';
import { Module } from 'vuex';

export interface Post {
id: number;
title: string;
content: string;
}

export interface PostShowStoreState {
loading: boolean;
post: Post;
}

export const PostShowStoreModule: Module<PostShowStoreState, RootState> = {
namespaced: true,

state: {
loading: false,
post: {},
} as PostShowStoreState,

getters: {
loading(state) {
return state.loading;
},
post(state) {
return Object.keys(state.post).length ? state.post : null;
},
},

mutations: {
setLoading(state, data) {
state.loading = data;
},
setPost(state, data) {
state.post = data;
},
},

actions: {
async getPostById({ commit }, postId) {
commit('setLoading', true);

try {
const response = await apiHttpClient.get(`/posts/${postId}`);
commit('setLoading', false);
commit('setPost', response.data);
return response;
} catch (error) {
commit('setLoading', false);
throw error.response;
}
},
},
};

post.store.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { RootState } from '../app.store';
import { Module } from 'vuex';
import {
postCreateStoreModule,
PostCreateStoreState,
} from './create/post-create.store';
import { PostIndexStoreModule } from './index/post-index.store';
import { PostShowStoreModule } from './show/post.show.store';

export interface PostItem {
id: number;
title: string;
content: string;
}

export interface PostStoreState {
create: PostCreateStoreState;
}

export const postStoreModule: Module<PostStoreState, RootState> = {
namespaced: true, //使用命名空间

modules: {
create: postCreateStoreModule, //设置其他的store模块
index: PostIndexStoreModule,
show: PostShowStoreModule,
},
};

内容页面:定义路由与组件
新建 post-show.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div v-if="showPost">
<h1>{{ post.title }}</h1>
<div>
{{ post.content }} - <small>{{ post.user.name }}</small>
</div>
</div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import { defineComponent } from 'vue';

export default defineComponent({
props: {
postId: String,
},
created() {
this.getPostById(this.postId);
},
computed: {
...mapGetters({
loading: 'post/show/loading',
post: 'post/show/post',
}),

showPost() {
return !this.loading && this.post;
},
},

methods: {
...mapActions({
getPostById: 'post/show/getPostById',
}),
},
});
</script>

post.routes.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { RouteRecordRaw } from 'vue-router';
import PostIndex from './index/post-index.vue';
import PostShow from './show/post-show.vue';

/**
* 定义路由
*/
const routes: Array<RouteRecordRaw> = [
{
name: 'postIndex',
path: '/posts',
component: PostIndex,
},
{
name: 'postShow',
path: '/posts/:postId',
component: PostShow,
props: true,
},
];

/**
* 默认导出
*/
export default routes;

post-list-item.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h3>
<router-link :to="{ name: 'postShow', params: { postId: item.id } }">
{{ item.title }}
</router-link>
-
<small>{{ item.user.name }}</small>
</h3>
</template>

<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
item: Object,
},
});
</script>

Mixin 制作一个设置页面标题的混合

在环境变量文件里新增项目名称 .env.development , .env.production

1
VUE_APP_APP_NAME = cxn

app.config.ts 使用一下

1
2
3
4
5
6
7
8
9
/**
* 应用名字
*/
export const APP_NAME = process.env.VUE_APP_APP_NAME;

/**
* 服务接口基本地址
*/
export const API_BASE_URL = process.env.VUE_APP_API_BASE_URL;

新建 app/app.mixin.ts 定义 mixin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { ComponentOptions, ComponentPublicInstance } from 'vue';
import { APP_NAME } from './app.config';
/**
* 设置页面标题
*/
const setTitle = (vm: ComponentPublicInstance) => {
//获取组件的title选项
const { title } = vm.$options;

if (title) {
let titleContent;

if (typeof title === 'function') {
//执行title
titleContent = title.call(vm);
} else {
titleContent = title;
}

document.title = `${titleContent} - ${APP_NAME}`;
}
if (vm.$route.path === '/') {
document.title = APP_NAME;
}
};

/**
* 标题混合
*/

export const titleMixin: ComponentOptions = {
created() {
setTitle(this);
},
updated() {
setTitle(this);
},
};

在入口文件 main.ts 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { createApp } from 'vue';
import App from './app/app.vue';
import appStore from './app/app.store';
import appRouter from './app/app.router';
import { titleMixin } from './app/app.mixin';

/**
* 创建应用
*/
const app = createApp(App);

/**
* 应用store
*/
app.use(appStore);

/**
* 应用路由器
*/
app.use(appRouter);

/**
* 标题混合
*/
app.mixin(titleMixin);

/**
* 挂载应用
*/
app.mount('#app');

最后在组件里定义页面的标题就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<PostList />
</template>

<script>
import { defineComponent } from 'vue';
import PostList from './components/post-list';
export default defineComponent({
title() {
return '内容列表'; //使用title方法或者属性定义此页面标题
},
components: {
PostList,
},
});
</script>