2年vue项目实战经验汇总
前言
你将收获
vue框架使用注意事项和佳经验 vue项目配置经验总结 vue组件设计经验总结 vue项目架构与服务化探索
正文
数组常用方法的使用,比如遍历有forEach,map,filter,every, some,reduce,操作方法有splice,slice, join,push,shift, pop,sort等 基本数据结构,引用类型(对象,数组) 基本逻辑运算if else, switch,三目运算:?,for/while循环等 字符串常见api(如replace,slice, substr,indexOf) 基本正则使用 变量作用域,作用域链,变量提升,函数声明提升 对象基本用法,面向对象编程
基本盒模型(border/content/padding等) 4种常用定位(static/absolute/relative/fixed) 常用布局方式(浮动布局/弹性布局flex/自适应布局/网格布局grid) css3基本样式与动画(transition,animation)
新标签基本用法和使用 head标签作用与用法(主要是meta属性的用法)
1. vue框架使用注意事项和佳经验
vue学习快的方式就是实践,根据官网多写几个例子是掌握vue快的方式。 接下来笔者就来总结一下在开发vue项目中的一些实践经验。
1.1 vue生命周期以及不同生命周期下的应用
beforeCreate:在beforeCreate生命周期执行时,data和methods中的数据还未初始化,所以此时不能使用data中的数据和methods中的方法 create:data 和 methods初始化完毕,此时可以使用methods 中的方法和data 中的数据 beforeMount:template模版已经编译好,但还未挂载到页面,此时页面还是上一个状态 mounted:此时Vue实例初始化完成了,DOM挂载完毕,可以直接操作dom或者使用第三方dom库 beforeUpdate: 此时data已更新,但还未同步页面 updated:data和页面都已经更新完成 beforeDestory:Vue实例进入销毁阶段,但所有的 data 和 methods , 指令, 过滤器等都处于可用状态 destroyed: 此时组件已经被销毁,data,methods等都不可用
1.2 vue常用的指令以及动态指令的使用
v-bind 用于响应式地更新 HTML属性 v-if 根据表达式的值的真假来决定是否插入/移除元素 v-on 用于监听 DOM 事件 v-show 用于决定是否展示该元素,底层通过display:none实现 v-html 在dom内插入html内容 v-for 循环 v-text 渲染指定dom的内容文本 v-cloak 和CSS规则如 [v-cloak] { display: none } 一起用,可以隐藏未编译的 Mustache 标签直到实例准备完毕
<a v-on:[eventName]="doSomething"> ... </a>
复制代码
1.3 vue常用修饰符及作用
事件修饰符
.stop 阻止事件冒泡 .prevent 阻止事件默认行为 .self 事件绑定的元素本身触发时才触发回调 .once 事件只能触发一次,第二次就不会触发了 .native 将一个vue组件变成一个普通的html,使其可以监听click等原生事件 具体使用如下:
<Tag @click.native="handleClick">ok</Tag>
复制代码
表单修饰符
.lazy 在输入框输入完内容,光标离开时才更新视图 .trim 过滤首尾空格 .number 如果先输入数字,那它就会限制你输入的只能是数字;如果先输入字符串,那就相当于没有加.number
<input type="text" v-model.trim="value">
复制代码
1.4 组件之间,父子组件之间的通信方案
通过事件总线(bus),即通过发布订阅的方式 vuex
父组件通过prop向自组件传递数据 子组件绑定自定义事件,通过this.$emit(event,params) 来调用自定义事件 使用vue提供的 children & $refs方法来通信
1.5 vue实现按需加载组件
使用() => import(), 具体代码如下:
<template>
<div>
<ComponentA />
<ComponentB />
</div>
</template>
<script>
const ComponentA = () => import('./ComponentA')
const ComponentB = () => import('./ComponentB')
export default {
// ...
components: {
ComponentA,
ComponentB
},
// ...
}
</script>
复制代码
使用resolve => require(['./ComponentA'], resolve),使用方法如下:
<template>
<div>
<ComponentA />
</div>
</template>
<script>
const ComponentA = resolve => require(['./ComponentA'], resolve)
export default {
// ...
components: {
ComponentA
},
// ...
}
</script>
复制代码
1.6 vuex的几种属性和作用,以及使用vuex的基本模式
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
vuex的基本工作模式如下图所示:
state 单一状态树,用一个对象就包含了全部的应用层级状态,并且作为一个数据源而存在 getters 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算 比如如下案例:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
// 访问getters里的属性
this.$store.getters.doneTodos
复制代码
Mutation 更改 Vuex 的 store 中的状态的方法,使用案例如下:
const store = new Vuex.Store({
state: {
num: 1
},
mutations: {
add (state) {
// 变更状态
state.num++
}
}
})
// 在项目中使用mutation
store.commit('add')
// 添加额外参数
store.commit('add', 10)
复制代码
Action Action提交的是mutation,而不是直接变更状态,可以包含任意异步操作,具体用法如下:
const store = new Vuex.Store({
state: {
num:
},
mutations: {
add (state) {
state.num++
}
},
actions: {
add (context) {
context.commit('add')
},
asyncAdd ({ commit }) {
setTimeout(() => {
commit('add')
}
}
})
// 分发action
store.dispatch('add')
// 异步action
store.dispatch('asyncAdd')
// 异步传参
store.dispatch('asyncAdd', { num: 10 })
复制代码
Module 将store分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
笔者更具实际经验总结了一套标准使用模式,就拿笔者之前的开源XPXMS举例,如下:
store目录是用来组织vuex代码用的,我将action,mutation,state分文件管理,这样项目大了之后也很容易管理和查询。接下来看看是如何组织的:
// type.ts
// 用来定义state等的类型文件
export interface State {
name: string;
isLogin: boolean;
config: Config;
[propName: string]: any; // 用来定义可选的额外属性
}
export interface Config {
header: HeaderType,
banner: Banner,
bannerSider: BannerSider,
supportPay: SupportPay
}
export interface Response {
[propName: string]: any;
}
// state.ts
// 定义全局状态
import { State } from './type'
export const state: State = {
name: '',
isLogin: false,
curScreen: '0', // 0为pc, 1为移动
config: {
header: {
columns: ['首页', '产品', '技术', '运营', '商业'],
height: '50',
backgroundColor: '#000000',
logo: ''
}
},
// ...
articleDetail: null
};
// mutation.ts
import {
State,
Config,
HeaderType,
Banner,
BannerSider,
SupportPay
} from './type'
export default {
// 预览模式
setScreen(state: State, payload: string) {
state.curScreen = payload;
},
// 删除banner图
delBanner(state: State, payload: number) {
state.config.banner.bannerList.splice(payload, 1);
},
// 添加banner图
addBanner(state: State, payload: object) {
state.config.banner.bannerList.push(payload);
},
// ...
};
// action.ts
import {
HeaderType,
Response
} from './type'
import http from '../utils/http'
import { uuid, formatTime } from '../utils/common'
import { message } from 'ant-design-vue'
export default {
/**配置 */
setConfig(context: any, paylod: HeaderType) {
http.get('/config/all').then((res:Response) => {
context.commit('setConfig', res.data)
}).catch((err:any) => {
message.error(err.data)
})
},
/**header */
saveHeader(context: any, paylod: HeaderType) {
http.post('/config/setHeader', paylod).then((res:Response) => {
message.success(res.data)
context.commit('saveHeader', paylod)
}).catch((err:any) => {
message.error(err.data)
})
},
// ...
};
// index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import { state } from './state';
import mutations from './mutation';
import actions from './action';
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions
});
// main.ts
// 后挂载到入口文件的vue实例上
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store/';
import './component-class-hooks';
import './registerServiceWorker';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
复制代码
我们在实际项目中都可以使用这种方式组织管理vuex相关的代码。
1.7 vue-router基本使用模式和导航钩子的用法及作用
vue-router使用大家想必不是很陌生,这里直接写一个案例:
// router.ts
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/admin/Home.vue';
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
component: Home,
beforeEnter: (to, from, next) => {
next();
},
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: '',
name: 'header',
component: () => import(/* webpackChunkName: "header" */ './views/admin/subpage/Header.vue'),
},
{
path: '/banner',
name: 'banner',
component: () => import(/* webpackChunkName: "banner" */ './views/admin/subpage/Banner.vue'),
},
{
path: '/admin',
name: 'admin',
component: () => import(/* webpackChunkName: "admin" */ './views/admin/Admin.vue'),
},
],
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'),
meta:{
keepAlive:false //不需要被缓存的组件
}
},
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "404" */ './views/404.vue'),
},
],
});
// 路由导航钩子的用法
router.beforeEach((to, from, next) => {
if(from.path.indexOf('/preview') < ) {
sessionStorage.setItem('prevToPreviewPath', from.path);
}
next();
})
export default router
复制代码
1.8 vue中检测变化的注意事项
1.9 对指定页面使用keep-alive路由缓存
通过路由配置文件和router-view设置:
// routes 配置
export default [
{
path: '/A',
name: 'A',
component: A,
meta: {
keepAlive: true // 需要被缓存
}
}, {
path: '/B',
name: 'B',
component: B,
meta: {
keepAlive: false // 不需要被缓存
}
}
]
复制代码
路由视图配置:
// 路由设置
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 会被缓存的视图组件-->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 不需要缓存的视图组件-->
</router-view>
复制代码
通过router-view的key属性 具体方式如下:
<template>
<div id="app">
<keep-alive>
<router-view :key="key" />
</keep-alive>
</div>
</template>
<script lang="ts">
import { Vue } from 'vue-property-decorator';
import Component from 'vue-class-component';
@Component
export default class App extends Vue {
get key() {
// 缓存除预览和登陆页面之外的其他页面
console.log(this.$route.path)
if(this.$route.path.indexOf('/preview') > -1) {
return '0'
}else if(this.$route.path === '/login') {
return '1'
}else {
return '2'
}
}
}
</script>
复制代码
1.10 vue常用工具函数总结
总结一下笔者在vue项目中的常用的工具函数。
识别ie浏览器
/**
* 识别ie--浅识别
*/
export const isIe = () => {
let explorer = window.navigator.userAgent;
//判断是否为IE浏览器
if (explorer.indexOf("MSIE") >= ) {
return true;
}else {
return false
}
}
复制代码
颜色16进制转rgba
/**
* 颜色转换16进制转rgba
* @param {String} hex
* @param {Number} opacity
*/
export function hex2Rgba(hex, opacity) {
if(!hex) hex = "#2c4dae";
return "rgba(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt("0x" + hex.slice(5, 7)) + "," + (opacity || "1") + ")";
}
复制代码
去除html标签
// 去除html标签
export const htmlSafeStr = (str) => {
return str.replace(/<[^>]+>/g, "")
}
复制代码
获取url参数对象
/* 获取url参数 */
export const getQueryString = () => {
let qs = location.href.split('?')[1] || '',
args = {},
items = qs.length ? qs.split("&") : [];
items.forEach((item,i) => {
let arr = item.split('='),
name = decodeURIComponent(arr[]),
value = decodeURIComponent(arr[1]);
name.length && (args[name] = value)
})
return args;
}
复制代码
解析url参数
/* 解析url参数 */
export const paramsToStringify = (params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
return `${query.join('&')}`
}else{
return ''
}
}
复制代码
将数据转化为数组
export const toArray = (data) => {
return Array.isArray(data) ? data : [data]
}
复制代码
带参数跳转url(hash模式)
/**
* 带参数跳转url(hash模式)
* @param {String} url
* @param {Object} params
*/
export const toPage = (url, params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
window.location.href = `./index.html#/${url}?${query.join('&')}`;
}else{
window.location.href = `./index.html#/${url}`;
}
}
复制代码
控制字符串显示,超出指定字数则显示省略号
/**
* 指定字符串 溢出显示省略号
* @param {String} str
* @param {Number} num
*/
export const getSubStringSum = (str = "", num = 1) => {
let newStr;
if(str){
str = str + '';
if (str.trim().length > num ) {
newStr = str.trim().substring(, num) + "...";
} else {
newStr = str.trim();
}
}else{
newStr = ''
}
return newStr;
}
复制代码
生成uuid
/**
* 生成uuid
* @param {number} len 生成指定长度的uuid
* @param {number} radix uuid进制数
*/
export function uuid(len, radix) {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = ; i < len; i++) uuid[i] = chars[ | Math.random()*radix];
} else {
let r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = ; i < 36; i++) {
if (!uuid[i]) {
r = | Math.random()*16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
复制代码
生成指定格式的时间字符串
/**
* 生成指定格式的时间
* @param {*} timeStemp 时间戳
* @param {*} flag 格式符号
*/
export function formatTime(timeStemp, flag) {
let time = new Date(timeStemp);
let timeArr = [time.getFullYear(), time.getMonth() + 1, time.getDate()];
return timeArr.join(flag || '/')
}
复制代码
1.11 如何基于axios二次封装一个具有请求/响应拦截的http请求
import axios from 'axios'
import qs from 'qs'
// 请求拦截
axios.interceptors.request.use(config => {
// 此处可以封装一些加载状态
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截
axios.interceptors.response.use(response => {
return response
}, error => {
return Promise.resolve(error.response)
})
function checkStatus (response) {
// 此处可以封装一些加载状态
// 如果http状态码正常,则直接返回数据
if(response) {
if (response.status === 200 || response.status === 304) {
return response.data
// 如果不需要除了data之外的数据,可以直接 return response.data
} else if (response.status === 401) {
location.href = '/login';
} else {
throw response.data
}
} else {
throw {data:'网络错误'}
}
}
// axios默认参数配置
axios.defaults.baseURL = '/api/v0';
axios.defaults.timeout = 10000;
// restful API封装
export default {
post (url, data) {
return axios({
method: 'post',
url,
data: qs.stringify(data),
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
get (url, params) {
return axios({
method: 'get',
url,
params, // get 请求时带的参数
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
del (url, params) {
return axios({
method: 'delete',
url,
params, // get 请求时带的参数
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
}
}
复制代码
1.12 vue常用社区组件,插件
UI框架
elementUI
iview
Mint UI 基于 Vue.js 的移动端组件库
Vant 有赞团队的移动端组件库
社区组件
Vuetable-2 基于vue的强大的表格组件
vue-fa 基于vue的图标组件库
vue-notification vue优美的信息通知组件
vue-progress-path vue个性的路径进度条组件
Vue树组件,可让您以美观和逻辑的方式呈现层次结构的数据
vue-social-sharing vue社区分享组件
vue-qrcode-reader 一组用于检测和解码二维码的Vue.js组件
vue-clipboard2 基于vue的剪切板组件
cool-emoji-picker vue表情包组件
Vue-tabs-component 强大而美观的tab组件
更多组件可以在vue插件社区查看。
2. vue项目配置经验总结
在讲完vue项目经验之后,为了让大家能独立负责一个项目,我们还需要知道从0开始搭建项目的步骤,以及通过项目实际情况,自己配置一个符合的项目框架,比如有些公司会采用vue+element+vue+less搭建,有些公司采用vue+iview+vue+sass,或者其他更多的技术栈,所以我们要有把控能力,我们需要熟悉webpack或者vue-cli3脚手架的配置,笔者之前有些过详细的webpack和vue-cli3搭建自定义项目的文章,这里由于篇幅有限就不一一举例了。感兴趣的朋友可以参考以下两篇文章:
一张图教你快速玩转vue-cli3
用 webpack 4.0 撸单页/多页脚手架 (jquery, react, vue, *cript)
3. vue组件设计经验总结
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。几乎任意类型的应用界面都可以抽象为一个组件树。在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。
确定组件设计的边界和功能定位 组件尽量遵循单一职责原理,用组合代替功能的杂糅 组件属性暴露要适度,不可过渡暴露属性 组件封装要考虑可重用,可组合,可配置 做好组件类型设计的划分(展示型组件,录入型组件,基础组件, 布局组件,反馈型组件,业务组件等)
<template>
<div>
<a-upload
:action="action"
listType="picture-card"
:fileList="fileList"
@preview="handlePreview"
@change="handleChange"
:remove="delFile"
:data="data"
>
<template v-if="!fileList.length && defaultValue">
<img :src="defaultValue" alt="" style="width: ">
</template>
<template v-else>
<div v-if="fileList.length < 2">
<a-icon type="plus" />
<div class="ant-upload-text">上传</div>
</div>
</template>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: " :src="previewImage" />
</a-modal>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component
export default class Upload extends Vue {
@Prop({ default: 'https://www.mocky.io/v2/5cc8019d300000980a055e76' })
action!: string;
@Prop()
defaultValue: string;
@Prop()
data: object;
@Prop({ default: function() {} })
onFileDel: any;
@Prop({ default: function() {} })
onFileChange: any;
public previewVisible: boolean = false;
public previewImage: string = '';
public fileList: object[] = [];
// 预览图片
public handlePreview(file: any) {
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
}
// 删除文件和回调
public delFile(file: any) {
this.fileList = [];
this.onFileDel();
}
// 文件上传变化的处理函数
public handleChange({ file }: any) {
this.fileList = [file];
if(file.status === 'done') {
this.onFileChange(file.response.url);
} else if(file.status === 'error') {
this.$message.error(file.response.msg)
}
}
// 取消预览
public handleCancel() {
this.previewVisible = false;
}
}
</script>
复制代码
《精通react/vue组件设计》之快速实现一个可定制的进度条组件 《精通react/vue组件设计》之用纯css打造类materialUI的按钮点击动画并封装成react组件 3分钟教你用原生js实现具有进度监听的文件上传预览组件
4. vue项目架构与服务化探索
这里是笔者总结的一套思维导图:
后
相关文章