• 记一次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>
    
    
    

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

  • 相关阅读:
    发夹模式的使用及应用场景
    springBoot项目配置日志打印管理(log4j2)
    idea创建springBoot项目
    修改jdk注册表
    文件下载——下载Excel
    stream().filter()的用法
    文件上传——Spring MVC跨服务器
    文件上传——Spring MVC方式
    文件上传——传统方式
    Spring MVC响应数据方式
  • 原文地址:https://www.cnblogs.com/Dusks/p/14010383.html
Copyright © 2020-2023  润新知