记一次Vue实战总结
# vue
目录结构
- assets一般用来放置小的图片,icon类, public一般用来放置大的图片
- build之后,public下面的文件夹会原封不动的添加到dist中
- assets目录,build之后会被合并到一个文件中,进行压缩,多用来存放业务级的js、css等,如一些全局的scss样式文件、全局的工具类js文件等。
跨域
原因
跨域问题是有浏览器的同源策略导致的。同源策略是浏览器的安全策略,能极大避免浏览器收到攻击。
同源是指协议、域名、端口这三者相同(必须是严格三者都相同),对于http协议,默认的端口是80;对于https协议,默认端口为443,也就是说 http://www.baidu.com:80 等价于 http://www.baidu.com
解决办法
jsonp请求
原理就是利用script标签的src没有跨域限制.直接通过<script src="接口+回调函数">
调用
Nginx反向代理
通过配置nginx,比如
location /api {
proxy_pass https://www.imooc.com;
}
通过代理,我们就可以这样调用
login.onclick = ()=>{
axios.get('/api/xxx').then(()=>{
xxxx
})
}
这样我们通过代理,实际访问的是 https://www.imooc.com/xxx
CROS
服务端设置,前端直接调用,(说明:后台允许前端某个站点进行访问),后端设置Access-Control-Allow-Origin,来指定某个站点进行调用,可以是静态的也可以动态的。
接口代理
在vue当中的实现方式,创建一个vue.config.js配置文件(这个其实是webpack配置表,最终会传递给nodejs服务器)
module.exports = {
devServer:{
host: "localhost",
port: 8080,
proxy: {
'/api': {
target: 'https://www.imooc.com',
changeOrigin: true,
pathRewrite: {
'/api': ''
}
}
}
}
}
封装storage
cookie,localStorage,sessionStorage三者区别
localStorage与sessionStorage合称为storage。localStorage存储在本地,浏览器关闭不会消失。sessionStorage存储在内存,随着浏览器关闭会消失。
cookie与storage区别:
- 储存大小 : cookie 4k , storage 4M
- 有效期: cookie 拥有有效期 , storage永久存储
- cookie会发送到服务端,存储到内存,storage只存储在浏览器端
- 路径: cookie有路径限制,storage只存储在域名下
- API: cookie没有专门的Api
为什么要封装storage
- storage只存储字符串,需要人工智能转化为json对象
- storage只能一次性清空,不能单个清空
封装
/**
* storage封装
*/
const STORAGE_KEY = 'mi'; //storage标识符,名字
export default {
/*
setItem(key,value,module)
module是某一模块下的,比如
{
user:{
username: xxx
}
}
设置user下面的username
*/
setItem(key, value, module) {
if (module) {
let val = this.getItem(module);
val[key] = value;
this.setItem(module, val)
} else {
let val = this.getStorage();
val[key] = value;
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(val))//保存数据到sessionStorage中,window.sessionStorage.setItem(名字,数据);
}
},
/*
{
user:{
username: lihua,
age: 18
}
}
getItem(username,user)
*/
getItem(key, module) {
if (module) {
let val = this.getItem(module)
if (val)
return val[key]
}
return this.getStorage()[key]
},
// 获取sessionStorage
getStorage() {
return JSON.parse(window.sessionStorage.getItem(STORAGE_KEY) || '{}')
},
//清除方法
clear(key, module) {
let val = this.getStorage();
if (module) {
if (!val[module]) return;
delete val[module][key]
} else {
delete val[key]
}
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(val))
}
}
axios
config default
axios.defaults.baseURL = 'https://api.example.com';//设置接口路径的相同部分
axios.defaults.timeout = 8000 //如果8秒了还没请求返回数据,就终止
接口拦截
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
每次调用axios请求,首先都会经过interceptors数据拦截,比如
axios.interceptors.response.use(function(response) {
let val = response.data;//接口返回的数据
let path = window.location.pathname;
if (val.status == 0) {// 根据接口返回的状态字来过滤
return val.data // 返回 接口数据 当中的data模块,而不用返回状态字了
} else if (val.status == 10) { // 如果状态字为10(自己设定),那么就是未登录,我们就拦截,不返回data模块
if (path != '/index')
window.location.href = '/login'
return Promise.reject(val);
} else { //其他状态字,也同样不返回数据
alert(val.msg)
return Promise.reject(val)
}
}, (error) => { //请求的接口地址报错,进行处理
let res = error.response;
Message.error(res.data.message);
return Promise.reject(error);
})
{
status: 0 ,
data:{
user:{
username: 'kkk',
age: 18
}
}
}
假设我们通过请求返回这个数据,首先通过interceptors过滤,判断状态字 为 0 ,所以就返回了里面的data字段,最终我们axios请求返回的res为data。
语法
get请求
axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
或者
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
或者
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
post请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
动态请求
this.axios[method](url,params).then(res=>{
this.$message.success("操作成功")
})
method动态,可以为post,get,delete,
vue 路由
路由嵌套
如果多个页面有相同的头部和顶部,可以把他们抽离出来,使用路由嵌套进行开发
const router = new VueRouter({
routes: [
{ path: '/user/', component: User,
children: [//嵌套子路由
{
// 当 /user/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
/user的vue文件如下
<template>
<div class="home">
<nav-header></nav-header>
<router-view></router-view>
<nav-footer></nav-footer>
</div>
</template>
子路由的component就会渲染到router-view当中。这样他们的nav-header和nav-footer就会得到复用。
当我们访问/user时,router-view里面是不会渲染任何东西的
动态路由
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
像 /user/foo
和 /user/bar
都将映射到相同的路由.
路径参数使用冒号:标记,参数值会被设置到 this.$route.params 当中
this.$route.path 对应当前路由的路径,总被解析为绝对路径,如 /user/boo
this.$route.query 标识url查询字符串,对于路径 /foo?user=1 则,this.$route.query.user == 1,如果没有则为空对象。
this.$route.hash 当前路由的 hash 值 (带 #
) ,如果没有 hash 值,则为空字符串。
this.$route.name 当前路由的名称,如果有的话。
动态路由,原来的组件实例会被复用,这也就意味着组件的生命周期钩子不会再被重复调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route
对象
编程式导航
this.$router.push
想要导航到不同的 URL,则使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
this.$router.push 相当于
const userId = '123' router.push({ name: 'user', params: { userId }}) // -> /user/123 router.push({ path: `/user/${userId}` }) // -> /user/123 // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }})
this.$router.go
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步
// 在浏览器记录中前进一步,等同于 history.forward()
this.$router.go(1)
// 后退一步记录,等同于 history.back()
this.$router.go(-1)
// 前进 3 步记录
this.$router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
this.$router.go(-100)
this.$router.go(100)
路由的命名
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',// 将这个路由命名为user
component: User
}
]
})
通过上面代码,将次路由命名为user,则我们通过push可以这么调用
router.push({ name: 'user', params: { userId: 123 }})
路由重定向
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
路由懒加载
两种方式。
第一种:
{
path: 'product/:id',
name: 'product',
component: resolve => require(['./../views/product.vue'], resolve)
},
第二种
{
path: 'confirm',
name: 'order-confirm',
component: () =>import ('./../views/orderConfirm.vue')
},
Mixin
好处
减少代码冗余,避免样式污染
用法
创建一个scss文件(mixin.scss),然后。。就可以在里面写函数,封装我们代码,来重复调用
//flex布局复用
@mixin flex($hov:space-between,$col:center){
display:flex;
justify-content:$hov;
align-items:$col;
}
@mixin bgImg($w:0,$h:0,$img:'',$size:contain){
display:inline-block;
width:$w;
height:$h;
background:url($img) no-repeat center;
background-size:$size;
}
调用,用 @include 去调用
@import './../assets/scss/mixin'; // 首先得引入下刚刚创建的mixin文件
.munu-item-a{
font-size: 16px;
color: #fff;
padding-left: 30px;
display: block;
position: relative;
&::after{
content:' ';
position: absolute;
right: 30px;
top: 17.5px;
@include bgImg(10px,15px,'../../public/imgs/icon-arrow.png') // 调用
}
}
vuex
介绍
每一个vuex的应用核心是store(仓库),有以下特点。
- vuex的存储状态是响应式的,如果store中的内容状态发生变化,相应的组件也会得到更新应用。
- 不能直接改变store状态,唯一途径是通过提交(commit)mutation,这样可以方便的跟踪每一个状态的变化。
- 为了让各个组件访问得了store,需要将store挂载到vue实例上,这样各个组件就可以通过this.$store来访问。
State
单一状态树,包含了所有状态,可以和data相比较,相当于存储数据。
可以直接访问,或者通过辅助函数MapState
Getters
getters可以理解为vuex的计算属性,只有他的依赖值发生改变,getters接收state作为其第一个参数
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,接收state作为第一个参数,通过this.$store.commit方法来触发mutation
mutations: {
increment (state, n) {
state.count += n
}
}
mutation必须是同步函数,可以用commit提交或者mapMutation
Action
类似于mutation,Action提交的是mutation,而不是直接变更状态,Action可以包含异步操作。
Action接收一个context对象作为参数,这个对象与store实例具有相同方法和属性
context.commit 就相当于提交一个mutation。 context.state就相当于this.$store.state
Action通过this.$store.dispatch("increament")方法触发
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
流程就如那一副图所示,
组件通过dispatch派发action,然后action会commit提交mutation来改变我们的state进而改变我们的视图。
问?为什么不直接commit,还要通过派发action?
因为action无所限制,可以是异步。
项目结构
如果状态过多,应该分离出来各个文件
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
完整实例
store目录下的index.js文件,导出store的地方。
/*index.js文件*/
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
const state = {
username: '',
cartCount: 0
}
export default new Vuex.Store({
state,
mutations,
actions,
modules: {}
})
/**
* vue-mutations , mutation文件
*/
export default {
saveUserMsg(state, username) {
state.username = username
},
saveCartCount(state, cartCount) {
state.cartCount = cartCount
}
}
/**
* vue-actions , action文件
*/
export default {
saveUserMsg(context, username) {
context.commit('saveUserMsg', username)
},
saveCartCount(context, cartCount) {
context.commit('saveCartCount', cartCount)
}
}
vue封装组件
props:父组件通过将数据绑定在props当中声明的key,props声明在子组件当中,然后子组件就可以通过props使用父组件传过来的值。
emit是子组件向父组件传递数据,具体实例如下:
子组件(可重新复用):
<template>
<transition name="slide">
<div class="modal" v-show="showModal">
<div class="mask">
</div>
<div class="modal-dialog">
<div class="modal-header">
<span>{{title}}</span>
<a href="javascript:;" class="close_icon" v-on:click="$emit('cancel')"></a>
</div>
<div class="modal-body">
<slot name="body"></slot>
</div>
<div class="modal-footer">
<a href="javascript:;" class="btn" v-if="btnType==1" v-on:click="$emit('submit')">确定</a>
<a href="javascript:;" class="btn" v-if="btnType==2" v-on:click="$emit('cancel')">取消</a>
<div class="btn-group" v-if="btnType==3">
<a href="javascript:;" class="btn" v-on:click="$emit('submit')">{{sureText}}</a>
<a href="javascript:;" class="btn btn-default" v-on:click="$emit('cancel')">{{cancelText}}</a>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
props:{
modalType:{
// 小 small 中 middle 大large 表单form
type: String,
default: 'form'
},
// 标题
title:String,
// 按钮类型 1,确定按钮 2,取消按钮 3,确定取消
btnType: String,
sureText:{
type: String,
default: '确定'
},
cancelText:{
type: String,
default: '取消'
},
showModal: Boolean
}
}
</script>
<style lang="scss">
@import "../assets/scss/mixin";
@import "../assets/scss/modal";
</style>
父组件
<template>
<modal
title="提示"
sureText="查看购物车"
btnType="3"
modalType="middle"
:showModal= "showModal"
v-on:submit = goToCart
v-on:cancel = "showModal = false"
>
<template v-slot:body>
<p>添加商品成功</p>
</template>
</modal>
</template>
<script>
import Modal from '../components/Modal'
export default{
components: {
Modal
},
methods:{
goToCart(){
this.$router.push('/cart')
},
}
}
</script>
导入,引入,传参,父向子传参,子向父传事件,一气呵成