Vue学习随笔+商城项目【下】

更新日期:2021-2-10 晚 【新年快乐】
先看这里:Vue学习随笔+商城项目【上】

目录

  • (十二)组件化高级
    • 12.1 slot-插槽的基本使用
    • 12.2 slot-具名插槽的使用
    • 12.3 编译的作用域
    • 12.4 作用域插槽案例
  • (十三)Vue实例的生命周期
    • 13.1 生命周期图
    • 13.2 再探究
      • 13.2.1 beforeCreate之前
      • 13.2.2 beforeCreate和created钩子函数间的生命周期
      • 13.2.3 created钩子函数和beforeMount间的生命周期
        • 13.2.3.1el选项对生命周期影响
        • 13.2.3.2 template
      • 13.2.4 beforeMount和mounted钩子函数间的生命周期
      • 13.2.5 beforeUpdate钩子函数和updated钩子函数间的生命周期
      • 13.2.6 beforeDestroy和destroyed钩子函数间的生命周期
        • 13.2.6.1 beforeDestroy
        • 13.2.6.2 destroyed
  • (十四)前端模块化
    • 14.1 为什么要有模块化
    • 14.2 使用导出全局变量模块解决全局变量同名问题
    • 14.3 CommonJS的模块化实现
    • ES6的模块化实现
      • 14.4.1 直接导出
      • 14.4.2 统一导出
      • 14.4.3 导出函数/类
      • 14.4.4 默认导入 export default
      • 14.4.5 统一全部导入
  • (十五)webpack
    • 15.1 webpack起步
      • 15.1.1 什么是webpack
      • 15.1.2 webpack的安装
      • 15.1.3 起步
    • 15.2 webpack的配置
      • 15.2.1 基本配置
        • webpack.config.js
        • package.json
      • 15.2.2 全局安装和局部安装
    • 15.3 webpack的loader
      • 什么是loader
        • **loader使用**
      • CSS文件处理
      • less文件处理
      • 图片文件的处理
      • ES6语法处理
        • 安装
        • 配置
    • 15.4 webpack的vue
      • 简单安装使用vue
      • 如何分步抽取实现vue模块
    • 15.5 webpack的plugin
      • 添加版权的Plugin
      • 打包html的plugin
      • 压缩打包代码插件
    • 15.6 webpack搭建本地服务器
    • 15.7 webpack的配置文件分离
  • (十六)vue-cli
    • 16.1 vue-cli起步
      • 什么是vue-cli
      • **CLI是什么意思?**
      • vue cli使用
    • 16.2 vue-cli的目录结构
      • build和config
      • src和static
      • 其他相关文件
        • .babelrc文件
        • .editorconfig文件
        • .eslintignore文件
        • .eslintrc.js文件
        • .gitignore文件
        • .postcssrc.js文件
        • index.html文件
        • package.json和package-lock.json
    • 16.3 runtime-compiler和runtime-only区别
        • render函数
    • 16.4 vue-cli3
      • vue-cli3起步
      • vue-cli3的配置
  • (十七)vue-router
    • 17.1 路由简介
    • 17.2 前端/后端路由
    • 17.3 URL的hash和HTML5的history
      • URL的hash
      • HTML5的history模式
        • pushState
        • replaceState
        • go
    • 17.4 vue-router的安装配置
    • 17.5 vue-router的使用
      • 创建路由组件
      • 配置路由映射:组件和路径映射关系
      • 使用路由:通过``和``
      • 路由的默认值和history模式
      • ``的其他属性
      • 通过代码修改路由跳转
    • 17.5 渐入vue-router
      • vue-router的动态路由
      • 懒加载——vue-router的打包文件解析
      • 认识嵌套路由
      • vue-router的参数传递
        • 传递参数主要有两种类型:params和query
      • router和route的由来
    • 17.6 vue-router其他
      • vue-router的导航守卫
      • 前置守卫——前置钩子(回调)
      • 导航守卫补充——后置钩子
      • 完整的导航解析流程
    • 17.7 keep-alive
      • **keep-alive的属性**
    • 17.8 综合练习-实现Tab-Bar
      • 实现Tab-Bar思路
      • 代码实现
      • 别名配置
  • (十八)Promise
    • 18.1 什么是Promise
    • 18.2 Promise的基本使用
      • 什么时候使用Promise?
      • Promise对象
    • 18.3 Promise的三种状态
    • 18.4 Promise的链式调用
    • 18.5 Promise的all使用
  • (十九)Vuex
    • 19.1 什么是Vuex
      • 状态管理
      • 什么状态需要Vuex去管理?
      • Vuex简单模型
    • 19.2 Vuex基本使用
    • 19.3 Vuex的流程
      • 修改index.js使用mutation
      • 修改App.vue提交mutation
      • 测试
    • 19.4 Vuex的核心概念
      • State(单一状态树)
      • Getters
      • Mutation(状态更新)
        • mutation接受单个参数
        • mutation接受多个参数
        • mutation的提交风格
        • Vuex的响应式原理
        • mutation的类型常量
      • Actions
      • moudules(模块)
      • 项目结构
  • (二十)Axios
    • 20.1 为什么选择Axios
    • 20.2 axios框架基本使用
      • 多种请求方式
      • axios()
        • get请求——参数拼接
    • 20.3 axios发送并发请求
      • 格式
        • 分割结果
    • 20.4 axios的配置信息
        • 实例
        • 常见配置选项
    • 20.5 axios的实例和模块封装
        • 创建axios实例
        • 封装成单独文件
    • 20.6 axios拦截器使用
  • (二十一)Better Scroll
    • 21.1 基本使用
      • 安装
      • 使用
    • 21.2 参数
        • probeType
        • pullUpLoad
        • useTransition
    • 21.3 BScroll可滚动区域问题
      • 防抖函数
  • 商城项目1.0
    • Project setup
      • Compies and hot-reloads for development
      • Compiles and minifies for production
    • 目录结构
    • 步骤
    • tabControl的吸顶效果
        • 获取到tabControl的offsetTop
        • 监听滚动,动态改变TabControl的样式
      • 让Home保持原来状态
        • 让Home不要随意销毁掉
        • 让home中保持原来位置
    • FastClick点击事件优化
    • 图片懒加载 vue-lazyload库
      • 基本使用
      • 参数
    • CSS单位转化插件 px2vw
    • 部署
        • get请求——参数拼接
    • 20.3 axios发送并发请求
      • 格式
        • 分割结果
    • 20.4 axios的配置信息
        • 实例
        • 常见配置选项
    • 20.5 axios的实例和模块封装
        • 创建axios实例
        • 封装成单独文件
    • 20.6 axios拦截器使用
  • (二十一)Better Scroll
    • 21.1 基本使用
      • 安装
      • 使用
    • 21.2 参数
        • probeType
        • pullUpLoad
        • useTransition
    • 21.3 BScroll可滚动区域问题
      • 防抖函数
  • 商城项目1.0
    • Project setup
      • Compies and hot-reloads for development
      • Compiles and minifies for production
    • 目录结构
    • 步骤
    • tabControl的吸顶效果
        • 获取到tabControl的offsetTop
        • 监听滚动,动态改变TabControl的样式
      • 让Home保持原来状态
        • 让Home不要随意销毁掉
        • 让home中保持原来位置
    • FastClick点击事件优化
    • 图片懒加载 vue-lazyload库
      • 基本使用
      • 参数
    • CSS单位转化插件 px2vw
    • 部署


(十二)组件化高级

12.1 slot-插槽的基本使用

概念:

  • 组件的插槽是为了让我们封装的组件更具有扩展性
  • 使用者可以决定组件内部的一些内容到底展示什么

思想:

  • 抽取共性,保留不同
  • 我们在使用组件的时候有时候希望,在组件内部定制化内容,例如京东这样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8jb2Pe0-1612964660653)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izzIOxgP-1612964660654)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-2.png)]

  • 这两个都是导航栏,组件的思想是可以复用的,把这个导航栏看做一个组件。

  • 这个组件都可以分成三个部分,左边/中间/右边,如果可以分割组件,就可以定制化组件内容了。

  
  <div id="app">

    <cpn>cpn>
    <cpn>
      <span style="color:red;">这是插槽内容222span>
    cpn>
    <cpn>
      <i style="color:red;">这是插槽内容333i>
    cpn>
    <cpn>cpn>

  div>

  
  
  <template id="cpn">

    <div>
      <div>
        {
    {message}}
      div>
      
      <slot><button>buttonbutton>slot>
    div>
  template>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>

  <script>
    const cpn = {
      
      template: "#cpn",
      data() {
      
        return {
      
          message: "我是子组件"
        }
      },
    }
    const app = new Vue({
      
      el: "#app",
      data() {
      
        return {
      
          message: "我是父组件消息"
        }
      },
      components: {
      
        cpn
      },
    })
  script>

简单使用插槽,定义template时候使用slot

  
  <template id="cpn">
    <div>
      <div>
        {
    {message}}
      div>
      
      <slot><button>buttonbutton>slot>
    div>
  template>

插槽可以使用默认值,就是插槽的默认值。

<cpn>cpn>
<cpn><span style="color:red;">这是插槽内容222span>cpn>

使用插槽,这是插槽内容222替换插槽的默认值

上述代码结果如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMgM66ey-1612964660655)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-3.png)]

替换了两次插槽,两次未替换显示默认的button。

如果想实现组件分成三部分就可以使用三个来填充插槽了。

12.2 slot-具名插槽的使用

  • 具名插槽,就是可以让插槽按指定的顺序填充,而没有具名的插槽是按照你填充的顺序排列的,而具名插槽可以自定义排列。

  <div id="app">

    <cpn>
      <span>没具名span>
      <span slot="left">这是左边具名插槽span>
      
      <template v-slot:center>这是中间具名插槽template>
      
      <template #right>这是右边具名插槽template>


    cpn>


  div>

  
  
  <template id="cpn">

    <div>

      <slot name="left">左边slot>
      <slot name="center">中间slot>
      <slot name="right">右边slot>
      <slot>没有具名的插槽slot>
    div>
  template>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>

  <script>
    const cpn = {
      
      template: "#cpn",
      data() {
      
        return {
      
          message: "我是子组件"
        }
      },
    }
    const app = new Vue({
      
      el: "#app",
      data() {
      
        return {
      
          message: "我是父组件消息"
        }
      },
      components: {
      
        cpn
      },
    })
  script>

如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ea5pNbW-1612964660657)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.2-1.png)]

没有具名的插槽排在最后,因为在定义组件的时候,排在了最后,如果有多个按顺序排列。具名插槽按照自定义的顺序排列。

定义具名插槽,使用name属性,给插槽定义一个名字。

  
  
  <template id="cpn">
    <div>
      <slot name="left">左边slot>
      <slot name="center">中间slot>
      <slot name="right">右边slot>
      <slot>没有具名的插槽slot>
    div>
  template>

使用具名插槽,在自定义组件标签内使用slot="left",插入指定插槽

  
  <div id="app">
    <cpn>
      <span>没具名span>
      <span slot="left">这是左边具名插槽span>
      
      <template v-slot:center>这是中间具名插槽template>
      
      <template #right>这是右边具名插槽template>
    cpn>
  div>

注意:此处有是三种写法,获取指定插槽。

12.3 编译的作用域

  • 前面说过组件都有自己的作用域,自己组件的作用在自己组件内。

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>编译的作用域title>
head>
<body>
  
  <div id="app">
    
    <cpn v-show="isShow">cpn>
  div>

  
  <template id="cpn">
    <div>
      <h2>我是子组件h2>
      <p>哈哈哈p>
      
      <button v-show="isShow">button>
    div>
  template>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>

  <script>
    const cpn = {
      
      template: "#cpn",
      data() {
      
        return {
      
          isShwo:false
        }
      },
    }
    const app = new Vue({
      
      el: "#app",
      data() {
      
        return {
      
          message: "我是父组件消息",
          isShow:true
        }
      },
      components: {
      
        cpn
      },
    })
  script>
body>

html>

结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ol0Mc4rI-1612964660658)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.3-1.png)]

子组件使用的是子组件的isShow,子组件为false,所以button没显示,被隐藏。

12.4 作用域插槽案例

  • 目的:父组件替换插槽的标签,但是内容是由子组件来提供。

  • 同一组数据,不同展示方式

  • 当组件需要在多个父组件多个界面展示的时候,将内容放在子组件插槽中,父组件只需要告诉子组件使用什么方式展示界面。


<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>作用域插槽案例title>
head>

<body>


  
  <div id="app">
    <cpn>cpn>
    
    <cpn>
      
      <template slot-scope="slot">
        
        <span>{
    {slot.data.join(' - ')}}span>
      template>
    cpn>
    <cpn>
        
        <template slot-scope="slot">
          
          <span>{
    {slot.data.join(' * ')}}span>
        template>
      cpn>
  div>


  
  <template id="cpn">

    <div>
      <slot :data="pLanguage">
          <ul>
              <li v-for="(item, index) in pLanguage" :key="index">{
    {item}}li>
            ul>
      slot>

    div>
  template>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>

  <script>
    const cpn = {
      
      template: "#cpn",
      data() {
      
        return {
      
          isShwo:false,
          pLanguage:['JavaScript','Java','C++','C']
        }
      },
    }
    const app = new Vue({
      
      el: "#app",
      data() {
      
        return {
      
          isShow:true
        }
      },
      components: {
      
        cpn
      },
    })
  script>
body>

html>

组件中使用slot-scope="slot"**(2.6.0已经废弃)**给插槽属性命名,在通过slot调用绑定在插槽上的属性。也可以使用v-slot="slot"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWchwHTW-1612964660659)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.4-1.png)]

(十三)Vue实例的生命周期

13.1 生命周期图

​ Vue实例的生命周期中有多个状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdyfidhJ-1612964660660)(C:\Users\86136\Desktop\repository\DailyCode\source_BASIS\Stage10.0-Vue\VueLearnNotes-master\VueLearnNotes-master\13-Vue实例的生命周期\images\lifecycle1.png)]

测试代码


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue实例的生命周期title>

    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
head>
<body>
    <div id="app">
        <h1>测试生命周期h1>
        <div>{
    {msg}}div>
        <hr>
        <h3>测试beforeUpdate和update两个钩子函数h3>
        <button @click="handlerUpdate">更新数据button>
    div>
    <script>
        var app = new Vue({
      
            el:"#app",
            data:{
      
                msg:"12345"
            },
            methods: {
      
                handlerUpdate:function(){
      
                    this.msg=this.msg.split("").reverse().join("");
                },
            },//按照示意图依次调用
            beforeCreate:function(){
      
                console.log("调用了beforeCreate钩子函数");
            },
            created:function () {
      
                console.log("调用了created钩子函数");
            },
            beforeMount: function () {
      
                console.log('调用了beforeMount钩子函数')
            },
            mounted: function () {
      
                console.log('调用了mounted钩子函数')
            },
            beforeUpdate: function () {
      
                console.log("调用了beforeUpdate钩子函数")
            },
            updated: function () {
      
                console.log("调用了updated钩子函数");
            },
            beforeDestroy: function () {
      
                console.log("调用了beforeDestroy钩子函数")
            },
            destroyed: function () {
      
                console.log("调用了destroyed钩子函数");
            },
        });
    script>
body>
html>

如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rtw1k0lD-1612964660661)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\1.png)]

初始化页面依次调用了:

  1. 调用了beforeCreate钩子函数
  2. 调用了created钩子函数
  3. 调用了beforeMount钩子函数
  4. 调用了mounted钩子函数

点击更新数据后:

12345变成了54321,此时调用了:

  1. 调用了beforeUpdate钩子函数
  2. 调用了updated钩子函数

打开F12控制台
直接输入app.$destroy()主动销毁Vue实例调用:

  1. 调用了beforeDestroy钩子函数
  2. 调用了destroyed钩子函数

13.2 再探究

13.2.1 beforeCreate之前

初始化钩子函数和生命周期

13.2.2 beforeCreate和created钩子函数间的生命周期

在beforeCreate和created之间,进行数据观测(data observer) ,也就是在这个时候开始监控data中的数据变化了,同时初始化事件。
生命周期展示图

13.2.3 created钩子函数和beforeMount间的生命周期

对于created钩子函数和beforeMount有判断:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pysh2brU-1612964660665)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\2.png)]

13.2.3.1el选项对生命周期影响

  1. 有el选项
new Vue({
    el: '#app',
    beforeCreate: function () {
        console.log('调用了beforeCreat钩子函数')
    },
    created: function () {
        console.log('调用了created钩子函数')
    },
    beforeMount: function () {
        console.log('调用了beforeMount钩子函数')
    },
    mounted: function () {
        console.log('调用了mounted钩子函数')
    }
})

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g88IvsmO-1612964660667)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.png)]

  1. 无el选项
new Vue({
    beforeCreate: function () {
        console.log('调用了beforeCreat钩子函数')
    },
    created: function () {
        console.log('调用了created钩子函数')
    },
    beforeMount: function () {
        console.log('调用了beforeMount钩子函数')
    },
    mounted: function () {
        console.log('调用了mounted钩子函数')
    }
})

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TEokYau-1612964660669)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.png)]

证明没有el选项,则停止编译,也意味着暂时停止了生命周期。生命周期到created钩子函数就结束了。而当我们不加el选项,但是手动执行vm.$mount(el)方法的话,也能够使暂停的生命周期进行下去,例如:

var app = new Vue({
    beforeCreate: function () {
        console.log('调用了beforeCreat钩子函数')
    },
    created: function () {
        console.log('调用了created钩子函数')
    },
    beforeMount: function () {
        console.log('调用了beforeMount钩子函数')
    },
    mounted: function () {
        console.log('调用了mounted钩子函数')
    }
})
app.$mount('#app')

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TrT6qNzn-1612964660671)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.png)]

13.2.3.2 template

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwfebznN-1612964660673)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\5.png)]

同时使用templateHTML,查看优先级:

    

测试template和HTML的优先级

HTML优先

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xGmcwHkY-1612964660674)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\6.png)]

结论

  1. 如果Vue实例对象中有template参数选项,则将其作为模板编译成render函数
  2. 如果没有template参数选项,则将外部的HTML作为模板编译(template),也就是说,template参数选项的优先级要比外部的HTML高
  3. 如果1,2条件都不具备,则报错

注意

  1. Vue需要通过el去找对应的template,Vue实例通过el的参数,首先找自己有没有template,如果没有再去找外部的html,找到后将其编译成render函数。
  2. 也可以直接调用render选项,优先级:render函数选项 > template参数 > 外部HTML

new Vue({
    el: '#app',
    render (createElement) {
        return (....)
    }
})

13.2.4 beforeMount和mounted钩子函数间的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8sx37we-1612964660675)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\7.png)]

beforeMount

载入前(完成了data和el数据初始化),但是页面中的内容还是vue中的占位符,data中的message信息没有被挂在到Dom节点中,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取。

Mount

载入后html已经渲染(ajax请求可以放在这个函数中),把vue实例中的data里的message挂载到DOM节点中去

这里两个钩子函数间是载入数据。

13.2.5 beforeUpdate钩子函数和updated钩子函数间的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zqu4DhzU-1612964660676)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\8.png)]
在Vue中,修改数据会导致重新渲染,依次调用beforeUpdate钩子函数和updated钩子函数

如果待修改的数据没有载入模板中,不会调用这里两个钩子函数

var app = new Vue({
    el: '#app',
    data: {
        msg: 1
    },
    template: '

', beforeUpdate: function () { console.log('调用了beforeUpdate钩子函数') }, updated: function () { console.log('调用了updated钩子函数') } }) app.msg = 2

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpI4PVU3-1612964660678)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\9.png)]
如果绑定了数据,会调用两个钩子函数:

测试有数据绑定修改数据,钩子函数调用情况

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2GK8LgEH-1612964660678)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\10.png)]

注意只有写入模板的数据才会被追踪

13.2.6 beforeDestroy和destroyed钩子函数间的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4h8rCwr-1612964660679)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.png)]

13.2.6.1 beforeDestroy

销毁前执行($destroy方法被调用的时候就会执行),一般在这里善后:清除计时器、清除非指令绑定的事件等等…’)

13.2.6.2 destroyed

销毁后 (Dom元素存在,只是不再受vue控制),卸载watcher,事件监听,子组件

总结

  • beforecreate : 可以在这加个loading事件
  • created :在这结束loading,还做一些初始数据的获取,实现函数自-执行
  • mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
  • beforeDestroy: 你确认删除XX吗?
  • destroyed :当前组件已被删除,清空相关内容

(十四)前端模块化

14.1 为什么要有模块化

  • 随着前端项目越来越大,团队人数越来越多,多人协调开发一个项目成为常态。例如现在小明和小张共同开发一个项目,小明定义一个aaa.js,小张定义了一个bbb.js。

aaa.js

//小明开发
var name = '小明'
var age = 22

function sum(num1, num2) {
     
  return num1 + num2
}
var flag = true
if (flag) {
     
  console.log(sum(10, 20));
}
  • 此时小明的sum是没有问题的。

bbb.js

//小红
var name = "小红"
var flag = false
  • 此时小明和小红各自用各自的flag你变量没问题。

但是此时小明又创建了一个mmm.js

//小明
if(flag){
     
  console.log("flag是true")
}
  • 在index.html页面导入这些js文件
  
  
  
  • 此时小明知道自己在aaa.js中定义的flagtrue,认为打印没有问题,但是不知道小红的bbb.js中也定义了flagtrue,所以mmm.js文件并没有打印出“flag是true”。

这就是全局变量同名问题。

14.2 使用导出全局变量模块解决全局变量同名问题

aaa.js

//模块对象
var moduleA = (function (param) {
     
  //导出对象
  var obj = {
     }
  var name = '小明'
  var age = 22

  function sum(num1, num2) {
     
    return num1 + num2
  }
  var flag = true
  if (flag) {
     
    console.log(sum(10, 20))
  }
  obj.flag=false
  return obj
})()

mmm.js

//小明
//使用全局变量moduleA
if(moduleA.flag){
     
  console.log("flag是true")
}

这样直接使用aaa.js导出的moduleA变量获取小明自己定义的flag

14.3 CommonJS的模块化实现

​ CommonJS需要nodeJS的依支持。

aaa.js

//CommonJS需要nodeJS支持
var name = '小明'
var age = 22

function sum(num1, num2) {
     
  return num1 + num2
}
var flag = true
if (flag) {
     
  console.log(sum(10, 20))
}

// module.exports = {
     
//   flag : flag,
//   sum : sum
// }
//导出对象
module.exports = {
     
  flag,
  sum
}

使用module.exports = {}导出需要的对象。

mmm.js

//导入对象,nodejs语法,需要node支持,从aaa.js取出对象
var {
     flag,sum} = require("./aaa")

console.log(sum(10,20));

if(flag){
     
  console.log("flag is true");
}

使用 var {flag,sum} = require("./aaa")获取已经导出的对象中自己所需要的对象。

ES6的模块化实现

​ 如何实现模块化,在html中需要使用type='module'属性。

  <script src="aaa.js" type="module">script>
  <script src="bbb.js" type="module">script>
  <script src="mmm.js" type="module">script>

此时表示aaa.js是一个单独的模块,此模块是有作用域的。如果要使用aaa.js内的变量,需要在aaa.js中先导出变量,再在需要使用的地方导出变量。

14.4.1 直接导出

export let name = '小明'

使用

import {
     name} from './aaa.js'
console.log(name)

./aaa.js表示aaa.js和mmm.js在同级目录。

如图打印结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ALP9vlME-1612964660680)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-1.png)]

14.4.2 统一导出

var age = 22
function sum(num1, num2) {
     
  return num1 + num2
}
var flag = true
if (flag) {
     
  console.log(sum(10, 20))
}
//2.最后统一导出
export {
     
  flag,sum,age
}

使用import {name,flag,sum} from './aaa.js'导入多个变量

import {
     name,flag,sum} from './aaa.js'

console.log(name)

if(flag){
     
  console.log("小明是天才");
}

console.log(sum(20,30));

使用{}将需要的变量放置进去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UxKMlUaq-1612964660681)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-2.png)]

14.4.3 导出函数/类

在aaa.js中添加

//3.导出函数/类
export function say(value) {
     
  console.log(value);
}
export class Person{
     
  run(){
     
    console.log("奔跑");
  }
}

在mmm.js中添加

import {
     name,flag,sum,say,Person} from './aaa.js'

console.log(name)

if(flag){
     
  console.log("小明是天才");
}

console.log(sum(20,30));

say('hello')
const p = new Person();
p.run();

如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MUJtzvqf-1612964660682)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-3.png)]

14.4.4 默认导入 export default

导出

  • 一个模块只允许有一个default
export default {
     
  flag,sum,age
}

导入

//4.默认导入 export default
import aaa from './aaa.js'
console.log(aaa.sum(10,110));

注意:使用默认导出会将所有需要导出的变量打包成一个对象,此时导出一个对象,此时我在mmm.js中导入变量时候命名为aaa,如果要调用变量需要使用aaa.变量。

14.4.5 统一全部导入

使用import * as aaa from './aaa.js'统一全部导入

// 5.统一全部导入
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.name);

(十五)webpack

15.1 webpack起步

15.1.1 什么是webpack

webpack是一个JavaScript应用的静态模块打包工具。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTMlmYuI-1612964660683)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-1.png)]

从这句话中有两个要点,模块打包需要关注。grunt/gulp都可以打包,那有什么区别。

模块化

webpack可以支持前端模块化的一些方案,例如AMD、CMD、CommonJS、ES6。可以处理模块之间的依赖关系。不仅仅是js文件可以模块化,图片、css、json文件等等都可以模块化。

打包

webpack可以将模块资源打包成一个或者多个包,并且在打包过程中可以处理资源,例如压缩图片,将scss转成css,ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。grunt/gulp也可以打包。

和grunt/glup的对比

  • grunt/glup的核心是Task
    • 我们可以配置一系列的task,并且定义task要处理的事务(例如ES6/TS转化,图片压缩,scss转css)
    • 之后可以让grunt/glup来执行依次这些任务,让整个流程自动化
    • 所以grunt/glup也被称为前端自动化任务管理工具
  • 看一个gulp例子
    • task将src下的js文件转化为ES5语法
    • 并输入到dist文件夹中
const gulp = require('gulp')
    const babel = require('gulp-babel')
    gulp.task('js'()=>
      gulp.src('src/*.js')
        .pipe(babel({
          presets:['es2015']
        }))
        .pipe(gulp.dest('dist'))
    );
  • 什么时候使用grunt/gulp呢?
    • 如果工程依赖简单,甚至没有模块化
    • 只需要进行简单的合并/压缩
    • 如果模块复杂,相互依赖性强,我们需要使用webpack
  • grunt/glup和webpack区别
    • grunt/glup更加强调的是前端自动化流程,模块化不是其核心
    • webpack加强模块化开发管理,而文件压缩/合并/预处理等功能,是附带功能

webpack就是前端模块化打包工具

15.1.2 webpack的安装

  1. webpack依赖node环境。
  2. node环境依赖众多包,所以需要npm,npm(node packages manager)node包管理工具
  3. nvm是node管理工具可以自由切换node环境版本

全局安装webpack

npm install webpack -g
//指定版本安装
npm install [email protected] -g

由于vue-cli2基于webpack3.6.0
如果要用vue-cli2的可以使用npm install [email protected] -g

局部安装

npm install webpack --save-dev
  • 在终端执行webpack命令,使用的是全局安装。

  • 当在package.json中定义了scripts时,其中包括了webpack命令,那么使用的是局部webpack

15.1.3 起步

新建一个文件夹,新建如下结构的目录:

目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jcq1zAYs-1612964660685)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-2.png)]

如图所示在src文件夹(源码文件夹),dist(要发布的文件,已经处理过的)。

1.新建入口js文件main.jsmathUtils.jsmain.js依赖mathUtils.js

mathUtils

//1.新建mathUtils.js,用CommonJs规范导出
function add(num1,num2) {
     
  return num1+num2
}
function mul(num1,num2) {
     
  return num1*num2
}
module.exports = {
     
  add,mul
}

main.js

//2.新建入口js文件main.js 导入mathUtil.js文件,并调用
const {
     add,mul} = require("./mathUtils.js")

console.log(add(10,20))
console.log(mul(10,10))

2.使用webpack命令打包js文件

注意:webpack3使用webpack ./src/main.js ./dist/bundle.js

webpack4,webpack打包在01-webpack的起步目录下打开终端 webpack ./scr/main.js -o ./dist/bundle.js

我全局安装的是[email protected],所以在根路径执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAe82Pd0-1612964660687)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-3.png)]

如图显示打包成功,查看dist文件夹下自动生成了一个bundle.js

bundle.js

//2.新建入口js文件main.js 导入mathUtil.js文件,并调用
const {
     add,mul} = __webpack_require__(1)

console.log(add(10,20))
console.log(mul(10,10))

/***/ }),
/* 1 */
/***/ (function(module, exports) {
     

//1.新建mathUtils.js,用CommonJs规范导出
function add(num1,num2) {
     
  return num1+num2
}
function mul(num1,num2) {
     
  return num1*num2
}
module.exports = {
     
  add,mul
}

内容很多,其中包含mathUtils.js和main.js 内容,打包成功。

3.新建一个index.html文件,导入bundle.js


<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>webpack入门title>
head>
<body>
  
  
  <script src="./dist/bundle.js">script>
body>
html>

如图测试,打印成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vUOf9n8-1612964660688)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-4.png)]

4.新建一个info.js使用ES6的语法导出

info.js

//es6语法导出
export default {
     
  name:'zzz',
  age:24,
}

main.js导入info.js

//使用es6语法导入
import info from './info.js'

console.log(info.name)
console.log(info.age)

再次使用webpack ./src/main.js ./dist/bundle.js,重新打包

5.打开index.html测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfWJwXmt-1612964660689)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-5.png)]

总结

webpack可以帮我们打包js文件,只要指定入口文件(main.js)和输出的文件(bundle.js),不管是es6的模块化还是CommonJs的模块化,webpack都可以帮我们打包,还可以帮我们处理模块之间的依赖。

15.2 webpack的配置

15.2.1 基本配置

如果每次都用webpack命令自己写入口文件和出口文件会很麻烦,此时我们可以使用webpack的配置。

准备工作:复制01-webpack的起步文件夹并粘贴在同级目录,改名为02-webpack的配置

webpack.config.js

1.在根目录(02-webpack的配置)下新建一个webpack.config.js

//1.导入node的path包获取绝对路径,需要使用npm init初始化node包
const path = require('path')

//2.配置webpack的入口和出口
module.exports = {
     
  entry: './src/main.js',//入口文件
  output:{
     
    path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
    filename: 'bundle.js'//打包后的文件名
  }
}

2.在02-webpack的配置根目录执行npm init初始化node包,因为配置文件中用到了node的path包

npm init 

初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyX8dBmh-1612964660690)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-6.png)]

3.使用webpack打包

webkpack

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwzaOVW4-1612964660691)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-7.png)]

这样入口和出口的配置已经配置完成了,只需要使用webpack命令就行了。

4.使用自定义脚本(script)启动

一般来是我们使用的是

npm run dev//开发环境
npm run build//生产环境

package.json

  • 在package.json中的script中加上映射:
"build": "webpack"
  • 使用npm run build相当于webpack命令
npm run build

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT6yZJh4-1612964660692)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-8.png)]

15.2.2 全局安装和局部安装

  • webpack有全局安装和局部安装。

  • 使用npm run build执行webpack会先从本地查找是否有webpack,如果没有会使用全局的。

  • 此时本地需要安装webapck

npm install [email protected] --save-dev
  • package.json中自动加上开发时的依赖devDependencies
    • 区别运行时依赖:dependencies

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EtuUOXQG-1612964660693)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-9.png)]

再次使用npm run build,使用的是本地webpack版本。

15.3 webpack的loader

什么是loader

  • loader是webpack中一个非常核心的概念。

  • webpack可以将js、图片、css处理打包,但是对于webpack本身是不能处理css、图片、ES6转ES5等。

  • 此时就需要webpack的扩展,使用对应的loader就可以。

loader使用

  • 步骤

    • 步骤一:通过npm安装需要使用的loader

    • 步骤二:通过webpack.config.js中的modules关键字下进行配置

  • 大部分loader可以在webpack的官网找到对应的配置。

CSS文件处理

1.将除了入口文件(main.js)所有js文件放在js文件夹,新建一个css文件夹,新建一个normal.css文件

normal.css

body{
     
  background-color: red;
}

2.main.js导入依赖

//4.依赖css文件
require('./css/normal.css')

此时如果直接进行打包npm run build

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fipMVkId-1612964660694)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-10.png)]

提示信息很清楚,打包到css文件时报错,提示我们可能需要一个loader来处理css文件。

3.安装css-loader

npm install --save-dev css-loader

4.使用css-loader

module.exports = {
     
  module: {
     
    rules: [
      {
     
        test: /\.css$/,//正则表达式匹配css文件
        //css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
        use: [{
     
          loader: 'css-loader'
        }]//使用loader
      }
    ]
  }
}

执行npm run build,提示打包成功,但是背景色并没有变红色,是因为css-loader只负责加载css文件,不负责解析,如果要将样式解析到dom元素中需要使用style-loader。

5.安装使用style-loader

npm install --save-dev style-loader
  module: {
     
    rules: [
      {
     
        test: /\.css$/,//正则表达式匹配css文件
        //css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
        use: [{
     
          loader: 'style-loader'
        }, {
     
          loader: 'css-loader'
        }]//使用loader
      }
    ]
  }

注意:webpack使用多个loader是从右往左解析的,所以需要将css-loader放在style-loader右边,先加载后解析。

此时样式成加载解析到DOM元素上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wqRubL8O-1612964660695)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-11.png)]

less文件处理

1.在css文件夹中新增一个less文件

special.less

@fontSize:50px;//定义变量字体大小
@fontColor:orange;//定义变量字体颜色
body{
  font-size: @fontSize;
  color: @fontColor;
}

2.main.js中导入less文件模块

//5.依赖less文件
require('./css/special.less')
//6.向页面写入一些内容
document.writeln("hello,zzzz!")

3.安装使用less-loader

npm install --save-dev less-loader less

webpack.config.js中使用less-loader

  module: {
     
    rules: [
      {
     
        test: /\.less$/,//正则表达式匹配css文件
        //css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
        use: [{
     
          loader: 'style-loader'
        }, {
     
          loader: 'css-loader'
        }, {
     
          loader: 'less-loader'//less文件loader
        }]//使用loader
      }
    ]
  }

4.执行npm run build

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4z0u9Od-1612964660696)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-12.png)]

less文件生效了,字体是orange,大小为50px。

图片文件的处理

准备工作,准备两张图片,图片大小为一张8KB以下(实际大小为5KB,名称为small.jpg),一张大于8KB(实际大小为10KB,名称为big.jpg),新建一个img文件夹将两张图片放入。

1.修改normal.css样式,先使用小图片作为背景

body{
  /* background-color: red; */
  background: url("../img/small.jpg");
}
  • 此时如果直接使用npm run build 直接打包会报错,因为css文件中引用了图片url,此时需要使用url-loader

2.安装使用url-loader处理图片

  • url-loader像 file loader 一样工作,但如果文件小于限制,可以返回 data URL 。
npm install --save-dev url-loader
  • 配置
{
     
        test: /\.(png|jpg|gif)$/,//匹配png/jpg/gif格式图片
        use: [
          {
     
            loader: 'url-loader',
            options: {
     
              limit: 8192//图片小于8KB时候将图片转成base64字符串,大于8KB需要使用file-loader
            }
          }
        ]
      }

limit属性

  • 图片小于8KB时候将图片转成base64字符串,直接响应给浏览器(字符串)
  • 大于8KB需要使用file-loader,被打包后放在dist一起发给浏览器(图片)

3.打包

  • 使用npm run build打包后,打开index.html。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbfiOPVE-1612964660697)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-13.png)]

小于limit大小的图片地址被编译成base64格式的字符串。

  • 此时修改css文件,使用big.jpg做背景。
body{
     
  /* background-color: red; */
  /* background: url("../img/small.jpg"); */
  background: url("../img/big.jpg");
}
  • 再次打包,报错,提示未找到file-loader模块。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkhApmlJ-1612964660698)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-14.png)]

因为大于limit的图片需要file-loader来打包。

4.安装使用file-loader处理图片

npm install --save-dev file-loader
  • 不需要配置,因为url-loader超过limit的图片会直接使用file-loader。

再次打包,没有报错,打包成功,但是图片未显示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edtCmIhv-1612964660699)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-15.png)]

1.当加载的图片大小小于limit,使用base64将图片编译成字符串

2.当加载的图片大小大于limit,使用file-loader模块直接将big.jpg直接打包到dist文件夹,文件名会使用hash值防止重复。

3.此时由于文件路径不对所以导致没有加载到图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpq1xt6F-1612964660700)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-16.png)]

5.如何使用file-loader,指定路径

修改output属性 publicPath: ‘dist/’

  output:{
     
    path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
    filename: 'bundle.js',//打包后的文件名
    publicPath: 'dist/'
  },

此时打包,图片正常显示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpjhJuI7-1612964660701)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-17.png)]

注意:一般来说,index.html最终也会打包到dist文件夹下,所以,并不需要配置publicPath,如何打包index.html请看webpack处理.vue文件。

file-loader打包后,使用hash值做文件名太长,此时可以使用options的一些配置。

options: {
     
              limit: 8192,
              name: 'img/[name].[hash:8].[ext]'
            }

修改options,加上name属性,其中img表示文件父目录,[name]表示文件名(原文件名),[hash:8]表示将hash截取8位,[ext]表示后缀

再次打包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9E3vJed-1612964660702)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-18.png)]

ES6语法处理

  • webpack打包时候ES6语法没有打包成ES5语法,如果需要将ES6打包成ES5语法,那么就需要使用babel。直接使用babel对应的loader就可以了。

安装

npm install --save-dev babel-loader@7 babel-core babel-preset-es2015

配置

      {
     
        test: /\.js$/,
        //排除node模块的js和bower的js
        exclude: /(node_modules|bower_components)/,
        use: {
     
          loader: 'babel-loader',
          options: {
     
            // presets: ['@babel/preset-env']
            //这里直接使用指定
            presets: ['es2015']
          }
        }
      }

1.如果要使用@babel/preset-env这里需要在根目录新建一个babel的文件

2.exclude排除不需要打包的文件

15.4 webpack的vue

简单安装使用vue

  • 如果需要使用vue,必须使用npm先安装vue。
npm install vue --save	
  • 使用vue简单开发。

准备工作

  • 复制03-webpack的loader到同级目录,改名为04-webpack的vue,并在04-webpack的vue根目录执行npm install vue --save,下载安装vue。

1.在入口文件main.js导入已安装的vue,并在index.html声明要挂载的div。在main.js加入以下代码。

//6.使用vue开发
import Vue from 'vue'

const app = new Vue({
     
  el: "#app",
  data: {
     
    message: "hello webpack and vue"
  }
})
  • 修改index.html代码,添加
  <div id="app">
    <h2>{
    {message}}h2>
  div>

2.再次打包npm run build后打开index.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1hiivl6-1612964660703)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-19.png)]

  • 发现message并没有正确显示,打开console发现vue报错。错误提示我们,正在使用runtime-only构建,不能将template模板编译。

1.runtime-only模式,代码中不可以有任何template,因为无法解析。

2.runtime-complier模式,代码中可以有template,因为complier可以用于编译template。

  • 在webpack中配置,设置指定使用runtime-complier模式。

webpack.config.js

  resolve: {
     
    // alias:别名
    alias: {
     
        //指定vue使用vue.esm.js
      'vue$':'vue/dist/vue.esm.js'
    }
  }

3.重新打包,显示正确

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMUcdDE0-1612964660704)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-20.png)]

如何分步抽取实现vue模块

创建vue的template和el关系

  • el表示挂载DOM的挂载点

  • template里面的html将替换挂载点

  • 一般我们使用vue会开发单页面富应用(single page application),只有一个index.html,而且index.html都是简单结构。

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>webpack入门title>
head>
<body>
  <div id="app">
  div>
  <script src="./dist/bundle.js">script>
body>
html>

1.第一次抽取,使用template替换

修改mian.js的vue相关代码

//6.使用vue开发
import Vue from 'vue'

new Vue({
     
  el: "#app",
  template:`
  

{ {message}}

{ {name}}

`
, data: { message: "hello webpack and vue", name: 'zzzz' }, methods: { btnClick(){ console.log("按钮被点击了") } }, })
  • 使用template模板替换挂载的id为app的div元素,此时不需要修改html代码了,只需要写template。

  • 再次打包,显示成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G24HSsrt-1612964660705)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-21.png)]

2.第二次抽取,使用组件化思想替换template

  • 考虑第一次抽取,写在template中,main.js的vue代码太冗余。

修改main.js的代码

//1.定义一个组件
const App = {
     
  template: `
  

{ {message}}

{ {name}}

`
, data() { return { message: "hello webpack and vue", name: 'zzzz' } }, methods: { btnClick(){ console.log("按钮被点击了") } }, }

修改main.js,vue实例中注册组件,并使用组件

new Vue({
     
  el: "#app",
  //使用组件
  template: '',
  components: {
     
    //注册局部组件
    App
  }
})

再次使用npm run build打包,打包成功,显示和使用template替换div一样。

3.第三次抽取组件对象,封装到新的js文件,并使用模块化导入main.js

此处我的vue-loader是15.7.2。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgREDQaZ-1612964660706)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-24.png)]

将其修改为13.0.0

"vue-loader": "^13.0.0"

重新安装版本

npm install

再次打包,打包成功,样式生效了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-01w403rX-1612964660707)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-25.png)]

6.组件化开发

我们使用app.vue分离了模板、行为、样式,但是不可能所有的模板和样式都在一个vue文件内,所以要用组件化。

在vue文件夹下新建一个Cpn.vue文件

Cpn.vue组件






将Cpn.vue组件导入到App.vue






再次打包,打开index.html,cpn组件的内容显示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2XZOibJw-1612964660708)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-26.png)]

基于此,一个vue文件可以依赖导入很多vue文件,组成一个单页面富应用。

如果你在使用ES6语法导入模块时候想要简写的时候,例如这样省略.vue后缀

import Cpn from './Cpn'

可以在webpack.config.js中配置:

  resolve: {
     
    //导入模块简写省略指定后缀
    extensions: ['.js', '.css', '.vue'],
    // alias:别名
    alias: {
     
      //指定vue使用vue.esm.js
      'vue$':'vue/dist/vue.esm.js'
    }
  }

15.5 webpack的plugin

plugin插件用于扩展webpack的功能的扩展,例如打包时候优化,文件压缩。

  • loader和plugin的区别

    • loader主要用于转化某些类型的模块,是一个转化器

    • plugin主要是对webpack的本身的扩展,是一个扩展器

  • plugin的使用过程

    • 步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要在安装)

    • 步骤二:在webpack.config.js中的plugins中配置插件。

准备工作

复制04-webpack的vue到同级目录,并改名为05-webpack的plugin

添加版权的Plugin

  • BannerPlugin插件是属于webpack自带的插件可以添加版权信息。

  • 自带的插件无需安装,直接配置。

  • 先获取webpack的对象,在配置BannerPlugin插件。

//获取webpack
const webpack = require('webpack')
//2.配置plugins
module.exports = {
     
    ...
    plugins:[
        new webpack.BannerPlugin('最终解释权归zz所有')
      ]
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yENSCbEL-1612964660708)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-27.png)]

  • 打包后,查看bundle.js,结果如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9R2lZCmU-1612964660709)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-28.png)]

  • 多了一行我们自定义的版权声明注释。

打包html的plugin

  • 之前我们的index.html文件都是存放在根目录下的。

  • 在正式发布项目的时候发布的是dist文件夹的内容,但是dist文件夹是没有index.html文件的,那么打包就没有意义了。

  • 所以我们需要将index.html也打包到dist文件夹中,这就需要使用**HtmlWebpackPlugin**插件了。

HtmlWebpackPlugin

自动生成一个index.html文件(指定模板)

将打包的js文件,自动同script标签插入到body中

  • 首先需要安装**HtmlWebpackPlugin**插件
npm install html-webpack-plugin --save-dev	
  • 使用插件,修改webpack.config.js文件中的plugins部分
//获取htmlWebpackPlugin对象
const htmlWbepackPlugin = require('html-webpack-plugin')
//2.配置plugins
module.exports = {
     
    ...
    plugins:[
        new webpack.BannerPlugin('最终解释权归zz所有'),
        new htmlWbepackPlugin({
     
          template: 'index.html'
        })
      ]
}

1.template表示根据哪个模板来生成index.html

2.需要删除output中添加的publicPath属性,否则插入的script标签的src可能有误

  • 再次打包,打开dist文件夹,多了一个index.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWXarUgw-1612964660710)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-29.png)]

  • 自动加入了script引入了bundle.js。

压缩打包代码插件

  • uglifyjs-webpack-plugin是第三方插件,如果是vuecli2需要指定版本1.1.1。

  • 安装:

npm install [email protected] --save-dev
  • 配置plugin
//获取uglifyjs-webpack-plugin对象
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
//2.配置plugins
module.exports = {
     
    ...
    plugins:[
        new webpack.BannerPlugin('最终解释权归zz所有'),
        new htmlWbepackPlugin({
     
          template: 'index.html'
        }),
    	new uglifyjsWebpackPlugin()
      ]
}
  • 打包过后,打开bundle.js,发现已经压缩了,此时版权声明被删除了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zblEW31p-1612964660711)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-30.png)]

webpack高版本自带了压缩插件。

15.6 webpack搭建本地服务器

  • webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用了express框架,可以实现热启动。

准备工作复制05-webpack的plugin文件夹到同级目录,并改名为06-webpack搭建本地服务器。

  • 不过这是一个单独的模块,在webpack中使用之前需要先安装:
npm install --save-dev [email protected]
  • devServe也是webpack中一个选项,选项本省可以设置一些属性:

    • contentBase:为哪个文件夹提供本地服务,默认是根文件夹,这里我们需要改成./dist
    • port:端口号
    • inline:页面实时刷新
    • historyApiFallback:在SPA(单页面富应用)页面中,依赖HTML5的history模式
  • 修改webpack.config.js的文件配置

//2.配置webpack的入口和出口
module.exports = {
     
 ...
  devServer: {
     
    contentBase: './dist',//服务的文件夹
    port: 4000,
    inline: true//是否实时刷新
  }

}

  • 配置package.json的script:
    • 直接输入指令 “webpack-dev-server --open” 会在全局找
"dev": "webpack-dev-server --open"

–open表示直接打开浏览器

  • 启动服务器
npm run dev
  • 启动成功,自动打开浏览器,发现在本地指定端口启动了,此时你修改src文件内容,会热修改。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hs1pth8-1612964660712)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-31.png)]

1.服务器启动在内存中。

2.开发调试时候最好不要使用压缩js文件的插件,不易调试。

15.7 webpack的配置文件分离

  • webpack.config.js文件中有些是开发时候需要配置,有些事生产环境发布编译需要的配置,比如搭建本地服务器的devServer配置就是开发时配置,接下来我们分析如何分离配置文件。

准备工作:复制06-webpack搭建本地服务器文件夹到同级目录,并改名为07-webpack的配置文件分离。

  • 在根目录下新建一个build的文件夹,新建配置文件。

base.config.js(公共的配置)

//1.导入node的path包获取绝对路径,需要使用npm init初始化node包
const path = require('path')
//获取webpack
const webpack = require('webpack')
//获取htmlWebpackPlugin对象
const htmlWbepackPlugin = require('html-webpack-plugin')

//2.配置webpack的入口和出口
module.exports = {
     
  entry: './src/main.js',//入口文件
  output:{
     
    path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
    filename: 'bundle.js',//打包后的文件名
    // publicPath: 'dist/'
  },
  module: {
     
    rules: [
      {
     
        test: /\.css$/,//正则表达式匹配css文件
        //css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
        use: [{
     
          loader: 'style-loader'
        }, {
     
          loader: 'css-loader'
        }]//使用loader
      },
      {
     
        test: /\.less$/,//正则表达式匹配css文件
        //css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
        use: [{
     
          loader: 'style-loader'
        }, {
     
          loader: 'css-loader'
        }, {
     
          loader: 'less-loader'//less文件loader
        }]//使用loader
      },
      {
     
        test: /\.(png|jpg|gif)$/,//匹配png/jpg/gif格式图片
        use: [
          {
     
            loader: 'url-loader',
            options: {
     
              limit: 8192,//图片小于8KB时候将图片转成base64字符串,大于8KB需要使用file-loader
              name: 'img/[name].[hash:8].[ext]'//img表示文件父目录,[name]表示文件名,[hash:8]表示将hash截取8位[ext]表示后缀
            }
          }
        ]
      },
      {
     
        test: /\.js$/,
        //排除node模块的js和bower的js
        exclude: /(node_modules|bower_components)/,
        use: {
     
          loader: 'babel-loader',
          options: {
     
            //如果要使用@babel/preset-env这里需要在根目录新建一个babel的文件
            // presets: ['@babel/preset-env']
            //这里直接使用指定
            presets: ['es2015']
          }
        }
      },
      {
     
        test: /\.vue$/,//正则匹配.vue文件
        use: {
     
          loader: 'vue-loader'
        }
      }
    ]
  },
  resolve: {
     
    // alias:别名
    alias: {
     
      //指定vue使用vue.esm.js
      'vue$':'vue/dist/vue.esm.js'
    }
  },
  plugins:[
    new webpack.BannerPlugin('最终解释权归zz所有'),
    new htmlWbepackPlugin({
     
      template: 'index.html'
    })
  ]
}

dev.config.js(开发时候需要的配置)

module.exports = {
     
  devServer: {
     
    contentBase: './dist',//服务的文件夹
    port: 4000,
    inline: true//是否实时刷新
  }
}

prod.config.js(构建发布时候需要的配置)

const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
     
  plugins:[
    new uglifyjsWebpackPlugin()
  ]
}
  • 此时我们将webpack.config.js文件分成了三个部分,公共部分、开发部分、构建发布的部分。

1.如果此时是dev环境,我们只需要使用base.config.js+dev.config.js的内容

2.如果此时是生产发布构建的环境,我们只需要使用base.config.js+prod.config.js的内容

  • 要将两个文件内容合并需要使用webpack-merge插件,安装webpack-merge
npm isntall webpack-merge --save-dev
  • 合并内容都是将base.config.js的内容合并到dev或者prod的文件中,修改dev.config.jsprod.config.js文件。

修改dev.config.js

//导入webpack-merge对象
const webpackMerge = require('webpack-merge')
//导入base.config.js
const baseConfig = require('./base.config')

//使用webpackMerge将baseConfig和dev.config的内容合并
module.exports = webpackMerge(baseConfig, {
     
  devServer: {
     
    contentBase: './dist',//服务的文件夹
    port: 4000,
    inline: true//是否实时刷新
  }

})

修改prod.config.js

const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
//导入webpack-merge对象
const webpackMerge = require('webpack-merge')
//导入base.config.js
const baseConfig = require('./base.config')

//使用webpackMerge将baseConfig和prod.config的内容合并
module.exports = webpackMerge(baseConfig, {
     
  plugins:[
    new uglifyjsWebpackPlugin()
  ]
})
  • 此时我们使用三个文件构成了配置文件,此时在不同环境使用不同的配置文件,但是webpack不知道我们新配置文件,此时我们需要在package.json中的script指定要使用的配置文件。
"scripts": {
     
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config ./build/prod.config.js",
    "dev": "webpack-dev-server --open --config ./build/dev.config.js"
  }
  • 此时使用npm run build打包文件,dist文件并不在根目录下,因为我们在base.config.js中配置的出口文件使用的是当前文件的路径,即打包的根路径是配置文件的当前路径,也就是build文件夹。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLb4WQTk-1612964660715)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-32.png)]

  entry: './src/main.js',//入口文件
  output:{
     
    path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
    filename: 'bundle.js',//打包后的文件名
    // publicPath: 'dist/'
  }

注意:__dirname是当前文件路径,path.resolve拼接路径,所以在当前路径下创建了一个dist文件夹。

  • 此时修改output属性:
output:{
     
    path: path.resolve(__dirname, '../dist'),//动态获取打包后的文件路径,path.resolve拼接路径
    filename: 'bundle.js',//打包后的文件名
    // publicPath: 'dist/'
  }

使用../dist,在当前目录的上级目录创建dist文件夹

否则会在build目录下创建dist文件夹

(十六)vue-cli

16.1 vue-cli起步

什么是vue-cli

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 搭建交互式的项目脚手架。
  • 通过 @vue/cli + @vue/cli-service-global 快速开始零配置原型开发。
  • 一个运行时依赖 (@vue/cli-service),该依赖:
    • 可升级;
    • 基于 webpack 构建,并带有合理的默认配置;
    • 可以通过项目内的配置文件进行配置;
    • 可以通过插件进行扩展。
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。

CLI是什么意思?

  • CLI是Command-Line Interface,即命令行界面,也叫脚手架。
  • vue cli 是vue.js官方发布的一个vue.js项目的脚手架
  • 使用vue-cli可以快速搭建vue开发环境和对应的webpack配置

vue cli使用

vue cli使用前提node

  • vue cli依赖nodejs环境,vue cli就是使用了webpack的模板。

  • 安装vue脚手架,现在脚手架版本是vue cli3

npm install -g @vue/cli
  • 如果使用yarn
yarn global add @vue/cli
  • 安装完成后使用命令查看版本是否正确:
vue --version

注意安装cli失败

  1. 以管理员使用cmd
  2. 清空npm-cache缓存
npm clean cache -force

拉取2.x模板(旧版本)

Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:

npm install -g @vue/cli-init

# `vue init` 的运行效果将会跟 `[email protected]` 相同
vue init webpack my-project

1.在根目录新建一个文件夹16-vue-cli,cd到此目录,新建一个vue-cli2的工程。

cd 16-vue-cli
//全局安装桥接工具
npm install -g @vue/cli-init
//新建一个vue-cli2项目
vue init webpack 01-vuecli2test

注意:如果是创建vue-cli3的项目使用:

vue create 02-vuecli3test

@vue-cli4
npx vue create 02-vuecli4test

2.创建工程选项含义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Dx1dNNu-1612964660716)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-1.png)]

  • project name:项目名字(默认)
  • project description:项目描述
  • author:作者(会默认拉去git的配置)
  • vue build:vue构建时候使用的模式
    • runtime+compiler:大多数人使用的,可以编译template模板
    • runtime-only:比compiler模式要少6kb,并且效率更高,直接使用render函数
  • install vue-router:是否安装vue路由
  • user eslint to lint your code:是否使用ES规范
  • set up unit tests:是否使用unit测试
  • setup e2e tests with nightwatch:是否使用end 2 end,点到点自动化测试
  • Should we run npm install for you after the project has been created? (recommended):使用npm还是yarn管理工具

注意:如果创建工程时候选择了使用ESLint规范,又不想使用了,需要在config文件夹下的index.js文件中找到useEslint,并改成false。

    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: true,

16.2 vue-cli的目录结构

  • 创建完成后,目录如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePjoCxio-1612964660717)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-2.png)]

  • 其中build和config都是配置相关的文件。

build和config

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OUcPAUwn-1612964660718)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-3.png)]

  • 如图所示,build中将webpack的配置文件做了分离:

    • webpack.base.conf.js(公共配置)
    • webpack.dev.conf.js(开发环境)
    • webpack.prod.conf.js(生产环境)
  • 我们使用的脚本命令配置在package.json中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3r1TVf90-1612964660719)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-4.png)]

  • 打包构建:
npm run build
  • 如果搭建了本地服务器webpack-dev-server,本地开发环境:
npm run dev
  • 此时npm run build打包命令相当于使用node 执行build文件夹下面的build.js文件。

build.js

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zz6pWaTw-1612964660720)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-5.png)]

  1. 检查dist文件夹是否已经存在,存在先删除
  2. 如果没有err,就使用webpack的配置打包dist文件夹
  • 在生产环境,即使用build打包时候,使用的是webpack.prod.conf.js配置文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGhDjxlI-1612964660721)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-6.png)]

  • 源码中,显然使用了webpack-merge插件来合并prod配置文件和公共的配置文件,合并成一个配置文件并打包,而webpack.dev.conf.js也是如此操作,在开发环境使用的是dev的配置文件。

  • config文件夹中是build的配置文件中所需的一些变量、对象,在webpack.base.conf.js中引入了index.js

const config = require('../config')

src和static

  • src源码目录,就是我们需要写业务代码的地方。

  • static是放静态资源的地方,static文件夹下的资源会原封不动的打包复制到dist文件夹下。

其他相关文件

.babelrc文件

  • .babelrc是ES代码相关转化配置。
{
     
  "presets": [
    ["env", {
     
      "modules": false,
      "targets": {
     
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"]
}
  • browsers表示需要适配的浏览器,份额大于1%,最后两个版本,不需要适配ie8及以下版本

  • “stage-2”:阶段

  • “plugins”:babel需要的插件

.editorconfig文件

  • .editorconfig是编码配置文件。
root = true		// 根据root开始查找

[*]
charset = utf-8
indent_style = space
indent_size = 2		// 缩进
end_of_line = lf	// 最后换行
insert_final_newline = true // 最后添加一行
trim_trailing_whitespace = true // 清除多余空格
  • 一般是配置编码,代码缩进2空格,是否清除空格等。

.eslintignore文件

  • .eslintignore文件忽略一些不规范的代码。
/build/
/config/
/dist/
/*.js
  • 忽略build、config、dist文件夹和js文件。

.eslintrc.js文件

.gitignore文件

  • .gitignore是git忽略文件,git提交忽略的文件。

.postcssrc.js文件

  • css转化是配置的一些。

index.html文件

  • index.html文件是使用html-webpack-plugin插件打包的index.html模板。

package.json和package-lock.json

  1. package.json(包管理,记录大概安装的版本)
  2. package-lock.json(记录真实安装版本)

16.3 runtime-compiler和runtime-only区别

  • 新建两个vuecli2项目:
//新建一个以runtime-compiler模式
vue init webpack 02-runtime-compiler
//新建一个以runtime-only模式
vue init webpack 03-runtime-only
  • 两个项目的main.js区别

runtime-compiler

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
     
  el: '#app',
  components: {
      App },
  template: ''
})

runtime-only

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
     
  el: '#app',
  render: h => h(App)
})

render: h => h(App)

render:function(h){
     
  return h(App)
}

compiler编译解析template过程

template → ast → render → virtual dom → 真实dom

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EnFlPlAN-1612964660722)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-7.png)]

  • 过程
    • vm.options.template解析(parse)成ast(abstract syntax tree)抽象语法树
    • 抽象语法树编译(compiler)成vm.options.render(functions)render函数
    • render函数最终将template解析的ast渲染成虚拟DOM(virtual dom
    • 最终虚拟dom映射到ui上。

runtime-compiler

  • template会被解析 => ast(抽象语法树) => 然后编译成render函数 => 渲染成虚拟DOM(vdom)=> 真实dom(UI)

runtime-only

  • render => vdom => UI
  • 性能更高
  • 需要代码量更少

render函数

render:function(createElement){
     
  //1.createElement('标签',{标签属性},[''])
  return createElement('h2',
    {
     class:'box'},
    ['Hello World',createElement('button',['按钮'])])
  //2.传入组件对象
  //return createElement(cpn)
}
  • h就是一个传入的createElement函数

  • .vue文件的template是由vue-template-compiler插件解析。

  • 将02-runtime-compiler的main.js修改

new Vue({
     
  el: '#app',
  // components: { App },
  // template: ''
  //1.createElement('标签',{标签属性},[''])
  render(createElement){
     
    return createElement('h2',
    {
     class:'box'},
    ['hello vue', createElement('button',['按钮'])])
  }
})
  • 并把config里面的inedx.js的useEslint: true改成false,即关掉eslint规范,打包项目npm run dev,打开浏览器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EIqUTzJU-1612964660724)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-8.png)]

在修改main.js

new Vue({
     
  el: '#app',
  // components: { App },
  // template: ''
  //1.createElement('标签',{标签属性},[''])
  render(createElement){
     
    // return createElement('h2',
    // {class:'box'},
    // ['hello vue', createElement('button',['按钮'])])
    //2.传入组件
    return createElement(App)
  }

再次打包,发现App组件被渲染了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MfBrQxt7-1612964660725)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-9.png)]

16.4 vue-cli3

vue-cli3起步

vue-cli3与2版本区别

  • vue-cli3基于webpack4打造,vue-cli2是基于webpack3
  • vue-cli3的设计原则是"0配置",移除了配置文件,build和config等
  • vue-cli3提供vue ui的命令,提供了可视化配置
  • 移除了static文件夹,新增了public文件夹,并将index.html移入了public文件夹

创建vue-cli3项目

vue create 04-vuecli3test

目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbfFsrGP-1612964660726)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-10.png)]

  • public 类似 static文件夹,里面的资源会原封不动的打包
  • src源码文件夹

使用npm run serve运行服务器,打开浏览器输入http://localhost:8080/

打开src下的main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
     
  render: h => h(App),
}).$mount('#app')

Vue.config.productionTip = false构建信息是否显示

如果vue实例有el选项,vue内部会自动给你执行$mount('#app'),如果没有需要自己执行。

vue-cli3的配置

  • 在创建vue-cli3项目的时候可以使用vue ui命令进入图形化界面创建项目,可以以可视化的方式创建项目,并配置项。

  • vue-cli3配置被隐藏起来了,可以在node_modules文件夹中找到@vue模块,打开其中的cli-service文件夹下的webpack.config.js文件。

  • 再次打开当前目录下的lib文件夹,发现配置文件service.js,并导入了许多模块,来自与lib下面的config、util等模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzYsr0yt-1612964660728)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-11.png)]

  • 如何要自定义配置文件

在项目根目录下新建一个vue.config.js配置文件,必须为vue.config.js,vue-cli3会自动扫描此文件,在此文件中修改配置文件。

//在module.exports中修改配置
module.exports = {
     
  
}

(十七)vue-router

17.1 路由简介

什么是路由?

  • 路由就是通过互联的网络把信息用源地址传送到目的地的活动

  • 路由提供了两种机制:路由传送

    • 路由是决定数据包从来源目的地的路径
    • 转送就是将数据转移
  • 路由表

    • 路由表本质就是一个映射表,决定了数据包的指向

17.2 前端/后端路由

回顾:https://www.bilibili.com/video/BV15741177Eh?p=101&spm_id_from=pageDriver

  • 第一阶段 后端渲染(服务端渲染)

    • 后端路由:后端处理URL和页面映射关系

    • 例如springmvc中的@requestMapping注解配置的URL地址,映射前端页面

    • jsp技术

    • 缺点:前后端责任不清晰

  • 第二阶段 前后端分离(前端渲染)(ajax请求数据)

    • 后端只负责提供数据
    • 静态资源服务器(html+css+js)
    • ajax发送网络请求后端服务器,服务器回传数据
      js代码渲染dom
  • 第三阶段 单页面富应用(SPA页面)

    • 前后端分离加上前端路由,前端路由的url映射表不会向服务器请求,是单独url的页面自己的ajax请求后端,后端只提供api负责响应数据请求。改变url,页面不进行整体的刷新。
    • 整个网站只有一个html页面。
    • 前端来维护一套路由规则
    • 核心
      • 改变url,页面整体不进行刷新
      • 实现手段
        • hash
        • history

17.3 URL的hash和HTML5的history

URL的hash

  • URL的hash是通过锚点(#),其本质上改变的是window.location的href属性。
  • 可以通过直接赋值location.hash来改变href,但是页面并不会发生刷新。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJApYZIH-1612964660729)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sGbMSML-1612964660730)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-2.gif)]

结论

  • 测试发现url的地址栏改变了变成了http://localhost:8080/#/zty ,通过查看network发现只有favicon.ico资源重新请求了,这个是工程的logo图标,其他资源都未请求。可以通过改变hash改变url,此时页面是未刷新的。

  • vue-router其实用的就是这样的机制,改变url地址,这个url地址存在一份路由映射表里面,比如/user代表要请求用户页面,只要配置了这个路由表(路由关系),就可以前端跳转而不刷新页面,所有的数据请求都走ajax。

HTML5的history模式

pushState

同样的使用HTML5的history模式也是不会刷新页面的,history对象栈结构,先进后出,pushState类似压入栈中,back是回退。

hristory.pushState({
     }, '', '/foo')
history.back()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74PAvEUH-1612964660731)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-3.png)]

replaceState

  • replaceState模式与pushState模式区别在于replaceState模式浏览器没有返回(浏览器左上角没有返回按钮),只是替换,不是压入栈中。“无痕浏览”
history.replaceState({
     }, '', 'home')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iAclIyoa-1612964660732)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-4.png)]

go

  • go只能在pushState模式中使用,go是前进后退到哪个历史页面
history.go(-1) // 回退一个页面
history.go(1) // 前进一个页面
history.forward() // 等价于go(1)
history.back() // 等价于go(-1)

17.4 vue-router的安装配置

  • 使用npm install vue-router --save来安装vue-router插件模块

  • 在模块化工程中使用他(因为是一个插件,所以可以通过Vue.user来安装路由功能)

    • 在src下创建一个router文件夹(一般安装vue-router时候会自动创建)用来存放vue-router的路由信息导入路由对象,并且调用Vue.use(VueRouter)
    • 创建路由实例,并且传入路由映射配置
    • 在vue实例中挂载创建的路由实例对象

router文件夹中的index.js

/**
 * 配置路由相关信息
 * 1.先导入vue实例和vue-router实例
 */
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

// 2. 通过Vue.use(插件),安装插件
Vue.use(Router)
//3. 创建 router路由对象
const routes = [
  //配置路由和组件之间的对应关系
  {
     
    path: '/',//url
    name: 'HelloWorld',
    component: HelloWorld //组件名
  }
]
const router = new Router({
     
  //配置路由和组件之间的应用关系
  routes
})
//4.导出router实例
export default router

main.js中挂载router对象

/* eslint-disable no-new */
new Vue({
     
  el: '#app',
  router,//使用路由对象,简写对象增强写法
  render: h => h(App)
})

17.5 vue-router的使用

创建路由组件

  • 在components文件夹下创建2个组件。

Home组件




About组件




配置路由映射:组件和路径映射关系

  • 在路由与组件对应关系配置在routes中。

修改index.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'

// 2. 通过Vue.use(插件),安装插件
Vue.use(Router)
//3. 创建 router路由对象
const routes = [
  //配置路由和组件之间的对应关系
  {
     
    path: '/home',//home  前端路由地址
    name: 'Home',
    component: Home //组件名
  },
  {
     
    path: '/about',//about 前端路由地址
    name: 'About',
    component: () => import('@/components/About') //懒加载组件
  }
]
const router = new Router({
     
  //配置路由和组件之间的应用关系
  routes
})
//4.导出router实例
export default router

使用路由:通过

在app.vue中使用 两个全局组件显示路由。

是全局组件,最终被渲染成a标签,但是只是标记路由指向类似一个a标签或者按钮一样,但是我们点击a标签要跳转页面或者要显示页面,所以就要用上

是用来占位的,就是路由对应的组件展示的地方,该标签会根据当前的路径,动态渲染出不同的组件。

路由切换的时候切换的是挂载的组件,其他不会发生改变。

默认使用hash模式,可以在index.js中配置修改为history模式。

app.vue修改template


使用npm run dev启动项目,此时下面,那渲染页面就在下面,此时未配置路由的默认值,所以第一次进入网页的时候占位的地方是没有内容的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NclowXsK-1612964660733)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-6.gif)]

路由的默认值和history模式

路由的默认值,修改index.js的routes

const routes = [
  {
     
    path: '',
    redirect: '/home'//缺省时候重定向到/home
  },
  //配置路由和组件之间的对应关系
  {
     
    path: '/home',//home  前端路由地址
    name: 'Home',
    component: Home //组件名
  },
  {
     
    path: '/about',//about 前端路由地址
    name: 'About',
    component: () => import('@/components/About') //懒加载组件
  }
]
  • 添加缺省值,并重定向/home路径,此时打开http://localhost:8080 ,直接显示home组件内容。

  • 修改hash模式为history模式,修改index.js的router对象

    • mode: ‘history’
const router = new Router({
     
  //配置路由和组件之间的应用关系
  routes,
  mode: 'history'//修改模式为history
})

此时发现浏览器地址栏的URL是没有#的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1TXtv1j-1612964660734)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-7.png)]

的其他属性

  • to属性:用于跳转到指定路径。

  • tag属性:可以指定之后渲染成什么组件使用会被渲染成一个按钮,而不是a标签。

  • relapce属性:在history模式下指定使用replaceState而不是pushState,此时浏览器的返回按钮是不能使用的。

  • active-class属性:当对应的路由匹配成功的时候,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称。

    • 在进行高亮显示的导航菜单或者底部tabbar时,会用到该属性

    • 但是通常不会修改类的属性,会直接使用默认的router-link-active

    • 此时被选中的就会有active的class。

    • 如果每个都要加上active-class='active',那就在路由里面统一更改。

    const router = new Router({
           
      //配置路由和组件之间的应用关系
      routes,
      mode: 'history',//修改模式为history
      linkActiveClass: 'active'
    })
    
    
    
    
    
    
    

    修改app.vue文件此时被选中的就有了active属性,给active的class加上字体变红的css。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPQE8rBC-1612964660735)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-8.png)]

通过代码修改路由跳转

$router属性






  • 修改app.vue,将换成button等任何组件,添加上点击事件,并写好点击事件响应方法,此时使用this.$router.push('/home'),push方法 等价于pushState方法,replace 方法等价于replaceState方法。

17.5 渐入vue-router

vue-router的动态路由

  • 一个页面的path路径可能是不确定的,例如可能有/user/aaaa或者/user/bbbb,除了/user之外,后面还跟上了用户ID/user/123等。这种path和component的匹配关系,叫动态路由

新建一个User组件





  • 该组件定义一个计算属性,通过this.$route.params.userId获取处于激活状态的路由参数userId
    • 注意区别 router 、​routes、route
      • $router:const router = new VueRouter({})对象——路由器。
      • routes:路由数组名,可自定义。
      • $route:每一个路由,拿到的就是当前渲染状态的路由。

配置路由参数index.js

  {
     
    path: '/user/:userId',
    name: 'User',
    component: () => import('@/components/User') //懒加载组件
  }

使用:userId指定动态路由参数userId

app.vue中添加user页面的,并添加userId变量

    用户
  data (){
     
    return {
     
      userId: 'zty'
    }

启动项目,点击用户。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPX5nVoK-1612964660736)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-9.png)]

总结

$route是代表处于激活状态的路由,这里指的也就是

  {
     
    path: '/user/:userId',
    name: 'User',
    component: () => import('@/components/User')
  }

通过$route.params获取 $route 所有的参数,$route.params.userId,获取所有参数中的名字叫userId的属性,此时可以在User组件中动态获取路由参数,也就可以在app.vue中动态设置路由中的userId,其他属性请参考 $route

懒加载——vue-router的打包文件解析

问题:打包时候js太大,页面响应缓慢

  • 懒加载
    • 如果组件模块化了,当路由被访问的时候才开始加载被选中的组件
ES6
component: () => import('@/components/User')

AMD写法
const About = resolve => require(['../components/About.vue'], resolve)

使用npm run build命令将之前创建的项目打包,打开dist文件夹,器目录结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Hb4qvn9-1612964660737)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-10.png)]

  • app.xxx.js是我们自己编写的业务代码
  • vendor.xxx.js是第三方框架,例如vue/vue-router/axios等
  • mainfest.xxx.js是为了打包的代码做底层支持的,一般是webpack帮我们做一些事情
  • 除了这三个还多了2个js,这2个js文件(0.5bxxx.js和1.e5xxx.js)分别是About和User组件,因为这2个组件是懒加载的所以被分开打包了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSomBRUH-1612964660738)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-11.png)]

  • 此时因为是懒加载,需要用到这个组件的时候才会加载,所以不会一次性请求所有js。

认识嵌套路由

  • 平常在一个home页面中,我们可能需要/home/news/home/message访问一些内容,一个路由映射一个组件就像后端一个api对应一个controller的一个requestMapping一样,访问两个路由也会分别渲染这两个组件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IO7PjMYx-1612964660739)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-12.png)]

  • 要实现嵌套路由:
    • 创建对应的子组件,并且在路由映射(router/index.js)中配置对应的子路由。

    • 在组件内部使用标签来占位。

新建2个组件HomeNews和HomeMessage







配置嵌套路由

  {
     
    path: '/home',//home  前端路由地址
    name: 'Home',
    component: Home, //组件名
    children: [
      {
     
        path: '',
        redirect: '/home/news'//缺省时候重定向到/home/news
      },
      {
     
        path: 'news',//子嵌套路由 无须加/
        name: 'News',
        component: () => import('@/components/HomeNews') //懒加载组件
      },
      {
     
        path: 'message',
        name: 'Message',
        component: () => import('@/components/HomeMessage') //懒加载组件
      }
    ]
  },

修改Home.vue组件加上


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ioUzr6cz-1612964660740)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-13.png)]

vue-router的参数传递

  • 之前的动态路由说的userId也是参数传递的方式的一种,准备新建一个Profile.vue组件,并配置路由映射,添加指定的



  {
     
    path: '/profile',
    name: 'Profile',
    component: () => import('@/components/Profile')
  }
档案
  • 在app.vue中设置初始的对象profileInfo
  data (){
     
    return {
     
      userId: 'zty',
      profileInfo: {
     
        name: "zty",
        age: 24,
        height: 177
      }
    }
  }

传递参数主要有两种类型:params和query

  • params的类型也就是动态路由形式

    • 配置路由的格式:/user/:userId
    • 传递的方式:在path后面跟上对应的userId
    • 传递形成的路径:/user/123/user/xxx
    • 通过$route.params.userId获取指定userId
  • query的类型

    • 配置路由的格式:/profile,也就是普通的配置
    • 传递的方式:对象中使用query的key作为传递的方式
    • 传递形成的路径:/profile?name=zty&age=24&height=177(这个传递的是三个键值对),/profile?profileInfo=%5Bobject%20Object%5D(这个query传递的是一个对象的键值对,key为profileInfo,value是一个对象)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SBn8k3JT-1612964660741)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-14.png)]

  • 使用代码编写传递数据,使用button代替,并添加点击事件。
    
    
    userClick() {
     
      this.$router.push('/user/' + this.userId)
      console.log("userClick")
    },
    profileClick() {
     
      let profileInfo = this.profileInfo
      this.$router.push({
     
        path: '/profile',
        query: {
     
          profileInfo
        }
      })
      console.log("profileClick")
    }

router和route的由来

  • vue全局对象this.$router与main.js导入的router对象是一个对象,也就是我们router/index.js导出的对象router
new Vue({
     
  el: '#app',
  router, zz// 使用路由对象
  render: h => h(App)
})
//4.导出router实例
export default router
  • this.$route对象是当前处于活跃的路由,有params和query属性可以用来传递参数。

  • 查看vue-router源码,在我们项目中的router/index.js中,vue 对于插件必须要使用Vue.use(Router),来安装插件,也就是执行vue-router的install.js

在vue-router的github源码中查看src结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDHrmiGx-1612964660742)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-15.png)]

  • 其中index.js是入口文件,入口js文件就是导入并执行了install.js文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IrIrsR9s-1612964660744)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-16.png)]

发现

  • install.js中有注册2个全局组件RouterViewRouterLink,所以我们能使用组件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOJKc3ZX-1612964660745)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-17.png)]

r o u t e r 和 router和 routerroute是继承自vue的原型

  • 怎么理解原型?学过Java 的都知道有父类和子类,子类也可以有自己的子类,但是他们都有一个处于最顶层的类Object(所有类的父类)。在Vue中就有那一个Vue类似Object,在java中在Object中定义的方法,所有的类都可以使用可以重写,类似的Vue.prototype(Vue的原型)定义的属性方法,他的原型链上的对象都可以使用,而$router$route都在Vue的原型链上。

  • 在main.js入口文件中在vue的原型上定义一个方法test,然后在User组件中尝试调用。

import Vue from 'vue'
import App from './App'
import router from './router'

//在vue的原型上添加test方法
Vue.prototype.test = function () {
     
  console.log("test")
}
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
     
  el: '#app',
  router,//使用路由对象
  render: h => h(App)
})






  • 启动项目点击User页面上的按钮,打开浏览器控制台查看日志发现test方法被执行了,而User组件中并未定义test方法,却可以调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozGCeddj-1612964660746)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-18.png)]

  • 继续来读install.js,install.js中一开始就将Vue这个类当参数传入了install方法中,并把Vue赋值给_Vue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzusTKPA-1612964660747)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-19.png)]

  • 继续读install.js发现以下代码
  Object.defineProperty(Vue.prototype, '$router', {
     
    get () {
      return this._routerRoot._router }
  })
//Object.defineProperty用来定义属性
  Object.defineProperty(Vue.prototype, '$route', {
     
    get () {
      return this._routerRoot._route }
  })
  • Object.defineProperty用来定义属性,以上代码就是给Vue.prototype(Vue原型)添加$router$route属性并给属性赋值,等价于:
Vue.prototype.$router = {
     
    get () {
      return this._routerRoot._router }
}
Vue.prototype.$router = {
     
  get () {
      return this._routerRoot._router }
}
  • 也就是在Vue的原型上添加$router$route属性,再查看get()返回值this._routerRoot._router

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiVwUE99-1612964660748)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-20.png)]

这里的this.$options.router就是我们main.js入口文件传入的参数router,也就是router/index.js导出的router对象。

new Vue({
     
  el: '#app',
  router,//使用路由对象
  render: h => h(App)
})

17.6 vue-router其他

vue-router的导航守卫

问题:我们经常需要在路由跳转后,例如从用户页面跳转到首页,页面内容虽然可以自己定义,但是只有一个html文件,也只有一个title标签,我们需要改变标题。

  • 可以使用js去修改title,可以使用vue的生命周期函数在组件被创建的时候修改title标签内容。
created() {
     
	//创建的时候修改title
    document.title = '关于'
}
mounted() {
     
    //数据被挂载到dom上的时候修改title
}
update() {
     
    //页面刷新的时候修改
}
  • 当然不能每个组件去写生命周期函数,如果我们能监听路由的变化(了解路由从哪来往哪里跳转),那我们就能在跳转中修改title标签,这就是导航守卫能做的事情。

前置守卫——前置钩子(回调)

  • 修改router/index.js
/**
 * 前置钩子:从from跳转到to
 * from 来的路由
 * to 要去的路由
 */
router.beforeEach((to, from, next) => {
     
  document.title = to.matched[0].meta.title //给目标路由的页面的title赋值
  next()//必须调用,不调用不会跳转
})

router.beforeEach()称为前置钩子(前置守卫),顾名思义,跳转之前做一些处理。

  • 当然每个路由配置上也要加上meta属性,不然就取不到了,为什么要使用matched[0],因为如果你是嵌套路由,有没有给子路由添加meta(元数据:描述数据的数据)属性,就会显示undefined,使用matched[0]表示取到匹配的第一个就会找到父路由的meta属性。
  //配置路由和组件之间的对应关系
  {
     
    path: '/home',//home  前端路由地址
    name: 'Home',
    component: Home, //组件名
    meta: {
     
      title: '首页'
    },
    children: [
      {
     
        path: '',
        redirect: '/home/news'//缺省时候重定向到/home/news
      },
      {
     
        path: 'news',//子嵌套路由 无须加/
        name: 'News',
        component: () => import('@/components/HomeNews') //懒加载组件
      },
      {
     
        path: 'message',
        name: 'Message',
        component: () => import('@/components/HomeMessage') //懒加载组件
      }
    ]
  },
  • 启动服务发现功能已经实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoYQFozd-1612964660749)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-21.gif)]

导航守卫补充——后置钩子

  • 前面说了前置守卫router.beforeEach(),相对的应该也存在后置守卫(后置钩子)。
/**
 * 后置钩子
 */
router.afterEach((to, from) => {
     
  console.log('后置钩子调用了----')
})
  • 顾名思义,也就是在跳转之后的回调函数。
    • 前置守卫和后置守卫都是全局守卫
    • 还有路由独享守卫
    • 组件内的守卫

路由独享守卫,路由私有的

  {
     
    path: '/about',//about 前端路由地址
    name: 'About',
    component: () => import('@/components/About'),
    beforeEnter: (to, from, next) => {
     
      console.log('来自' + from.path + ',要去' + to.path)
      next()
    },
    meta: {
     
      title: '关于'
    }
  },

beforeEnter的参数与全局守卫一样,修改about路由的参数,添加路由独享守卫,此时只有跳转到about路由,才会打印日志。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkqiT1RM-1612964660750)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-22.png)]

组件内的守卫,直接在组件中定义的属性

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
     
  template: `...`,
  beforeRouteEnter (to, from, next) {
     
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
     
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
     
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
     
  next(vm => {
     
    // 通过 `vm` 访问组件实例
  })
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

beforeRouteUpdate (to, from, next) {
     
  // just use `this`
  this.name = to.params.name
  next()
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from , next) {
     
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
     
    next()
  } else {
     
    next(false)
  }
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

17.7 keep-alive

  • 先给Home组件加上created()destoryed()2个生命周期函数。

  • 启动项目,某些时候可能有这样的需求,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0MX5LAC-1612964660751)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-23.gif)]

分析

  • 在首页和关于组件之间路由跳转的时候,Home组件一直重复创建和销毁的过程,每次创建都是新的Home组件,但是我有这样的需求。当我点击首页消息页面,随后跳转到关于页面,又跳转到首页,此时我希望显示的是首页的消息页面而不是默认的新闻页面,此时就需要keep-alive来使组件保持状态,缓存起来,离开路由后,Home组件生命周期的**destroyed()不会被调用**,Home组件不会被销毁。

  • keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或者避免重新渲染。

  • router-view也是一个组件,如果用,将其包起来,所有路径匹配到的视图组件都会被缓存。

修改app.vue代码

    <keep-alive>
      <router-view/>
    keep-alive>
  • 再次启动项目,发现还是新闻页面?难道是keep-alive无效?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30UwVxCo-1612964660753)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-24.gif)]

  • 仔细看控制台发现,在跳转关于页面的时候Home组件并没有被销毁,说明keep-alive生效了。仔细查看路由配置发现,/home被默认重定向到了/home/news。所以在访问/home的时候每次出来的都是新闻。

思路

  • 将默认的重定向去掉,但是第一次进入首页,那新闻页面内容又不会显示了。

          // {
           
          //   path: '',
          //   redirect: '/home/news'//缺省时候重定向到/home/news
          // },
    
  • 为了第一次能使新闻页面内容显示,可以使用created(),将路由用代码的方式手动重定向,也就是push。

      created() {
           
        console.log('Home组件被创建了')
        this.$router.push('/home/news')
      },
    
  • 由于keep-alive组件只创建一次,第一次进入Home组件的时候,新闻页面显示正常,当第二次跳转首页的时候,因为不会再调用created(),所以新闻页面又不会显示了。

  • 为了解决问题,在Home组件中引入activated()deactivated()两个函数,这2个函数与keep-alive有关,不使用keep-alive的这两个函数无效。

    • activated()当组件属于进入活跃状态的时候调用
    • deactivated()当组件属于退出活跃状态的时候调用(此时路由已经跳转,所以不能在此方法中修改路由,因为修改的是to路由)
  • 为了使第二次进入首页新闻页面可以生效,使用activated()在Home组件使活跃状态时候就重定向

        data() {
           
        return {
           
          path: '/home/news'
        }
      },
      activated(){
           
        console.log('调用actived')
        this.$router.push(this.path)//在活跃的时候将保存的路由给当前路由
      },
      deactivated(){
           
        console.log('调用actived')
        console.log(this.$route.path)
        this.path = this.$route.path//变成不活跃状态,将最后的路由保存起来
      }
    
  • 发现还是不行,由于deactivated()调用的时候,此时路由已经跳转,所以不能在此方法中修改路由,因为修改的是to路由。

  • 使用路由守卫(组件内守卫),beforeRouteLeave (to, from , next)在离开路由的时候将当前的路由赋值给path并保存起来。

      activated(){
           
        console.log('调用actived')
        this.$router.push(this.path)
      },
      // deactivated(){
           
      //   console.log('调用actived')
      //   console.log(this.$route.path)
      //   this.path = this.$route.path
      // },
      beforeRouterLeave(to, from, next) {
           
        console.log(this.$route.path)
        this.path = this.$route.path
        next()
      }
    
  • 此时问题完全解决了。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdQdcXKb-1612964660754)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-25.gif)]

keep-alive的属性

    
      
    
  • 我们将包起来,那所有的组件都会缓存,都只会创建一次,如果我们需要某一个组件每次都创建销毁,就需要使用exclude属性。

   

  • 此时ProfileUser组件(这里组件需要有name属性,分别为ProfileUser)就被排除在外,每次都会创建和销毁。相对应的也有include属性,顾名思义就是包含,只有选中的才有keep-alive

   

includeexclude都是使用字符串和正则表达式,使用字符串的时候,注意“,”之后之前都别打空格。

17.8 综合练习-实现Tab-Bar

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gILBhF0V-1612964660755)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-26.gif)]

实现Tab-Bar思路

  1. 下方单独的Tab-Bar组件如何封装?
    • 自定义Tab-Bar组件,在APP中使用
    • Tab-Bar位置在底部,并设置你需要的样式
  2. Tab-Bar中显示的内容由外部决定
    • 定义插槽
    • flex布局平分Tab-Bar
  3. 自定义Tab-Bar-Item,可以传入图片和文字
    • 定义Tab-Bar-Item,并定义两个插槽:图片和文字
    • 给插槽外层包装div,设置样式
    • 填充插槽,实现底部Tab-Bar的效果
  4. 传入高亮图片
    • 定义另一个插槽,插入active-icon的数据
    • 定义一个变量isActicve,通过v-show来决定是否显示对应的icon
  5. Tab-Bar-Item绑定路由数据
    • 安装路由:npm install vue-router --save
    • router/index.js配置路由信息,并创建对应的组件
    • main.js中注册router
    • App.vue中使用router-linkrouter-view
  6. 点击item跳转到对应的路由,并且动态决定isActive
    • 监听item的点击,通过this.$router.replace()替换路由路径
    • 通过this.$route.path.indexOf(this.link)!==-1来判断是否使active
  7. 动态计算active样式
    • 封装新的计算属性:this.isActive?{'color': 'red'}:{}

代码实现

  • 使用vue init webpack 02-vue-router-tabbar-v1新建一个项目工程(使用vuecli2)。
  1. 在文件夹assest下新建css/base.css,用于初始化css

    base.css

    body {
           
      padding: 0;
      margin: 0;
    }
    

    修改App.vue,添加初步样式

    
    
    
    
    
    
    

    使用npm run dev,查看网页效果

    思考:如果每次都要复用tabbar,那每次都需要复制粘贴,应该要把tabbar抽离出来,vue就是组件化思想。

  2. 将tabbar抽离成组件

    在components下新建tabbar文件夹,新建TarBar.vueTabBarItem.vue,TabBarItem组件是在组件TarBar中抽取出来的,可以传入图片和文字(比如首页),所有需要使用插槽代替。

    TarBar.vue

    
    
TabBar弄一个slot插槽用于插入TabBarItem组件(可能插入多个).

> TabBarItem.vue

​```vue



  • TabBarItem组件中插入2个插槽一个用于插入图片一个用于插入文字。

MainTabBar.vue





  • 在MainTabBar组件中加入另外2个组件。

注意此处使用~assets@/components是使用了别名配置,[详情请看17.8.3别名配置](#17.8.3 别名配置)

  • 最后在app.vue中导入MainTabBar组件。




  • 效果如图所示,将组件进行了分离重组,只要修改MainTabBar组件就可以修改图片和文字描述,可以复用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LnDfBZN-1612964660756)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-28.png)]

  1. 如何实现点击首页首页字体变红图片变红色

    • 这里需要用到路由的active-class

    思路:引用2张图片,一张是正常颜色一张是红色,使用v-ifv-else来处理是否变色,在路由处于活跃状态的时候,变红色。

    • 引入路由使用路由就不细说了,这里仅贴上tabbar的修改代码。

    TabBarItem.vue组件

    
    
    
    
    
    
    1. 使用props获取传递的值,这里传递是激活颜色,默认是红色
    2. 设置计算属性isActiveactiveStyle,分别表示激活状态和激活的样式
    3. 定义itemClick()方法用于获取点击事件,点击后使用代码实现路由跳转,这里使用默认的hash模式
    4. 使用v-ifv-else来进行条件判断

    MainTabBar.vue组件

          
            
            
            
          
    
    • 添加激活状态的图片与未激活的图片并列。
  2. 配置路由信息,参考之前的代码

    import Vue from 'vue'
    import Router from 'vue-router'
    
    
    Vue.use(Router)
    
    const routes = [
      {
           
        path: '/',
        redirect: '/home'//缺省时候重定向到/home
      },
      {
           
        path: '/home',
        component: () => import ('../views/home/Home.vue')
      },
      {
           
        path: '/categories',
        component: () => import ('../views/categories/Categories.vue')
      },
      {
           
        path: '/shop',
        component: () => import ('../views/shop/Shop.vue')
      },
      {
           
        path: '/profile',
        component: () => import ('../views/profile/Profile.vue')
      },
    ]
    
    export default new Router({
           
      routes,
      // linkActiveClass:"active"
    })
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGDc493j-1612964660758)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-29.png)]

  3. 修改main.js和App.vue

    import Vue from 'vue'
    import App from './App'
    import router from './router'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
           
      el: '#app',
      router,
      render: h => h(App)
    })
    
    
    
    
    
    
    
    

别名配置

  • 经常的我们向引入图片文件等资源的时候使用相对路径,诸如../assets/xxx这样的使用../获取上一层,如果有多个上层就需要../../xxx等等这样不利于维护代码。此时就需要一个能获取到指定目录的资源的就好了。

配置

  • webpack.base.config中配置使用别名,找到resolve:{}模块,增加配置信息
  resolve: {
     
    extensions: ['.js', '.vue', '.json'],
    alias: {
     
      '@': resolve('src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components'),
      'views': resolve('scr/views')
    }
  },
  • 这里@指定目录是src,例如@/components表示src/components目录,assets表示src/assets前缀,如果是assets/img就表示src/assets/img目录。

(十八)Promise

18.1 什么是Promise

  • 简单说Promise是异步编程的一种解决方案。

  • Promise是ES6中的特性。

什么是异步操作?

  • 网络请求中,对端服务器处理需要时间,信息传递过程需要时间,不像我们本地调用一个js加法函数一样,直接获得1+1=2的结果。这里网络请求不是同步的有时延,不能立即得到结果。

如何处理异步事件?

  • 对于网络请求这种,一般会使用回调函数,在服务端传给我数据成功后,调用回调函数。例如ajax调用。
$.ajax({
     
	success:function(){
     
		...
	}
})

如果碰到嵌套网络请求,例如第一次网络请求成功后回调函数再次发送网络请求,这种代码就会让人很难受。

$.ajax({
     
	success:function(){
     
		$.ajax({
     
			...
        })
	}
})
  • 如果还需要再次网络请求,那么又要嵌套一层,这样的代码层次不分明很难读,也容易出问题。

18.2 Promise的基本使用

什么时候使用Promise?

  • 如何解决异步请求冗余这样的问题,promise就是用于封装异步请求的。

Promise对象

new Promise((resolve, reject) => {
     })
  • Promise对象的参数是一个函数(resolve, reject) => {}
  • 这个函数又有2个参数分别是resolvereject。这2个参数本身也是函数
  • 后面还有回调函数then(func)的参数也是一个函数。

模拟定时器的异步事件

  • 用定时器模拟网络请求,定时一秒为网络请求事件,用console.log()表示需要执行的代码。
//1.使用setTimeout模拟嵌套的三次网络请求
setTimeout(() => {
     //第一次请求
    console.log("hello world")//第一次处理代码
    setTimeout(() => {
     //第二次请求
        console.log("hello vuejs")//第二次处理代码
        setTimeout(() => {
     //第三次请求
            console.log("hello java")//第三次处理代码
        }, 1000)
    }, 1000)
}, 1000)
  • 一层套一层,看起是不是很绕。

  • 使用promise来处理异步操作

//参数 -> 函数
// resolve和reject本身也是函数
//then()的参数也是一个函数
new Promise((resolve, reject) => {
     
    setTimeout(() => {
     //第一次网络请求
        resolve()
    }, 1000)
}).then(() => {
     
    console.log("hello world")//第一次处理代码
    return new Promise((resolve, reject) => {
     
        setTimeout(() => {
     //第二次网络请求
            resolve()
        }, 1000).then(() => {
     
            console.log("hello vuejs")//第二次处理代码
            return new Promise((resolve, reject) => {
     
                setTimeout(() => {
     //第三次网络请求
                    resolve()
                }, 1000)
            }).then(() => {
     
                console.log("hello java")//第三次处理代码
            })
        })
    })
})
  • 是不是觉得代码还要更复杂了?仔细看看第一个如果使用了多个就找不到对应关系了。相反第二个流程就很清楚,调用resolve()就能跳转到then()方法就能执行处理代码,then()回调的返回值又是一个Promise对象。层次很明显,只要是then()必然就是执行处理代码,如果还有嵌套必然就是返回一个Promise对象,这样调用就像java中的StringBuffer的append()方法一样,链式调用。
new Promise((resolve, reject) => {
     
    setTimeout(() => {
     
    	resolve('success')
    }, 1000).then(success => {
     
    	console.log(success)
    })
})
  • setTimeout()模拟的是网络请求,而then()执行的是网络请求后的代码,这就将网络请求和请求得到响应后的操作分离了,每个地方干自己的事情。在resolve中传参了,那么在then()方法中的参数就有这个参数,例如data。

网络请求中也会有失败情况?例如网络堵塞。

  • 如何处理失败情况,此时就要用到reject()
new Promise((resolve, reject) => {
     
    setTimeout(() => {
     
    	reject('error message')
    }, 1000).catch(error => {
     
    	console.log(error)
    })
})
  • 此时reject(error)catch()方法捕获到reject()中的error。

合起来

new Promise((resolve, reject) => {
     
    setTimeout(() => {
     
        // 成功的时候调用resolve()
        // resolve('hello world')

        // 失败的时候调用reject()
        reject('error message')
    }, 1000).then(success => {
     
        console.log(success)
    }).catch(error => {
     
        console.log(error)
    })
})
  • 拿ajax来举例子:
new Promise((resolve, reject) => {
     
    $.ajax({
     
        success:function(){
     
            // 成功的时候调用resolve()
            // resolve('hello world')

            // 失败的时候调用reject()
            reject('error message')
        }
    }).then(success => {
     
        console.log(success)
    }).catch(error => {
     
        console.log(error)
    })
})

18.3 Promise的三种状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfayZAO3-1612964660759)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\18-1.png)]

  • pending:等待状态,比如正在进行的网络请求还未响应,或者定时器还没有到时间
  • fulfilled:满足状态,当我们主动回调了resolve函数,就处于满足状态,并会回调then()
  • reject:拒绝状态,当我们主动回调reject函数,就处于该状态,并且会回调catch()

18.4 Promise的链式调用

  1. 网络请求响应结果为 hello ,打印hello
  2. 处理: hello world ,打印hello world
  3. 处理: hello world,vuejs ,打印hello world,vuejs
    new Promise((resolve, reject) => {
     
      setTimeout(() => {
     
        resolve('hello')
      }, 1000)
    }).then(res => {
     
      console.log(res)//打印hello
      return new Promise(resolve => {
     
          resolve(res + ' world')
      }).then(res => {
     
        console.log(res)//打印hello world
        return new Promise(resolve => {
     
          resolve(res + ',vuejs')
        }).then(res => {
     
          console.log(res)//打印hello world,vuejs
        })
      })
    })
  • 链式调用就是then()方法的返回值返回一个Promise对象继续调用then(),此外还有简写Promise.resolve()
new Promise((resolve, reject) => {
     
      setTimeout(() => {
     
        resolve('hello')
      }, 1000)
    }).then(res => {
     
      console.log(res)//打印hello
      return Promise.resolve(res + ' world')
    }).then(res => {
     
        console.log(res)//打印hello world
        return Promise.resolve(res + ',vuejs')
    }).then(res => {
     
      console.log(res)//打印hello world,vuejs
    })
  • 还可以直接省略掉Promise.resolve()
    new Promise((resolve, reject) => {
     
      setTimeout(() => {
     
        resolve('hello')
      }, 1000)
    }).then(res => {
     
      console.log(res)//打印hello
      return res + ' world'
    }).then(res => {
     
        console.log(res)//打印hello world
        return res + ',vuejs'
    }).then(res => {
     
      console.log(res)//打印hello world,vuejs
    })
  • 如果中途发生异常,可以通过catch()捕获异常
    new Promise((resolve, reject) => {
     
      setTimeout(() => {
     
        resolve('hello')
      }, 1000)
    }).then(res => {
     
      console.log(res)//打印hello
      return res + ' world'
    }).then(res => {
     
        console.log(res)
        // return Promise.reject('error message')//发生异常
        throw 'error message' //抛出异常
    }).then(res => {
     
      console.log(res)//打印hello world,vuejs
    }).catch(error => {
     
      console.log(error)
    })
  • 也可以通过throw抛出异常,类似java
throw 'error message' //抛出异常

18.5 Promise的all使用

  • 有这样一个情况,一个业务需要请求2个地方(A和B)的数据,只有A和B的数据都拿到才能走下一步。

ajax实现

$.ajax({
     
    ...//结果A
    resultA = true
    callback()
})
$.ajax({
     
    ...//结果B
    resultB = true
    callback()
})
//回调函数
function callback(){
     
    if(resultA&&resultB){
     
        ...
    }
}
  • 由于不知道网络请求A和网络请求B哪个先返回结果,所以需要定义一个函数只有2个请求都返回数据才回调成功。

Promise实现

    Promise.all([
      new Promise((resolve, resjct) => {
     
        $.ajax({
     
          url: 'url1',
          success: function (data) {
     
            resolve(data)
          }
        })
      }),
      new Promise((resolve, resjct) => {
     
        $.ajax({
     
          url: 'url2',
          success: function (data) {
     
            resolve(data)
          }
        })
      }).then(results => {
     
        console.log(results)
      })
    ])
  • 上面是伪代码,只是包装了ajax,ajaxA和ajaxB的结果都放在resolve()中,Promise将其放在results中了,使用setTimeout模拟。
    Promise.all([
      new Promise((resolve, reject) => {
     
        setTimeout(() => {
     //  请求A
          resolve('结果A')
        }, 1000)
      }),
      new Promise((resolve, reject) => {
     
        setTimeout(() => {
     //  请求B
          resolve('结果B')
        }, 1000)
      })
    ]).then(results => {
     
      console.log(results)
    })

(十九)Vuex

19.1 什么是Vuex

Vuex就是一个状态管理模式,为什么叫模式?因为Vuex包含了一套对state(状态)的操作规范,集中存储管理应用的所有组件的状态

状态管理

  • 简单来说就是管理各个组件共享的数据,类似session

  • session可以存数据,存的过程就是管理,数据的每一次赋值就是当次状态。

  • Vuex在Vue实例顶层中。

    • Vuex也可以理解为java中的一个map,这个map是static(静态资源)的,每次获取map的值,都需要调用java的api,比如map.get(key)获取对应的值,也可以放入数据map.put(data),而且这个数据是所有类都可以调用的,只需要导入这个map就能使用里面的共享数据。

    • 不了解java也没关系,你也可以理解成为百度百科就是一个容纳百科知识的容器,你要搜vuex,百科就会出现vuex的描述,这个搜索就是获取状态,如果你角色百科的vuex描述有误。你也可以发起修改操作,改成正确的vuex描述,这个修改操作就是修改vuex在百科里面的状态。当然你可以搜索修改vuex,别人也可以,因为这个状态是共享的。

    • 简单来看实现这个功能好像我们自己封装一个对象就能实现,但是Vuex有一个特性就是响应式。如果我们自己封装对象想做到完美响应式比较麻烦,所有Vuex帮我们做了这个事情。

什么状态需要Vuex去管理?

  • 比如用户的登录的状态(token)、用户的信息(头像、名称、地理位置信息)等等
  • 比如商品的收藏,购物车的商品等等
  • 这些状态应该是响应式的,用户昵称、头像修改了需要响应

Vuex简单模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g9Qzjn6g-1612964660760)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-01.png)]

  • state,驱动应用的数据源,相当于组件的data对象;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

这是一个单页面数据流向,比如想要修改用户昵称,当前用户昵称的状态是A,通过输入框输入了新的昵称B,调用ajax请求后端修改成功后将state改成B,然后视图响应用户昵称的变化从A到B。

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。
  • 所以我们需要vuex的规范操作来管理状态。

19.2 Vuex基本使用

1.新建一个vuecli2工程

使用vue init webpack 01-vuex-vuecli2新建一个vue项目。

vue init webpack 01-vuex-vuecli2

2.修改App.vue并新建一个HelloVuex.vue组件




HelloVuex.vue




此时我们使用了父子组件通信来完成子组件HelloVuex获取父组件的count

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aarSlnnC-1612964660761)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-02.gif)]

  • 如果不是父子组件如何通信,此时就需要vuex了,还是这这2个组件,现在不使用父子通信,直接使用vuex。

3.使用vuex管理状态

  • 使用npm install vuex --save安装Vuex

  • 安装插件Vue.use(Vuex),在src下新建一个store文件夹,新建一个index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    // 1.安装插件
    Vue.use(Vuex)
    
    // 2.创建对象
    const store = new Vuex.Store({
           
      state: {
            // 状态集合
        count: 0 // 具体的状态数据
      }
    })
    
    // 3.导出store对象
    export default store
    
  • 修改App.vueHelloVuex.vue,直接使用$store.state.count获取count值

    
    
    
    
    
    
    
    
  • 一般不会直接使用$store.state.count获取vuex中的状态,也不是直接使用$store.state.count++来操作vuex中的状态。

19.3 Vuex的流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1nte1rh-1612964660762)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-03.png)]

  • Vue Components是vue组件

  • Mutations :更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

  • State 是vuex中状态的集合

  • Actions与Mutations 类似,经常与后端交互,不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作,mutation不能进行异步操作
  • 组件中修改state,通过提交 mutation,修改完成后vuex帮我们响应到vue组件上。

修改index.js使用mutation

// 2.创建对象
const store = new Vuex.Store({
     
  state: {
      // 状态集合
    count: 0 // 具体的状态数据
  },
  mutations: {
      // 操作修改state(状态)
    increment (state) {
      // 增加
      state.count++
    },
    decrement (state) {
      // 减少
      state.count--
    }
  }
})

修改App.vue提交mutation




测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPBMgZil-1612964660763)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-04.gif)]

  • 测试发现没有问题与直接使用$store.state.count++效果一致,通过提交mutation修改了状态state,在vue-devtools中也能跟踪state变化以及提交的mutation。

19.4 Vuex的核心概念

  • State —— “状态”
  • Getters
  • Mutation —— “改变”
  • Action —— “行为”
  • Moudule —— “模块”

State(单一状态树)

  • Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

  • 简单说就是把数据所有有关的数据封装到一个对象中,这个对象就是store实例,无论是数据的状态(state),以及对数据的操作(mutation、action)等都在store实例中,便于管理维护操作。

  • state的操作在19.2.Vuex的基本使用已经了解,直接通过this.$store.state获取state对象。

Getters

  • Getters类似计算属性,帮我们做一些重复的事情。

  • 例如有这样一个store实例,我们需要获取年龄大于20岁的学生数量

const store = new Vuex.Store({
     
  state: {
      // 状态集合
    students: [
      {
     id: 110, name: 'zzz', age: '18'},
      {
     id: 111, name: 'ttt', age: '20'},
      {
     id: 112, name: 'yyy', age: '22'},
      {
     id: 113, name: 'zty', age: '25'}
    ]
  }
})
  • 你可能会这样写这样一个计算属性去获取年龄大于20岁的学生数量
computed: {
     
    stuCount() {
     
      return this.$store.state.students.filter(student => student.age > 20).length
    }
  }
  • 如果很多组件中需要年龄大于20岁的学生数量,你可能会将这个计算属性复制,将filter函数写很多遍,但是如果你有Getters

在store实例中定义getters

  getters: {
     
    getStudentCounts: state => {
     
      return state.students.filter(s => s.age > 20).length
    }
  }

通过属性调用getters

computed: {
    stuCount() {
      return this.$store.getters.getStudents
    }
  }
  • 现在只需要调用getters的getStudents对象,就能获取数量。

  • 如果你想查询指定ID(传入ID)的学生信息。

  • 你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

定义getters

  getters: {
     
    getStuById: state => id => {
     
      return state.students.find(s => s.id === id)
    }
  }

通过方法访问

  computed: {
     
    stuById() {
     
      return this.$store.getters.getStuById(110)
    }
  }
  • 传入学生ID为110,输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WREMauV7-1612964660764)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-05.png)]

Mutation(状态更新)

  • Vuex的store状态更新的唯一方式:提交Mutation

  • Mutation主要包括两个部分:

    • 字符串的事件类型(type)
  • 一个回调函数(handler),这个回调函数就是我们实际进行状态更改的地方,该回调函数的第一个参数就是state

  • mutations规范

    • mutations中的每个方法尽可能完成的事件比较单一
  • Mutation的定义方式:

    mutation: {
           
        increment (state) {
           
            state.count++
        }
    }
    // increment 为事件类型
    
  • 通过Mutation更新

    mutation () {
           
    	this.$store.commit('increment')
    }
    

mutation接受单个参数

  • mutation携带的参数被称为是mutation的载荷(Payload)

  • 在19.2的工程的基础上修改,添加2个按钮分别是让state中的count+5、count+10,增加2个按钮



  • 新增addCount方法:
addCount (count) {
     
    this.$store.commit('addCount', count) // 将count传入
}
  • 新增一个mutation
addCount (state, count) {
      // 第二个参数是count,第一个始终是state
	state.count+=count
}
  • 测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGPbEfqa-1612964660765)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-06.gif)]

mutation接受多个参数

  • 如果mutation需要接受多个参数,此时可以传一个对象,例如新增一个功能点击按钮新增一个学生,此时需要传学生的ID、姓名、年龄,可以封装成一个学生对象传入。
  1. 新增按钮

    
    
  2. 新增mutation

    addStu (state, stu) {
           
        state.students.push(stu) // 向数组中添加指定的stu
        console.log(state.students.find(s => s.id === stu.id)) // 输出打印查看state中是否有新增stu
    }
    
  3. 新增方法

    addStu () {
           
        const stu = {
           
            id: 114,
            name: 'ytz',
            age: '35'
        }
        this.$store.commit('addStu', stu)
    }
    
  4. 测试

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sh97oPvO-1612964660765)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-07.png)]

mutation的提交风格

  1. 普通提交风格

    this.$store.commot('increment', count)
    

    此时count传过去的就是count=10

  2. 特殊的提交封装

    this.$store.commit({
           
    	type: 'addCount',
    	count //count:count
    })
    
    addCount (state, payload) {
            // 此时传入的就不是一个count值了,而是一个对象
    	state.count += payload.count
    }
    
    • 此时count传过去是一个对象payload(载荷)
    {
           
    	type: 'incrementCount',
    	count: 10
    }
    

Vuex的响应式原理

  • Vuex的store的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。

  • 响应式需要遵循规则

    • state的对象需要初始化
    • 如果需要给state中的对象添加新属性的时候,使用以下方式
      • 使用Vue.set(obj, ‘newProp’, 123)
      • 用新对象替换旧对象
  1. 在state中增加一个对象user

    user: {
           
        name: 'zhangsan',
        sex: '男'
    }
    
  2. 在app.vue增加按钮修改信息

    { { $store.state.user }}

  3. app.vue增加按updateInfo()方法

    updateInfo () {
           
        this.$store.commit('updateInfo', 12)
    }
    
  4. 在mutation中添加updateInfo()

    updateInfo (state, age) {
           
        state.user.age = age
    }
    
  5. 点击修改信息按钮,发现state的值变化了,但是页面没有响应变化

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bt1q2cCh-1612964660766)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-08.png)]

  6. 使用Vue.set()方法支持响应式

        updateInfo (state, age) {
           
          // state.user.age = age
          Vue.set(state.user, 'age', 12)
        }
    
  7. 再次点击修改信息按钮,发现变响应式了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfUi8kA7-1612964660768)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-09.png)]

总结

  1. state未初始化属性(age

    • 使用直接赋值的方式不能响应式
    • 需要使用Vue.set(state.user, 'age', 12)
  2. state已经初始化了,可以使用直接赋值方式

  3. 关于删除属性

          // 该方法没有响应式,需要使用vue.delete
          // delete state.user.age
          Vue.delete(state.user, age)// 响应式删除age
    

mutation的类型常量

  • 一个vue文件中有关mutation的方法太多了,常常可能写错,所有可以在store文件夹下定义一个mutation-type.js的常量。
  1. 定义一个mutation-type.js的常量
export const UPDATEINFO = 'updateInfo'
  1. 修改App.vue的updateinfo方法
import {
      UPDATEINFO } from './store/mutation-type'
[UPDATEINFO] () {
     
	this.$store.commit(UPDATEINFO, 12)
}
  1. 修改store的index.js,将mutation的方法名也改成常量使用方式
import {
      UPDATEINFO } from './mutation-type'
[UPDATEINFO] (state, age) {
     
    // state.user.age = age
    Vue.set(state.user, 'age', 12)
    // 该方法没有响应式,需要使用vue.delete
    // delete state.user.age
    // Vue.delete(state.user, age)// 响应式删除age
}
  • 这样保证了所有的方法都定义在mutation-types.js中,不会出问题。

Actions

  • 使用mutation操作更新state的时候,使用异步修改数据。
  • 有逻辑判断时使用
  1. 修改updateInfo()方法
[UPDATEINFO] (state, age) {
     
    Vue.set(state.user, 'age', 12)
    setTimeout(() => {
      // 延时模拟异步网络请求
        state.user.name = 'lisi'
    }, 1000)
}
  1. 点击修改信息按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGtjrmqT-1612964660769)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-10.png)]

  • 发现页面的数据改变了,但是vue-devtools工具中并未跟踪到改变。所以我们不要在mutation中进行异步操作。

定义

  • Action 类似于 mutation,不同在于:
    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作。
  1. 新增一个mutation
updateName (state, name) {
     
	state.user.name = name
}
  1. 新增一个actions
actions: {
     
    // context:上下文
    aUpdateInfo (context, name) {
     
        setTimeout(() => {
     
        context.commit(UPDATEINFO, 12)
        }, 1000)
    }
}
  1. App.vue中新增一个按钮修改user对象姓名

异步修改的信息:{ { $store.state.user }}

  1. 给按钮新增方法
aUpdateInfo () {
     
	this.$store.dispatch('aUpdateInfo', 'lisi')
}
  1. 点击异步修改信息按钮测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSbDdi8t-1612964660770)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-11.png)]

  • 在点击按钮之后,信息修改了,dev-tools也能跟踪到state的变化。通过$store.dispacth()方法来调用actions,发送异步请求,在actions中需要提交mutation来修改state。
  1. actions回调,在异步操作后,成功或者失败都应该会有回调,$store.dispacth()返回一个Promise对象,修改actions,返回一个Promise对象,成功调用resolve(msg),将成功的msg传入(不理解的请看一下18章的Promise对象详解)。

    actions: {
           
        // context:上下文
        aUpdateInfo (context, name) {
           
            let msg = '响应成功'
            return new Promise((resolve, reject) => {
           
                setTimeout(() => {
           
                    context.commit(UPDATEINFO, 12)
                    resolve(msg)
                }, 1000)
            })
        }
    }
    
  2. 修改aUpdateInfo()方法,获取回调参数msg,此时的response就是actions中回调的msg,也可以支持失败的回调,只要actions中使用了reject,在aUpdateInfo()方法中catch回调结果就能获取resjct对象回传结果。

    aUpdateInfo () {
           
        this.$store.dispatch('aUpdateInfo', 'lisi').then(response => {
           
            console.log(response)
        })
    }
    
  3. 再次点击异步修改信息,打印结果信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqqxpQaK-1612964660771)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-12.png)]

Actions 支持同样的载荷方式(payload)和对象方式进行分发

// 以载荷形式分发
store.dispatch('aUpdateInfo', {
     
  name: 'lisi'
})

// 以对象形式分发
store.dispatch({
     
  type: 'aUpdateInfo',
  name: 'lisi'
})

moudules(模块)

  • 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

  • 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

  • 比如这样

const moduleA = {
     
  state: () => ({
      ... }),
  mutations: {
      ... },
  actions: {
      ... },
  getters: {
      ... }
}

const moduleB = {
     
  state: () => ({
      ... }),
  mutations: {
      ... },
  actions: {
      ... }
}

const store = new Vuex.Store({
     
  modules: {
     
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状

模块的局部状态

  1. 模块内部的mutation 和 getter,接收的第一个参数是模块的局部状态对象

  2. 模块内部的 action,局部状态是 context.state根节点状态则为 context.rootState

  3. 对于模块内部的 getter,第三个参数是根节点状态。

    const moduleA = {
           
      state: () => ({
           
        count: 0
      }),
      mutations: {
           
        increment (state) {
           
          // 这里的 `state` 对象是模块的局部状态
          state.count++
        }
      },
      actions: {
           
        incrementIfOddOnRootSum (context) {
           
          if ((context.state.count + context.rootState.count) % 2 === 1) {
           
            context.commit('increment')
          }
        }
      },
      getters: {
           
        doubleCount (state, getters, rootState) {
           
          console.log(rootState.count) // 获取的是根状态的count
          return state.count * 2
        }
      }
    }
    

    注意actions的context

    actions: {
           
        incrementIfOddOnRootSum ({
            state, commit, rootState }) {
           
            if ((context.state.count + context.rootState.count) % 2 === 1) {
           
                context.commit('increment')
            }
        }
    }
    

    { state, commit, rootState }对应context对象中的属性,使用ES6的对象解构

项目结构

  • Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。
  • 只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

  • 对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

(二十)Axios

20.1 为什么选择Axios

  • 可以在浏览器发送XMH请求
  • 在nodejs发送http请求
  • 支持Promise API
  • 拦截请求和响应
  • 转换请求和响应数据

补充

axios:ajax i/o system 理解

20.2 axios框架基本使用

多种请求方式

  • axios( config )
  • axios.request(config)
  • axios.get(url [,config])
  • axios.delete(url [,config])
  • axios.head(url [,config])
  • axios.post(url [,data[, config]])
  • axios.put(url [,data[, config]])
  • axios.patch(url [,data[, config]])

axios()

// 默认使用GET方法
axios({
     
  url: 'http://123.207.32.32:8000/home/multidata',
  method: 'GET'
}).then(res => {
     
  console.log(res)
})

get请求——参数拼接

axios({
     
  url: 'http://123.207.32.32:8000/home/multidata',
  method: 'GET',
  params: {
     
    type: 'pop',
    page: 1
  }
}).then(res => {
     
  console.log(res)
})

20.3 axios发送并发请求

ajax: Promise.all ([])

axios: axios.all ([])

格式

axios.all([axios(),axios()]).then(res => {
     })

axios.all([
  axios({
     
    url: 'http://123.207.32.32:8000/home/multidata'
  }),
  axios({
     
    url: 'http://123.207.32.32:8000/home/data'
  })
])
.then(res => {
     
  console.log(res)
})

分割结果

axios.all([
  axios.get('http://123.207.32.32:8000/home/multidata'),
  axios.get('http://123.207.32.32:8000/home/data',{
     
     params: {
      type: 'shell', page: 1 }})])
.then(axios.spread((res1, res2) => {
     
  console.log(res1)
  console.log(res2)
}))

20.4 axios的配置信息

  • 上面的案例中,BaseURL是固定的
    • 在开发中很多参数都是固定的
    • 这个时候可以进行一些抽取,也可以利用axios的全局配置
    • defaults属性

实例

axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 1000
// 全局配置
axios.defaults.baseURL = 'http://123.207.32.32:8000'

// 并发请求
axios.all([
  axios({
     
    url: '/home/multidata'
  }),
  axios({
     
    url: '/home/data'
  })
])
.then(res => {
     
  console.log(res)
})

常见配置选项

  • 请求地址

    • url:‘/user’
  • 请求类型

    • method:‘get’
  • 请求路径

    • baseURL:‘http://www.xxx.com/api’
  • 请求前的数据处理

    • transformRequest: [function(data){}]
  • URL查询对象

    • params: { id:12 }

20.5 axios的实例和模块封装

创建axios实例

const instance1 =  axios.create({
     
  baseURL: 'http://123.207.32.32:8000',
  timeout: 5000
})

// 使用
instance1({
     
  url: '/home/multidata'
}).then(res => {
     
  console.log(res)
})

instance1({
     
  url: 'home/data',
  params: {
      
    type: 'pop',
    page: 1
  }
}).then(res => {
     
  console.log(res)
})

每个实例可以看成不同的服务器。

封装成单独文件

分析封装的优点

  • 第一次封装
export function request (config, success, failure) {
     
  // 1.创建axios实例
  const instance = axios.creat({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  instance(config)
    .then(res => {
     
      success(res)
    })
    .catch(err => {
     
      failure(err)
    })
}

// 其他文件调用
import {
      request } from './network/request.js'

request({
     
  url: '/home/data'
}, res => {
     
  console.log(res)
}, err => {
     
  console.log(err)
})

  • 第二次封装
export function request (config) {
     
  // 1.创建axios实例
  const instance = axios.creat({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  instance(config.baseConfig)
    .then(res => {
     
      config.success(res)
    })
    .catch(err => {
     
      config.failure(err)
    })
}


// 调用
request({
     
  baseConfig: {
     

  },
  success: function (res) {
     

  },
  failure: function (ree) {
     
    console.log(err)
  }
})

  • 方案三
export function request (config) {
     
  return new Promise((resolve, reject) => {
     
    // 1.创建axios实例
    const instance = axios.create({
     
      baseURL: 'http://123.207.32.32:8000',
      timeout: 5000
    })
    
    // 2.发送请求
    instance(config)
      .then(res => {
     
        resolve(res)
      })
      .catch(err => {
     
        reject(err)
      })
  })
}

// 调用
request({
     
  url: '/home/data'
}).then(res => {
     
  console.log(res)
}).catch(err => {
     
  console.log(err)
})

  • 最终方案
export function request (config) {
     
  // 1.创建axios实例
  const instance = axios.create({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })
  
  // 2.发送请求
  return instance(config)
}

// 调用
request({
     
  url: '/home/data'
}).then(res => {
     
  console.log(res)
}).catch(err => {
     
  console.log(err)
})

20.6 axios拦截器使用

  • 作用

    • 用于在发送每次请求或者得到响应后,进行对应处理
  • 使用

    • 请求拦截——成功、失败

      instance.interceptors.request

    • 响应拦截——成功、失败

      instance.interceptors.response

// 3.拦截器
  instance.interceptors.request.use(config => {
     
    console.log(config)
    // (1)处理数据、信息转化
    // (2)比如每次发送网络请求时,希望在界面显示一个请求图标
    // (3)某些网络请求必须携带一些特殊信息(token)
    return config  // 拦截后需要还回去
  }, err => {
     
    console.log(err)
  })

  instance.interceptors.response.use(res => {
     
    console.log(res)
    // 取出数据
  }, err => {
     
    console.log(err)
  })

(二十一)Better Scroll

21.1 基本使用

安装

使用

// js代码
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
     
    
})

<div class="content">
  <ul>
    <li>li>
    ...
  ul>
div>

21.2 参数

const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
     
  probeType: 2
})

probeType

实时侦测滚动位置,默认值0

    • 0/1都不监听
    • 2:手指滚动过程中侦测,离开后惯性滚动不侦测
    • 3:只要滚动都侦测

pullUpLoad

上拉加载功能

    • Boolean
    • object

useTransition

解决滑动时字体变模糊问题

    • true

21.3 BScroll可滚动区域问题

  • BScroll在决定有多少区域可以滚动时,是根据scrollerHeight属性决定

    • scrollerHeight属性是根据放BScroll的content中的子组件高度决定
    • 但是首页中,刚开始计算scrollerHeight属性时,是没有将图片计算在内的
    • 所以计算出来的高度是错误的
    • 后来图片加载进来后有了新高度,但是scrollerHeight属性没有进行更新
    • 所以出现了滚动问题
  • 解决

    • 监听每一张图片是否加载完成,只要有一张图片加载完成了,执行一次refresh

    • 如何监听图片加载完成了?

      • 原生js监听图片:img.onload = function(){}
      • Vue中监听:@load = ‘方法’
    • 调用scroll的refresh()

      • 隔太远,不好传值
      • 解决
        • Vuex
        • 事件总线
  • 事件总线 $bus

    • 作用:管理事件

    • 区别Vuex管理状态

    • 使用

      • Vue.prototype.$bus = new Vue()

      • goodslistitem发射事件

        • this.$bus.$emit('itemImageLoad')
      • home监听该事件

        • this.$bus.on('itemImageLoad', () => {})

防抖函数

  • 优化:refresh调用次数过多

防抖:在规定时间内执行多次事件只执行最后一次事件

  • 防抖debounce / 节流throttle

  • 作用过程

    • 如果直接执行refresh,那么refresh函数会被执行30次
    • 可以将refresh函数传入到debounce函数中,生成一个新的函数

商城项目1.0

Project setup

npm install

Compies and hot-reloads for development

npm run serve

Compiles and minifies for production

npm run build

目录结构

-  ..-	public/-  favicon.ico			// ico-	index.html
├-	src/-	assets/				// 项目资源- css/			
    			- normalize.css // 第三方统一样式
				- base.css		// 自定义统一样式- img/-	components			// 公共组件- common/		// 纯公共组件(扩展其他项目)- navbar/	// 公共导航条组件
               		- NavBar.vue
               ├- tabbar/ 	// 底部导航栏组件	
                   	- TabBar.vue
					- TabBarItem.vue
               ├- swiper/   // 轮播图组件	
                   	- index.js
					- Swiper.vue
					- SwiperItem.vue
           ├- content/		// 当前项目公共组件- mainTabBar// 底部导航栏(业务版)
					- MainTabBar.vue
    ├-	common/    			// 公共js文件-  network/			// 网络请求文件- home.js		// 主页网络请求文件封装- request.js	// 全局网络请求封装-	router/				// 路由文件- index.js		
    ├-  store/				// 状态管理文件-	views/				// 页面视图- category/- Category.vue // 分类页面主组件- home/			// 主页视图- childComps// 主页功能小组件
					- HomeSwiper.vue
			   ├- Home.vue  // home主组件- profile/- Profile.vue// 个人页面主组件- shotcart/- Shotcart.vue// 购物车主组件-	App.vue				// root组件- 	main.js				// 启动文件-	vue.config.js			// 自定义配置文件-	.gitignore				// git提交忽略文件-	babel.config.js
├-	package.json			// 版本控制-	package-lock.json		// 版本锁-	LICENSE					// 许可-	README.md				// what u see-	

步骤

  • 导入base.css样式
    • normalize.css
  • 配置文件路径别名
    • vue.config.js,必须与src文件同目录
    • 默认有‘@’:’src‘
  • 配置代码风格文件
    • .editorconfig
  • 导航模块划分
    • tabbar -> 路由映射关系
  • 首页开发
    • navbar封装
    • 网络数据的请求
    • 轮播图
    • 推荐信息

附录

[1] 首页保存商品的数据结构设计

goods(流行/新款/精选)

goods: {
      
'pop': {
      page: 5, list: [150]},
'news': {
      page: 1, list: [90]},
'sell': {
      page: 1, list: [30]}
}

附录

tabControl的吸顶效果

获取到tabControl的offsetTop

  • 必须知道滚动到多少时,开始有吸顶效果,这个时候就需要获取tabControl的offsetTop
  • 但是,如果直接在mounted中获取tabControl的offsetTop,那么值不正确
  • 如何获取正确的值?
    • 监听HomeSwiper中的img的加载完成
    • 加载完成后。发出事件,在home.vue中,获取正确的值
    • 补充:为了不让homeSwiper多次发出事件,使用isLoad变量进行记录状态
      • 注意:这里不进行多次调用和debounce的区别

监听滚动,动态改变TabControl的样式

  • 问题一:下面的商品内容会突然上移
  • 问题二:tabControl虽然设置了fixed,都是也随着BS一起滚出去了
  • 其他方案
    • 在最上面,复制了一份TabControl组件对象,利用它来实现停留效果
    • 当用户滚动到一定位置时,TabControl显示出来
    • 当用户滚动没有到达一定位置时,隐藏起来

让Home保持原来状态

让Home不要随意销毁掉

  • keep-alive

让home中保持原来位置

FastClick点击事件优化

图片懒加载 vue-lazyload库

基本使用

  • 安装

npm i vue-lazyload

  • 在main.js导入

    • import VueLazyLoad from 'vue-lazyload'
      
    • Vue.use(VueLazyLoad)
      
  • 修改img

    • :scr→ v-lazy

参数

  • 占位图:loading

CSS单位转化插件 px2vw

部署

  • windows-Nginx
    # 我们组装模块并导出 store 的地方
    ├── actions.js # 根级别的 action
    ├── mutations.js # 根级别的 mutation
    └── modules
    ├── cart.js # 购物车模块
    └── products.js # 产品模块







# (二十)Axios

## 20.1	为什么选择Axios

- 可以在浏览器发送XMH请求
- 在nodejs发送http请求
- 支持Promise API
- 拦截请求和响应
- 转换请求和响应数据

>  补充
>
>  axios:ajax i/o system 理解



## 20.2	axios框架基本使用

### 多种请求方式

- axios( config )
- axios.request(config)
- axios.get(url [,config])
- axios.delete(url [,config])
- axios.head(url [,config])
- axios.post(url [,data[, config]])
- axios.put(url [,data[, config]])
- axios.patch(url [,data[, config]])

### axios()

```javascript
// 默认使用GET方法
axios({
  url: 'http://123.207.32.32:8000/home/multidata',
  method: 'GET'
}).then(res => {
  console.log(res)
})

get请求——参数拼接

axios({
     
  url: 'http://123.207.32.32:8000/home/multidata',
  method: 'GET',
  params: {
     
    type: 'pop',
    page: 1
  }
}).then(res => {
     
  console.log(res)
})

20.3 axios发送并发请求

ajax: Promise.all ([])

axios: axios.all ([])

格式

axios.all([axios(),axios()]).then(res => {
     })

axios.all([
  axios({
     
    url: 'http://123.207.32.32:8000/home/multidata'
  }),
  axios({
     
    url: 'http://123.207.32.32:8000/home/data'
  })
])
.then(res => {
     
  console.log(res)
})

分割结果

axios.all([
  axios.get('http://123.207.32.32:8000/home/multidata'),
  axios.get('http://123.207.32.32:8000/home/data',{
     
     params: {
      type: 'shell', page: 1 }})])
.then(axios.spread((res1, res2) => {
     
  console.log(res1)
  console.log(res2)
}))

20.4 axios的配置信息

  • 上面的案例中,BaseURL是固定的
    • 在开发中很多参数都是固定的
    • 这个时候可以进行一些抽取,也可以利用axios的全局配置
    • defaults属性

实例

axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 1000
// 全局配置
axios.defaults.baseURL = 'http://123.207.32.32:8000'

// 并发请求
axios.all([
  axios({
     
    url: '/home/multidata'
  }),
  axios({
     
    url: '/home/data'
  })
])
.then(res => {
     
  console.log(res)
})

常见配置选项

  • 请求地址

    • url:‘/user’
  • 请求类型

    • method:‘get’
  • 请求路径

    • baseURL:‘http://www.xxx.com/api’
  • 请求前的数据处理

    • transformRequest: [function(data){}]
  • URL查询对象

    • params: { id:12 }

20.5 axios的实例和模块封装

创建axios实例

const instance1 =  axios.create({
     
  baseURL: 'http://123.207.32.32:8000',
  timeout: 5000
})

// 使用
instance1({
     
  url: '/home/multidata'
}).then(res => {
     
  console.log(res)
})

instance1({
     
  url: 'home/data',
  params: {
      
    type: 'pop',
    page: 1
  }
}).then(res => {
     
  console.log(res)
})

每个实例可以看成不同的服务器。

封装成单独文件

分析封装的优点

  • 第一次封装
export function request (config, success, failure) {
     
  // 1.创建axios实例
  const instance = axios.creat({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  instance(config)
    .then(res => {
     
      success(res)
    })
    .catch(err => {
     
      failure(err)
    })
}

// 其他文件调用
import {
      request } from './network/request.js'

request({
     
  url: '/home/data'
}, res => {
     
  console.log(res)
}, err => {
     
  console.log(err)
})

  • 第二次封装
export function request (config) {
     
  // 1.创建axios实例
  const instance = axios.creat({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })

  instance(config.baseConfig)
    .then(res => {
     
      config.success(res)
    })
    .catch(err => {
     
      config.failure(err)
    })
}


// 调用
request({
     
  baseConfig: {
     

  },
  success: function (res) {
     

  },
  failure: function (ree) {
     
    console.log(err)
  }
})

  • 方案三
export function request (config) {
     
  return new Promise((resolve, reject) => {
     
    // 1.创建axios实例
    const instance = axios.create({
     
      baseURL: 'http://123.207.32.32:8000',
      timeout: 5000
    })
    
    // 2.发送请求
    instance(config)
      .then(res => {
     
        resolve(res)
      })
      .catch(err => {
     
        reject(err)
      })
  })
}

// 调用
request({
     
  url: '/home/data'
}).then(res => {
     
  console.log(res)
}).catch(err => {
     
  console.log(err)
})

  • 最终方案
export function request (config) {
     
  // 1.创建axios实例
  const instance = axios.create({
     
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })
  
  // 2.发送请求
  return instance(config)
}

// 调用
request({
     
  url: '/home/data'
}).then(res => {
     
  console.log(res)
}).catch(err => {
     
  console.log(err)
})

20.6 axios拦截器使用

  • 作用

    • 用于在发送每次请求或者得到响应后,进行对应处理
  • 使用

    • 请求拦截——成功、失败

      instance.interceptors.request

    • 响应拦截——成功、失败

      instance.interceptors.response

// 3.拦截器
  instance.interceptors.request.use(config => {
     
    console.log(config)
    // (1)处理数据、信息转化
    // (2)比如每次发送网络请求时,希望在界面显示一个请求图标
    // (3)某些网络请求必须携带一些特殊信息(token)
    return config  // 拦截后需要还回去
  }, err => {
     
    console.log(err)
  })

  instance.interceptors.response.use(res => {
     
    console.log(res)
    // 取出数据
  }, err => {
     
    console.log(err)
  })

(二十一)Better Scroll

21.1 基本使用

安装

使用

// js代码
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
     
    
})

<div class="content">
  <ul>
    <li>li>
    ...
  ul>
div>

21.2 参数

const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
     
  probeType: 2
})

probeType

实时侦测滚动位置,默认值0

    • 0/1都不监听
    • 2:手指滚动过程中侦测,离开后惯性滚动不侦测
    • 3:只要滚动都侦测

pullUpLoad

上拉加载功能

    • Boolean
    • object

useTransition

解决滑动时字体变模糊问题

    • true

21.3 BScroll可滚动区域问题

  • BScroll在决定有多少区域可以滚动时,是根据scrollerHeight属性决定

    • scrollerHeight属性是根据放BScroll的content中的子组件高度决定
    • 但是首页中,刚开始计算scrollerHeight属性时,是没有将图片计算在内的
    • 所以计算出来的高度是错误的
    • 后来图片加载进来后有了新高度,但是scrollerHeight属性没有进行更新
    • 所以出现了滚动问题
  • 解决

    • 监听每一张图片是否加载完成,只要有一张图片加载完成了,执行一次refresh

    • 如何监听图片加载完成了?

      • 原生js监听图片:img.onload = function(){}
      • Vue中监听:@load = ‘方法’
    • 调用scroll的refresh()

      • 隔太远,不好传值
      • 解决
        • Vuex
        • 事件总线
  • 事件总线 $bus

    • 作用:管理事件

    • 区别Vuex管理状态

    • 使用

      • Vue.prototype.$bus = new Vue()

      • goodslistitem发射事件

        • this.$bus.$emit('itemImageLoad')
      • home监听该事件

        • this.$bus.on('itemImageLoad', () => {})

防抖函数

  • 优化:refresh调用次数过多

防抖:在规定时间内执行多次事件只执行最后一次事件

  • 防抖debounce / 节流throttle

  • 作用过程

    • 如果直接执行refresh,那么refresh函数会被执行30次
    • 可以将refresh函数传入到debounce函数中,生成一个新的函数

商城项目1.0

Project setup

npm install

Compies and hot-reloads for development

npm run serve

Compiles and minifies for production

npm run build

目录结构

-  ..-	public/-  favicon.ico			// ico-	index.html
├-	src/-	assets/				// 项目资源- css/			
    			- normalize.css // 第三方统一样式
				- base.css		// 自定义统一样式- img/-	components			// 公共组件- common/		// 纯公共组件(扩展其他项目)- navbar/	// 公共导航条组件
               		- NavBar.vue
               ├- tabbar/ 	// 底部导航栏组件	
                   	- TabBar.vue
					- TabBarItem.vue
               ├- swiper/   // 轮播图组件	
                   	- index.js
					- Swiper.vue
					- SwiperItem.vue
           ├- content/		// 当前项目公共组件- mainTabBar// 底部导航栏(业务版)
					- MainTabBar.vue
    ├-	common/    			// 公共js文件-  network/			// 网络请求文件- home.js		// 主页网络请求文件封装- request.js	// 全局网络请求封装-	router/				// 路由文件- index.js		
    ├-  store/				// 状态管理文件-	views/				// 页面视图- category/- Category.vue // 分类页面主组件- home/			// 主页视图- childComps// 主页功能小组件
					- HomeSwiper.vue
			   ├- Home.vue  // home主组件- profile/- Profile.vue// 个人页面主组件- shotcart/- Shotcart.vue// 购物车主组件-	App.vue				// root组件- 	main.js				// 启动文件-	vue.config.js			// 自定义配置文件-	.gitignore				// git提交忽略文件-	babel.config.js
├-	package.json			// 版本控制-	package-lock.json		// 版本锁-	LICENSE					// 许可-	README.md				// what u see-	

步骤

  • 导入base.css样式
    • normalize.css
  • 配置文件路径别名
    • vue.config.js,必须与src文件同目录
    • 默认有‘@’:’src‘
  • 配置代码风格文件
    • .editorconfig
  • 导航模块划分
    • tabbar -> 路由映射关系
  • 首页开发
    • navbar封装
    • 网络数据的请求
    • 轮播图
    • 推荐信息

附录

[1] 首页保存商品的数据结构设计

goods(流行/新款/精选)

goods: {
      
'pop': {
      page: 5, list: [150]},
'news': {
      page: 1, list: [90]},
'sell': {
      page: 1, list: [30]}
}

附录

tabControl的吸顶效果

获取到tabControl的offsetTop

  • 必须知道滚动到多少时,开始有吸顶效果,这个时候就需要获取tabControl的offsetTop
  • 但是,如果直接在mounted中获取tabControl的offsetTop,那么值不正确
  • 如何获取正确的值?
    • 监听HomeSwiper中的img的加载完成
    • 加载完成后。发出事件,在home.vue中,获取正确的值
    • 补充:为了不让homeSwiper多次发出事件,使用isLoad变量进行记录状态
      • 注意:这里不进行多次调用和debounce的区别

监听滚动,动态改变TabControl的样式

  • 问题一:下面的商品内容会突然上移
  • 问题二:tabControl虽然设置了fixed,都是也随着BS一起滚出去了
  • 其他方案
    • 在最上面,复制了一份TabControl组件对象,利用它来实现停留效果
    • 当用户滚动到一定位置时,TabControl显示出来
    • 当用户滚动没有到达一定位置时,隐藏起来

让Home保持原来状态

让Home不要随意销毁掉

  • keep-alive

让home中保持原来位置

FastClick点击事件优化

图片懒加载 vue-lazyload库

基本使用

  • 安装

npm i vue-lazyload

  • 在main.js导入

    • import VueLazyLoad from 'vue-lazyload'
      
    • Vue.use(VueLazyLoad)
      
  • 修改img

    • :scr→ v-lazy

参数

  • 占位图:loading

CSS单位转化插件 px2vw

部署

  • windows-Nginx
  • centos-Nginx

你可能感兴趣的:(实用推荐,web网站,javascript,vue.js,es6,webpack,前端)