基于Vue官方提供的一个脚手架Vue-cli,生成一个工程化的 Vue项目模板,学习Vue工程化、规范化的开发模式
1.项目结构
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
| ├── build ----------------------------------------------- webpack相关配置文件 │ ├── build.js ---------------- webpack打包配置文件 │ ├── check-versions.js ------- 检查npm,nodejs版本 │ ├── dev-client.js ----------- 设置环境 │ ├── dev-server.js ----------- 创建express服务器,配置中间件,启动可热重载的服务器 │ ├── utils.js ---------------- 配置资源路径,配置css加载器 │ ├── vue-loader.conf.js ------ 配置css加载器等 │ ├── webpack.base.conf.js ---- webpack基本配置 │ ├── webpack.dev.conf.js ----- 用于开发的webpack设置 │ ├── webpack.prod.conf.js ---- 用于打包的webpack设置 ├── config ----------------------------------------------- 配置文件 ├── node_modules ----------------------------------------- 存放依赖的目录 ├── src -------------------------------------------------- 源码 │ ├── api --------------------------------- 自己封装的api接口 │ ├── assets ------------------------------ 静态文件 │ ├── components -------------------------- 组件 │ ├── main.js ----------------------------- 主js │ ├── App.vue ----------------------------- 项目入口组件 │ ├── router ------------------------------ 路由 │ ├── store ------------------------------- Vuex插件:仓库模块 │ ├── utils ------------------------------- 自己封装的工具方法模块 ├── package.json ----------------------------------------- node配置文件 ├── .babelrc---------------------------------------------- babel配置文件 ├── .editorconfig----------------------------------------- 编辑器配置 ├── .gitignore-------------------------------------------- 配置git可忽略的文件
|
2.主要文件
2.1 index.html 主页
index.html如其他html一样,但一般只定义一个空的根节点,在main.js里面定义的实例将挂载在根节点下,内容都通过vue组件来填充
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vuedemo</title> </head> <body> <div id="app"></div> </body> </html>
|
2.2 main.js 入口文件
main.js主要是引入vue框架,根组件及路由设置,并且定义vue实例,下面的 components:{App}就是引入的根组件App.vue
后期还可以引入插件,当然首先得安装插件。
Vue2写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Vue from 'vue'
import App from './App'
import router from './router'
new Vue({ el:'#app', router, render: h => h(App) }).$mount('#app')
|
Vue3写法:
1 2 3 4 5 6 7 8 9 10 11
| import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App) app.use(router) app.mount('#app')
|
2.3 App.vue 根组件
在组件化的开发模式中,App.vue作为根组件,可以在这里写入其他组件
App.vue是整个应用程序的入口点,它负责管理路由、状态和其他全局资源
三部分组成:
- template: 结构 (有且只能一个根元素)
- script: js逻辑
- style: 样式(可支持less,需要装包)
2.4 package.json 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { "name": "demo", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build" }, "dependencies": { "core-js": "^3.8.3", "register-service-worker": "^1.7.2", "vue": "^3.2.13", "vue-router": "^4.0.3" }, "devDependencies": { "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-pwa": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0", "@vue/cli-service": "~5.0.0" } }
|
3.router模块
router使用方法:
yarn add vue-router@3
或
yarn add vue-router@4
- 下载安装vue-router,vue2用vue-router3.x,vue3用vue-router4.x
- 引入vue-router
- vue-router注册
- 创建router对象
- 注入router对象
- views目录中存放页面组件
- index.js中管理路由路径
- App.vue中使用
<router-view></router-view>
Vue2
index.js
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 Vue from "vue"
import VueRouter from 'vue-router'
import ViewOne from "@/views/ViewOne.vue" import ViewTwo from "@/views/ViewTwo.vue" import ViewThree from "@/views/ViewThree.vue"
Vue.use(VueRouter)
const router = new VueRouter({ routes:[ {path:'/',redirect:'/one'}, {path:'/one',component:ViewOne}, {path:'/two',component:ViewTwo}, {path:'/three/:id',component:ViewThree}, {path:'*',component:NotFind} ] });
export default router
|
mian.js
1 2 3 4 5 6 7 8 9 10 11 12
| import Vue from 'vue' import App from './App.vue'
import router from "@/router"
Vue.config.productionTip = false
new Vue({ render: h => h(App), router }).$mount('#app')
|
Vue3
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { createRouter, createWebHistory } from 'vue-router'
import ViewOne from "@/views/ViewOne.vue" import ViewTwo from "@/views/ViewTwo.vue" import ViewThree from "@/views/ViewThree.vue"
const routes = [ {path:'/',redirect:'/one'}, {path:'/one',component:ViewOne}, {path:'/two',component:ViewTwo}, {path:'/three',component:ViewThree}, {path:'*',component:NotFind} ]
const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes })
export default router
|
main.js
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import App from './App.vue'
import router from "@/router"
const app = createApp(App);
app.use(router); app.mount('#app');
|
App.vue
1 2 3 4 5 6 7 8
| <template> <div id="app"> <router-link to="/one">1 </router-link> <router-link to="/two"> 2 </router-link> <router-link to="/three"> 3</router-link> <router-view></router-view> </div> </template>
|
路由导航守卫
将下面内容写入index.js 。这里是Vue2的写法,具体参考官网:导航守卫 | Vue Router (vuejs.org)
1 2 3 4 5 6 7 8 9 10 11 12 13
| const authorUrls = ['/two','/three']
router.beforeEach((to, from, next) => { if (authorUrls.includes(to.path) && !localStorage.getItem('token')) { alert('需要登录才能访问') next({ path: '/login' }) } else { next() } })
|
Vue3的写法有所不同:导航守卫 | Vue Router (vuejs.org)
4.store模块(Vuex)
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({ state: { count: 114514, title: 'abcd' } })
export default store
|
main.js
1 2 3 4 5 6 7 8 9 10
| import Vue from 'vue' import App from './App.vue'
import store from '@/store'
new Vue({ store, render: h => h(App) }).$mount('#app')
|
数据引用-state
①通过store直接访问
1 2 3 4 5 6 7
| <template> <div> {{ $store.state.count }} - {{ $store.state.title }} </div> </template>
|
②通过辅助函数mapState(简化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div> {{ count }} - {{ title }} </div> </template>
<script> import { mapState } from 'vuex' export default { computed: { ...mapState(['count', 'title']) } } </script>
|
修改数据-mutations
Vuex同样遵循单向数据流,组件中不能直接修改仓库中的数据
this.$store.state.count++
像这样的写法虽然能运行,但不符合规范,在组件中如果需要修改仓库中的数据,应该通过mutations来实现
使用步骤如下:
①mutations中定义方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const store = new Vuex.Store({ state: { count: 1, title: 'abcd' }, mutations: { addCount (state, n) { state.count += n }, updateTitle (state, newTitle) { state.title = newTitle } } })
|
②通过this.$store.commit( ‘方法名’,参数 )调用
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
| <template> <div> {{ $store.state.count }} - {{ $store.state.title }} <button @click="handleAdd(1)">Count+1</button> <button @click="handleAdd(2)">Count+2</button> <button @click="handleAdd(3)">Count+3</button> <input v-model="newTitle"> <button @click="handleTitle(newTitle)">updateTitle</button> </div> </template>
<script>
export default { data () { return { newTitle: '' } }, methods: { //调用mutations中的方法 handleAdd (n) { this.$store.commit('addCount', n) }, handleTitle (newTitle) { this.$store.commit('updateTitle', newTitle) } } } </script>
|
简化②-辅助函数mapMutations
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
| <template> <div> {{ $store.state.count }} - {{ $store.state.title }} <button @click="addCount(1)">Count+1</button> <button @click="addCount(2)">Count+2</button> <button @click="addCount(3)">Count+3</button> <input v-model="newTitle"> <button @click="updateTitle(newTitle)">updateTitle</button> </div> </template>
<script> //引入mapMutations import { mapMutations } from 'vuex'
export default { data () { return { newTitle: '' } }, //简化后只需要通过mapMutations映射方法进来,即可直接使用 methods: { ...mapMutations(['addCount', 'updateTitle']) } } </script>
|
异步修改数据-actions
①在actions中定义异步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const store = new Vuex.Store({ state: { count: 1 }, mutations: { addCount (state, n) { state.count += n } }, actions: { addCountAsync (content, n) { setTimeout(() => { content.commit('addCount', n) }, 5000) } } })
|
②通过this.$store.dispatch( ‘方法名’,参数 )调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> {{ $store.state.count }} <button @click="HandleAddCountAsync(2)">Count+2</button> </div> </template>
<script> export default { methods: { HandleAddCountAsync (n) { this.$store.dispatch('addCountAsync', n) } } } </script>
|
简化②-辅助函数mapActions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> {{ $store.state.count }} <button @click="addCountAsync(2)">Count+2</button> </div> </template>
<script> import { mapMutations, mapActions } from 'vuex'
export default { //类似mutations methods: { ...mapMutations(['addCount']), ...mapActions(['addCountAsync']) } } </script>
|
类似计算属性-getters
①在getters中定义计算属性
1 2 3 4 5 6 7 8 9 10 11 12
| const store = new Vuex.Store({ state: { list: [1, 2, 3, 4, 5, 6, 7, 8, 9] }, getters: { filterList (state) { return state.list.filter(item => item > 5) } } })
|
②通过调用
1 2 3 4 5 6
| <template> <div> {{ $store.state.list }} {{ $store.getters.filterList }} </div> </template>
|
简化②-辅助函数mapGetters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> {{ $store.state.list }} {{ $store.getters.filterList }} </div> </template>
<script> import { mapGetters } from 'vuex'
export default { methods: { ...mapGetters(['filterList']) } } </script>
|
分模块-modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
在模块中使用:namespaced: true, 命名空间,添加之后,当前模块下的标识符可以和其它模块相同,用于解决不同模块的命名冲突问题
划分模块后的目录结构:
划分模块步骤:
①建立模块
setting.js - setting模块
1 2 3 4 5 6 7 8 9 10 11
| export default { namespaced: true, state: { theme: 'light', desc: '描述信息' }, mutations: {}, actions: {}, getters: {} }
|
user.js - user模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default { namespaced: true, state: { userInfo: { name: 'zgl', age: 18 }, score: 100 }, mutations: {}, actions: {}, getters: {} }
|
②index.js导入并挂载模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Vue from 'vue' import Vuex from 'vuex'
import user from '@/store/modules/user' import setting from '@/store/modules/setting'
Vue.use(Vuex)
const store = new Vuex.Store({ state: {}, modules: { user, setting } }) export default store
|
挂载完成后即可正常使用模块中的数据
5.utils工具模块
utils工具模块是自己封装的一些工具方法模块
下面以一个名为request、封装了axios实例的模块为例:
①创建utils包,创建request.js
②在axios官网查看创建实例以及配置其他内容的方式
③封装axios,导出request实例
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
| import axios from 'axios'
const instance = axios.create({ baseURL: 'https://api.vvhan.com/api', timeout: 5000, headers: { 'Content-type': 'text/json' } })
instance.interceptors.request.use(function (config) { return config }, function (error) { return Promise.reject(error) })
instance.interceptors.response.use(function (response) { return response.data }, function (error) { return Promise.reject(error) })
export default instance
|
④使用request
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
| <template> <div> {{ someWord }} <button @click="getSomething">获取</button> </div> </template>
<script> // 使用原生的axios // import axios from 'axios'
// 使用封装的工具-request import request from '@/utils/request'
export default { data () { return { someWord: '默认' } }, methods: { async getSomething () { // 原写法: // await axios({ // method: 'get', // url: 'https://api.vvhan.com/api/ian' // }).then((response) => { // this.someWord = response.data // }) // 现在的写法: const res = await request.get('/ian') this.someWord = res } } } </script>
|
6.api接口模块
将所有api封装为一个模块来进行管理,是一种规范化的开发方式。当项目越发复杂时,如果某个多次使用的api需要更改,那么通过一个单独的api模块来管理会减少许多不必要的修改
①创建api目录,不同功能的api放入不同的js文件
②封装api
1 2 3 4 5 6 7 8 9 10
| import request from '@/utils/request'
export function loginByPhone (phone, code) { return request.post('/login', { form: { phone: phone, code: code } }) }
|
③调用api
1 2 3 4 5 6 7 8 9 10 11 12
| <script> import { loginByPhone } from '@/api/login' export default { methods: { async login () { //调用 const res = await loginByPhone('15012345678', '1234') console.log(res) } } } </script>
|
7.打包
在package.js中有一条build命令,点击运行即可完成打包,或者通过命令行:
yarn build
打包完成后会生成一个dist目录,将该目录放到服务器中即可
默认情况下,需要放到服务器根目录打开,如果希望双击运行,需要配置publicPath 配成相对路径