目录:
- 1.1. 生命周期
- 1.2. v-if 和 v-show的区别
- 1.3. data 为什么一定是一个函数
- 1.4. slot插槽
- 1.5. 杂记
- 1.6. 混入 mixins
- 1.7. 自定义指令
- 1.8. 渲染函数render
- 1.9. 过滤器
- 1.10. 路由
- 1.11. 状态管理
- 1.12. 多语言
- 1.13. axios
- 1.14. mock模拟json数据
- 1.15. iconfont阿里巴巴图标库使用
- 1.16 项目地址
- 生命周期
可以在浏览器中运行以下代码,更能清楚的认识和理解生命周期
官方:https://cn.vuejs.org/v2/guide/instance.html<!DOCTYPE html> <html> <head> <title></title> <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script> </head> <body> <div id="app"> <p>{{ message }}</p> </div> <script type="text/javascript"> var app = new Vue({ el: '#app', data: { message : "xuxiao is boy" }, beforeCreate: function () { console.group('beforeCreate 创建前状态===============》'); console.log("%c%s", "color:red" , "el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //undefined console.log("%c%s", "color:red","message: " + this.message) }, created: function () { console.group('created 创建完毕状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeMount: function () { console.group('beforeMount 挂载前状态===============》'); console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, mounted: function () { console.group('mounted 挂载结束状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeUpdate: function () { console.group('beforeUpdate 更新前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, updated: function () { console.group('updated 更新完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, beforeDestroy: function () { console.group('beforeDestroy 销毁前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, destroyed: function () { console.group('destroyed 销毁完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message) } }) </script> </body> </html>
我们可以利用chrome的开发者工具,来显示的调用上面html的方法,app是vue的实例,例如app. destroyed()
- v-if 和 v-show的区别
官方:https://cn.vuejs.org/v2/guide/conditional.html
一般会用作面试题
区别:v-if 如果第一次为false,此条件块并不会进行渲染,直到为true时,开始渲染,会根据条件适当的销毁与重建条件块内的组件与事件监听器 而 v-show 无论初始条件是什么,其都会进行渲染,渲染为 display:none 与block的切换,建议频繁使用的话用v-show 记一下:v-if与v-for 一起使用时,v-for优先级更高,so,我们才会写 v-for=”item in arr” v-if=”item.a === 3 :key=”item.b”…….
官方:https://cn.vuejs.org/v2/guide/components.html
- data 为什么一定是一个函数
data: {
count: 0
}
上面的写法其实就是一个对象,你用到对象就要考虑对象的引用,这很容易会影响到其他组件的使用,so,组件的data必须是一个函数,返回对象的独立拷贝,即return{}
1 匿名插槽:
<slot></slot>
组件渲染的时候,会将slot替换为你的元素,当然如果你没给这个口子的话,任何传入组件的内容都会被抛弃
2 具名插槽
我们给slot添加一个name属性,
<slot name="header"></slot> 使用的时候可以在任意标签上,给其一个slot=“header”来告诉vue的渲染机制,这个元素就是插到 这个地方的,默认插槽,给个默认值的意思。
3 作用域插槽
可以从子组件获取数据的插槽,官方写的很好,看到这个,可以想到elementUi里的el-table-cloumn就是
<el-table-column
prop="name"
label="企业名称"
align="center"
:show-overflow-tooltip="true"
min-width="120">
<template slot-scope="scope">
<el-button @click.native="hanldeClick(scope.row)" type="text" class="table-link">
{{scope.row.name}}
</el-button>
</template>
</el-table-column>
可以清楚的看到 这种插槽的强大之处
异步组件,在vue-router使用,达到懒加载即按需引入的需求
- 杂记
component: resolve => {
require.ensure([], () => {
resolve(require('../../page/main_$tp/wait-approval/trade-orders.vue'))
}, 'mainwaitApproval')
}
Vue的广播事件,emitbus,达到非父子组件的消息传递
Vue的动画,实现炫酷的css效果,轮播图的实现变得非常简单
Vue.prototype 添加vue的实例方法
官方:https://cn.vuejs.org/v2/guide/mixins.html
这东西让我的感觉就像是vue实例初始化的copy,使用例如我们可以先定一个js文件 取名formatDate.js
- 混入 mixins
import moment from 'moment'
const mixin = {
methods: {
/**
* 格式化时间
* @param {string|number|object|Array} dateTime - 时间,可以是一个字符串、时间戳、表示时间的对象、Date对象或者******表示时间的数组
* @param {string} [fmt] - 格式
* @returns {string} 返回格式化后的日期时间,默认格式:2018年1月11日 15:00
* @see [momentjs]{@tutorial http://momentjs.cn/}
*/
formatDate (dateTime, fmt = 'YYYY年M月DD日 HH:mm:ss') {
if (!dateTime) {
return ''
}
moment.locale('zh-CN')
dateTime = moment(dateTime).format(fmt)
return dateTime
}
}
}
export default mixin
/// 组件内使用
全局使用:在main.js入口文件中
import mixinfrom'com@/mixin/formatDate'
Vue.mixin(mixin)
弄完之后在组件中,可以直接在data初始化时,直接使用
官方不推荐使用全局混入,因为这个会影响到每个单独创建的vue实例
局部使用: 在组件中
可以直接在data初始化时,直接使用
官方:https://cn.vuejs.org/v2/guide/custom-directive.html
例如,我们可以创建一个focus.js的文件
- 自定义指令
import Vue from 'vue';
Vue.directive('autoFcs',{
// 钩子函数,被绑定元素插入父节点时调用 (父节点存在即可调用,不必存在于 document 中)。
inserted(el){
el.focus()
}
})
在main.js入口文件全局引入import 'com@/directives/focus'
在组件中 和v-model类似 直接 v-autoFcs
上面是使用,理解 我们可以看一下下面的html
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<h1>自定义指令及其生命周期</h1>
<div id="app">
<div v-mydirective.modify1.mofify22="mycolor">
{{ name }}
</div>
</div>
<button onclick="unbindApp()">解绑</button>
<script>
function unbindApp() {
app.$destroy();
}
Vue.directive("mydirective",{
bind:function (el, binding, vnode) { //1-被绑定
console.log("1-bind 被绑定");
console.log("el:",el);
console.log("binding:",binding);
console.log("vnode:",vnode);
el.style.color=binding.value;
},
inserted:function (el, binding, vnode) { //2-被插入
console.log("2-inserted 被插入");
},
update:function (el, binding, vnode) { //3-更新
console.log("3-update 更新");
},
componentUpdated:function (el, binding, vnode) { //4-更新完成
console.log("4-componentUpdated 更新完成");
},
unbind:function (el, binding, vnode) { //5-解绑
console.log("5-unbind 解绑");
}
});
var app =new Vue({
el:"#app",
data:{
mycolor:"blue",
name:"mydirective指令"
}
});
</script>
</html>
Chrmo下开发者工具使用app来调用方法
官方:https://cn.vuejs.org/v2/guide/render-function.html
例:iview在的table渲染居然是用的reder,牛皮了哇,实现table可编辑,这里吐槽一下,elementUi用的是真的舒服,文档界面,代码使用都 good!
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="http://unpkg.com/iview/dist/styles/iview.css">
<script type="text/javascript" src="http://vuejs.org/js/vue.min.js"></script>
<script type="text/javascript" src="http://unpkg.com/iview/dist/iview.min.js"></script>
</head>
<body>
<div id="app">
<i-table border :columns="columns2" :data="data3"></i-table>
</div>
<script>
var app = new Vue({
el: '#app',
data () {
return {
list: [],
columns2: [
{
title: 'Name',
key: 'name'
},
{
title: 'Age',
key: 'age',
render: (h, params) => {
if (params.row.$isEdit) {
return h('input', {
domProps: {
value: params.row.age
},
on: {
input: function (event) {
this.$emit('input', event.target.value)
}
}
});
} else {
return h('div', params.row.age);
}
}
},
{
title: 'Address',
key: 'address'
},
{
title: 'Action',
key: 'action',
render: (h, params) => {
return h('Button', {
props: {
type: 'text',
size: 'small'
},
on: {
click: () => {
if (params.row.$isEdit) {
this.handleSave(params.row)
} else {
this.handleEdit(params.row)
}
}
}
}, params.row.$isEdit ? '保存' : '编辑')
}
}
],
data3: [
{
name: '哈哈',
age: 18,
address: '上海',
$isEdit: false
},
{
name: '啦啦',
age: 24,
address: '北京',
$isEdit: false
}
]
}
},
methods: {
handleEdit (row) {
this.$set(row, '$isEdit', true)
},
handleSave (row) {
this.$set(row, '$isEdit', false)
}
}
})
</script>
</body>
</html>
直接运行,看效果即可,看不懂的,建议看一下我上面写的vue官方文档的解释
- 过滤器
官方:https://cn.vuejs.org/v2/guide/filters.html
使用:我们可以创建一个money.js的文件。来帮我们改变参数达到需要的样子
import Vue from 'vue';
/**
* 转换为金额
* @param s 需要转换的数字
* @param n 金额后面保留的小数点的位数
* @return {string}
*/
const fmtMoney = (s, n) => {
let leZero = s < 0;
n = n > 0 && n <= 20 ? n : 2
s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(n) + ''
if(leZero) s = s.substr(1)
var l = s.split('.')[0].split('').reverse(), r = s.split('.')[1]
let t = ''
for (let i = 0; i < l.length; i++) {
t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? ',' : '')
}
let rst = t.split('').reverse().join('') + '.' + r
return leZero ? '-'+rst : rst
}
/**
* 金额转换为数字
* @param s 需要转换的金额字符串
* @return {Number}
*/
const parseMoney = (s) => {
return parseFloat(s.replace(/[^\d\.-]/g, ''))
}
/**
* @param num 需要转换的数字金额
* @returns {string} 金额中文大写
*/
const numToChinese = (num) => {
// 小数
let fraction = ['角', '分']
// 数字
let digit = [
'零', '壹', '贰', '叁', '肆',
'伍', '陆', '柒', '捌', '玖'
]
let unit = [
['元', '万', '亿'],
['', '拾', '佰', '仟']
]
let head = num < 0 ? '负' : ''
num = Math.abs(num)
let s = ''
for (let i = 0; i < fraction.length; i++) {
s += (digit[Math.floor(num * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '')
}
s = s || '整'
num = Math.floor(num)
for (let i = 0; i < unit[0].length && num > 0; i++) {
let p = ''
for (let j = 0; j < unit[1].length && num > 0; j++) {
p = digit[num % 10] + unit[1][j] + p
num = Math.floor(num / 10)
}
s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s
}
return head + s.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元整')
}
Vue.filter('fmtMoney', fmtMoney);
Vue.filter('parseMoney', parseMoney);
Vue.filter('numToChinese', numToChinese);
定义全局过滤器:在man.js中 import 'com@/filters/money'
在组件中即可使用 当然,我们可以串联过滤器,传递多个参数{{price| fmtMoney(3)}} price是fmtMoney函数的第一个参数,传入的参数从第二个后续排入
- 路由
官方:https://router.vuejs.org/zh/
使用,先npm install vue-router --save
import router from './router'
new Vue({
el: '#app',
router,
store,
i18n,
components: { Layout },
template: '<Layout/>'
})
这样就可以在项目中使用
里面有很多东西,建议将router的配置 放置在一个单独的文件里,最后引入main.js即可,用的最多当然是路由传参
这里以router-link为例,当然 this.$touter.push 也是一样的
路由配置
1
<router-link :to="{path: 'detail/analysis' , query: {id: 22}}">立即购买</router-link>
这里的参数会在url后面以?拼接,可以用route.query来获取
2
<router-link :to="{name: 'analysis' , query: {id: 22}}">立即购买</router-link>
效果和上面一样
3
<router-link :to="{name: 'analysis' , params: {id: 22}}">立即购买</router-link>
url没有变化,可以用route.parmas 来获取
注意 第三种,不可以使用path来传递参数params,否则获取不到
4
路由配置
<router-link :to="{name: 'analysis' , params: {id: 22}}">立即购买</router-link>
以/的形式拼接到url后面
或者
<router-link :to="{path: `detail/analysis/${333}`}">立即购买</router-link>
- 状态管理
vuex 传递参数,兄弟组件传参 通过eventbus广播传递,而父子组件通过prop emit传递,而vuex做到了把组件的共享状态抽取出来,以一个全局的单例模式(即全局只有一个vue实例)统一来进行管理,官方图例:
组件通过dispatch来触发action,而action通过commit来操作mutations从而改变其中的state数据,最后达到渲染到组件上,一个循环。当然,vue也提高了getter函数来让我们直接获取state数据
直接上手栗子:
新建一个js文件,基本结构:
import Vue from 'vue'
import {getAppData} from 'api@/list-api'
const state = {
orderList: [],
params: {}
}
const getters = {
getOrderList: state => state.orderList
}
const actions = {
fetchOrderList ({commit, state}) {
getAppData(state.params).then((rst) => {
state.orderList = rst.data.data.getOrderList.list
}).catch((err) => {
console.log(err)
})
}
}
const mutations = {
changeOrderList (state, payload) {
state.orderList = payload
},
updateParams (state, {key, val}) {
state.params[key] = val
}
}
export default {
state,
getters,
actions,
mutations
}
actions相比于mutations的好处在于其可以执行异步操作,api请求啊,promise等等,我们可以放到一个index.js出口文件统一将其导出
import Vuex from 'vuex'
import Vue from 'vue'
import orderList from './modules/orderList'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
orderList
}
})
最后将其import倒入main.js入口文件,即可全局使用操作orderList
- 多语言
项目上实现多语言
首先需要导入 vue-il8n,
简单demo:
当然我这边也是为了练习,自己写了个demo,so我将其放到了store去管理,当然,实际项目不需要。
// import Cookies from 'js-cookie'
const app = {
state: {
language: localStorage.getItem('language') || 'en'
},
getters: {
getLanguage: state => state.language
},
mutations: {
SET_LANGUAGE: (state, language) => {
state.language = language
localStorage.setItem('language', language)
},
DEL_LANGUAGE: (state) => {
localStorage.removeItem('language')
},
},
actions: {
setLanguage({ commit }, language) {
commit('SET_LANGUAGE', language)
},
delLanguage({ commit }) {
commit('DEL_LANGUAGE')
}
}
}
export default app
之前是将信息放到cookie里面去了,放到localStorage也能达到效果,而且浏览器工具也能看到
主要js,去使用VueI18n
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// import Cookies from 'js-cookie'
import enLocale from './en'
import zhLocale from './zh'
Vue.use(VueI18n)
const messages = {
en: {
...enLocale,
},
zh: {
...zhLocale,
}
}
const i18n = new VueI18n({
// set locale
// options: en or zh
locale: localStorage.getItem('language') || 'en',
// set locale messages
messages
})
export default i18n
上面可以看到有两个文件,json格式存放中英文 全局导入main.js 即可在页面下拉选切换
- axios
vue官方有一个叫vue-resource的插件来处理请求,但是尤大推荐axios等工具来请求api,简单封装一下
import axios from 'axios'
// create an axios instance
const demoApi = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: 5000 // request timeout
// headers: {
// Authorization: store.getters.token
// }
})
// request interceptor
demoApi.interceptors.request.use(
config => {
// Do something before request is sent
// if (store.getters.token) {
// // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
// config.headers['X-Token'] = getToken()
// }
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// respone interceptor
demoApi.interceptors.response.use(
response => response,
/**
* 下面的注释为通过在response里,自定义code来标示请求状态
* 当code返回如下情况则说明权限有问题,登出并返回到登录页
* 如想通过xmlhttprequest来状态码标识 逻辑可写在下面error中
* 以下代码均为样例,请结合自生需求加以修改,若不需要,则可删除
*/
// response => {
// const res = response.data
// if (res.code !== 20000) {
// Message({
// message: res.message,
// type: 'error',
// duration: 5 * 1000
// })
// // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// // 请自行在引入 MessageBox
// // import { Message, MessageBox } from 'element-ui'
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload() // 为了重新实例化vue-router对象 避免bug
// })
// })
// }
// return Promise.reject('error')
// } else {
// return response.data
// }
// },
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export {demoApi}
单独Api的js文件 只给了个get形式
import { demoApi } from '../fetch.js'
export function getAppData(params) {
return demoApi({
url: '/appData',
method: 'get',
params: { params }
})
}
即可在组件内import内直接倒入使用
- mock模拟JSON数据
1 项目本地创建一个json文件,里面类似模拟后台到前端的数据 在 webpack.dev.config.js中就可以在单组件中使用/api/appData来获取数据 当然。我不推荐这种写法,我推荐一个网站https://www.easy-mock.com/,来mock数据,适合个人团队,模拟的数据也是什么的棒,上面的写法推到服务器上是不适用的,而easy-mock 可以,不用考虑跨域问题,适合自己写一个demo配置不同环境的api路径 栗:在dev.env.js中配置
在 prod.env.js配置这样就可以实现线上与本地的api基本路径请求
- iconfont阿里巴巴图表库使用
这种网上的很多,栗子:https://www.cnblogs.com/xuange306/p/9156651.html 我说的基于上面的基础上的,我建议是封装一个vue组件,我这边
<style lang="scss" rel="stylesheet/scss">
@import "iconfont.css";
</style>
<template>
<i :class="classes" :style="styles"></i>
</template>
<script>
const prefixCls = 'uzi-icon'
export default {
name: 'UziIcon',
componentName: 'UziIcon',
props: {
type: String,
size: [Number, String],
color: String,
fontStyle: {
default: 'normal',
type: String
}
},
computed: {
classes () {
return `${prefixCls} ${prefixCls}-${this.type}`
},
styles () {
let style = {}
style['font-style'] = `normal`
if (this.size) {
style['font-size'] = `${this.size}rem`
}
if (this.fontStyle) {
style['font-style'] = this.fontStyle
}
if (this.color) {
style.color = this.color
}
return style
}
}
}
</script>
极大的方便我们的使用,入口文件导出供其他组件使用
import Icon from './icon.vue'
export default Icon
当然这样我门就可以用了,但是我比较懒,我想实现启动项目或者运行命令,自动化拉取更新iconfont几个文件,而且实现多个图标库的一起使用,
我在build目录下新建了一个icon.js的文件
const chalk = require('chalk')
const _ = require('lodash')
const path = require('path')
const fs = require('fs')
const request = require('request')
const wget = require('wget')
const FONT_FILE_TYPE_EOT = 'eot'
const FONT_FILE_TYPE_WOFF = 'woff'
const FONT_FILE_TYPE_TTF = 'ttf'
const FONT_FILE_TYPE_SVG = 'svg'
const icon = [{ // 自动维护图标
aliUrl: '//at.alicdn.com/t/font_819408_9zb80t2tc6.css', // 暂时只支持使用阿里巴巴图标库
dir: 'src/components/uzi-icon' // 公共图标
}]
const postUrl = (_url,fn) => {
request(_url, function (error, response, body) {
if (!error && response.statusCode == 200) {
fn(body)
}else {
throw new Error("gen Icon error")
}
})
}
const downIcon = (iconUrl,dir)=> {
postUrl('https:'+iconUrl,(chunk)=>{
let form = 0
let to = form
let urlList = []
let count = 0
while (form !== -1 && to !== -1){
count++
if(count > 3000) throw new Error("gen icon failed")
form = to + 1
form = chunk.indexOf("url(",form)
to = chunk.indexOf(")",form+1)
if(form !== -1 && to !== -1){
urlList.push(chunk.substr(form+5,to - form-6))
}
}
urlList = _.uniq( urlList.map(_url => _url.split("#")[0]) )
count = urlList.length
urlList.forEach(_url => {
let __url = _url.split("?")[0]
let {ext} = path.parse(__url)
let fileName = "iconfont"+ext
let filePath = path.join(dir,fileName)
fs.existsSync(filePath) && fs.unlinkSync(filePath)
if(__url[0] !== '/') return
let download = wget.download("https:"+__url, filePath, {})
chunk.split(_url).join("")
download.on('error', function(err) {
throw err
})
})
urlList.forEach(_url => {
let strs = _url.split('?')[0].split('.')
let type = strs[strs.length - 1]
if(_url[0] !== '/') return
chunk = chunk.replace(_url, './iconfont.' + type)
chunk = chunk.replace(_url, './iconfont.' + type)
})
fs.writeFileSync(path.join(dir, 'iconfont.css'),chunk)
})
}
for(let item of icon){
downIcon(item.aliUrl,path.resolve(item.dir))
}
通过wget插件实现下载,在package.json 中
或者启动项目运行,只需当然你需要更新图标路径:再每次启动项目 或者运行npm run uzi-icon 即可
当然这种方法 不是最好的,如何优雅的使用icon中这篇文章 推荐使用svg,我这篇也写过,在博客中有,个人推荐使用svg-icon
最后贴一下项目地址
https://github.com/goSunadeod/vue-demo1