记一次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,来指定某个站点进行调用,可以是静态的也可以动态的。

image-20200924145750703

接口代理

在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;
  $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

介绍

每一个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>


导入,引入,传参,父向子传参,子向父传事件,一气呵成

原文地址:https://www.cnblogs.com/Dusks/p/14010383.html