Vue.js 修炼秘籍

192 阅读9分钟

第一章节 初始 Vue.js

  • Vue 是一套用于构建用户界面的渐进式前端框架
  • 易学易用,性能出色,适用场景丰富的 Web 前端框架

Vue 的特点:

- 采用组件化模式,提高代码复用性,且让代码更好维护
- 声明式编码,让编码人员无需直接直接操作 DOM,提高开发效率
- 在使用了 vue 的页面中, vue 会监听数据的变化,从而自动重新渲染页面的结构
- 注意: 数据驱动视图是单向的数据绑定
- 双向数据绑定,可以辅助开发者在不操作 DOM 的情况下,自动把用户填写的内容同步到数据源中

- MVVM 是 vue 实现数据驱动视图和数据双向绑定的核心原理
    - Model 表示当前页面渲染时所依赖的数据源 --- 【data中的数据】 
    - View 表示当前页面所渲染的 DOM 结构 ---【模板视图】
    - ViewModel 表示 vue 的实例,它是 MVVM 的核心 ---【Vue实例对象】

    - ViewModel 作为 MVVM的核心,它把当前页面的数据源【Model】和页面的结构【View】连接在了一起
        - 当数据源发生变化时,会被 ViewModel 监听到, 它会根据最新的数据源自动更新页面中的结构
        - 当表单元素的值发生改变时,也会被ViewModel监听到,会把变化过后的最新值自动同步到 Model 数据源中

- vue 中的数据代理,通过 VM 实例对象来进行代理 data 对象中属性的操作【读/写】
    - 它的基本原理,通过 Object.defineProperty() 把 data 对象中所有属性添加到 VM 实例上
        为每一个添加到 vm 实例上的属性,都指定一个 getter/setter
        在 getter/setter 内部去操作【读/写】data 中对应的属性

第二章: 走入Vue.js 的世界

  • 使用步骤
    • 在Vue官网下载好 vue.js 的脚本文件 【开发版本】
    • 导入 vue.js 的文件
    • 在页面中声明一个将要被 vue 所控制的 DOM 区域
    • 创建 VM 实例对象【vue 实例对象】
<body>
    <!-- vue 控制的区域内容 -->
    <div id="app">
        <h1>{{username}}</h1>
    </div>

    <!-- 导入 vue.js 文件 -->
    <script src="./lib/vue.js"></script>
    <!-- 创建 Vue 的实例对象 -->
    <script>
        Vue.config.productionTip = false // 阻止 vue 启动时产生的提示

        const vm = new Vue({
            // el 表示是需要挂载的页面中的哪个区域
            el: "#app",
            // data 对象表示就是需要渲染早页面上的数据
            data: {
                username: '好好努力,必将成功!!!'
            }
        })
    </script>
</body>

指令的概念:

- 指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构
内部渲染指令:
  • 内部渲染指令用来辅助开发者渲染 DOM 元素的文本内容
    • v-text 文本渲染指令,它只会进行文本的渲染,会覆盖元素内部的原有内容
    • {{ }} 插值表达式,专门用来解决v-text会覆盖默认文本的问题,可以写任何合法的JS表达式
      • 注意:在使用插值表达式的时候会有一个小小的问题,在插值表达式渲染内容的时候会存储内容闪烁的问题,因此可以使用 v-cloak 指令进行解决
      • 最后需要在样式表中写入: [v-cloak] {display:none} 即可
    • v-html 渲染 HTML 标签中的内容,注意:有安全性的问题
属性绑定指令
- 如果需要为元素的属性动态绑定属性值,则需要用的 v-bind 指令绑定属性【可以简写为 :】
事件绑定指令
  - vue 提供了v-on 事件绑定指令,用来为 DOM 元素绑定事件监听【可以简写为 @】
  • 事件修饰符
    • vue 提供了 事件修饰符的概念,对事件的触发进行控制,常见的事件修饰符如下:
    • .prevent 阻止默认行为
    • .stop 阻止事件冒泡
    • .capture 以捕获模式触发当前的事件处理函数
    • .once 绑定的事件只触发一次
    • .self 只有在 event.target 是当前元素自身时触发事件处理函数
  • 按键修饰符
    • 1、.enter,可捕获enter键
    • 2、.tab,可捕获tab键;
    • 3、.delete,可捕获“删除”和“退格”按键;
    • 4、.esc,可捕获取消键;
    • 5、.space,可捕获空格键
    • 6、.up等
      • Vue.config.keyCodes.自定义按键名 = keycode码, 可以控制按键的别名
双向绑定指令
  - vue 提供了 **v-model** 双向数据绑定指令,用于在不操作 DOM 的前提下,快速获取表单数据
  • v-model 指令修饰符
    • .number 自动将用户输入的值转为数值类型
    • .trim 自动过滤用户输入的首尾空白字符
    • .lazy 在"change"时,而非"input"时更新
      • 注意:只有表单元素才可以使用 v-model 指令
条件渲染指令
 - 条件渲染指令用来辅助开发者按需控制 DOM 的显示和隐藏
  • v-if 每次动态创建或移除元素,来实现元素的显示或移除 - 如果刚进入页面时,某些元素默认不需要被展示,而且后期也很少会进行展示,那么使用 v-if 性能会更好

    • v-else-if 顾名思义,用于充当 v-if 的“else-if”块 指令必须结合 v-if 指令进行使用

    • v-show 动态的为元素添加或移除 display:none 样式,来实现元素的显示或隐藏

      • 如果需要频繁的切换元素的显示状态,则用 v-show 性能可能会更好
列表渲染指令
- vue 提供了v-for列表渲染指令,用于对列表进行渲染,指令需要使用 item in items 形式的特殊语法
 items 是待循环的数组
 item 是被循环每一项元素
  • 注意: 在使用 v-for 列表渲染的时候必须要绑定 key 属性,它的值必须是唯一的 - key 的值可以是字符串或数字类型 - 建议把数据项的 id 属性作为 key 的唯一标识

章节练习:【指令练习】

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>指令学习</title>
    <link rel="stylesheet" href="./css/bootstrap.css">
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1 v-cloak>{{userName}}</h1>
        <input type="text" v-model="userName">
        <h2 v-text="text"></h2>
        <div v-html="info"></div>
        <input type="text" :placeholder="tips">
        <p v-cloak>count: {{count}}</p>
        <button @click="add($event,1)">点击+1</button>
        <p v-if="flag">这是被v-if控制的元素</p>
        <p v-show="flag">这是被v-show控制的元素</p>
        <hr />
        <p v-if="score === 'A'">优秀</p>
        <p v-else-if="score === 'B'">良好</p>
        <p v-else-if="score === 'C'">一般</p>
        <p v-else=>差劲</p>
        <hr />
        <table class="table table-bordered table-hover table-striped">
           <thead>
               <th>id</th>
               <th>名称</th>
               <th>价格</th>
           </thead>
           <tbody>
               <tr v-for="item in list" :key="item.id">
                   <td>{{item.id}}</td>
                   <td>{{item.name}}</td>
                   <td>{{item.price}}</td>
               </tr>
           </tbody>
        </table>
    </div>
    <script src="./lib/vue.js"></script>

    <script>
        Vue.config.productionTip = false // 阻止 vue 启动时产生的提示
        const vm = new Vue({
            el: '#app', // 用来挂载页面上 vue 用于操作的区域容器节点
            data: { // data 对象用于存储渲染在页面上数据
                userName: '富贵',
                text: '花开富贵',
                info: '<h2>好远连连,花开富贵</h2>',
                tips: '输入用户名',
                count: 0,
                // 如果 flag 为 true,则显示元素,为false则隐藏元素
                flag: true,
                score: "A",
                list: [
                    {id: '001', name: '华为', price: '¥6888.88'},
                    {id: '002', name: '小米', price: '¥1988.88'},
                    {id: '003', name: '苹果', price: '¥9999.99'}
                ]
            },
            methods: { // methods 用于定义事件的处理函数
                add(event,n) {
                    this.count += n
                    event.target.style.backgroundColor = 'red'
                }
            }
        })
    </script>
</body>
</html>
自定义指令
  • 在 Vue 中可以通过 directives 节点下声明定义私有自定义指令
    • 有以下回调函数:【参数:element, binding】
      • bind:只调用一次,指令第一次绑定到元素时调用
      • inserted: 当绑定的元素插入到父节点时调用
      • update: 所在组件的 VNode 更新时调用
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <!-- 
        1. 需求1:自定义一个指令,v-big 和 v-text 功能类似,但是会把绑定的数值放大10倍
     -->
    <div id="app">
        <h1 v-cloak>当前值:<span v-text="n"></span></h1>
        <h1 v-cloak>放大10倍后:<span v-big="n"></span></h1>
        <button @click="n++">点击+1</button>
        <hr />
        <input type="text" v-fbind:value="n">
    </div>
    <script src="./lib/vue.js"></script>
    <script>
        Vue.config.productionTip = false

        // 全局 自定义指令
       /*  Vue.directive('指令名称', function(element,binding){

        }) */

        const vm = new Vue({
            el: "#app",
            data: {
                n: 1
            },
            // 定义私有 自定义指令
            directives: {
                // 指定绑定成功时调用, 指定所在的模板被重新解析时
                big(element, binding) {
                    element.innerText = binding.value * 10
                },
                fbind: {
                    // 指令与元素绑定成功时调用
                    bind(element, binding) {
                        element.value = binding.value
                    },
                    // 指令所在元素被插入页面时调用
                    inserted(element, binding) {
                        element.focus()
                    },
                    // 指令所在的模板被重新解析时调用
                    update(element, binding) {
                        element.value = binding.value
                    }
                }
            }
        })
    </script>
</body>

</html>

章节三 生命周期【过滤器,计算属性】学习

过滤器

  • 过滤器【filters】 是 vue 为开发者提供的功能,常用于 文本的格式化操作,可以用在【差值表达式、属性绑定】
  • 过滤器被添加在 JS 表达式的尾部,通过管道符进行调用,用法如下: 【过滤器中需要有返回值】
  • 注意: 在 Vue3.x 版本中剔除了过滤器的相关功能,官方建议使用【计算属性、方法】代替
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <div id="app">
        <!--使用过滤器 | 过滤器名称 -->
        <p v-cloak>{{message | capital}}</p>
    </div>
    <script src="./lib/vue.js"></script>
    <script>
        Vue.config.productionTip = false

        // 定义全局过滤器
        Vue.filter('capital', (str) => {
            const first = str.charAt(0).toUpperCase()
            const other = str.slice(1)
            return first + other
        })

        const vm = new Vue({
            el: '#app',
            data: {
                message: 'hello vue.js'
            },
            methods: {},
            // 过滤器函数必须定义在 filters 节点之下
            /* filters: {
                
                // 过滤器中,一定要有返回值
                // 过滤器中的新式参数,永远都是“管道符”前面的那个值
                capital(val) {
                    const first = val.charAt(0).toUpperCase()
                    const other = val.slice(1)
                    return first + other
                }
            } */
        })
    </script>
</body>

</html>

侦听器【watch】

- watch 侦听器 允许开发者监视数据的变化,从而针对数据的变化做特定的操作

- 当被侦听的属性变化时,回调函数自动调用,进行相关操作
- 侦听的属性必须存在,才能进行监视!!
- 侦听器的两种写法:
    - new Vue 实例时传入 watch 配置
    - 通过 vm.$watch 监视

- immediate 选项,控制侦听器是否自动触发一次【false、true】

- Vue 中的 watch 默认是不检测对象内部值的改变
    - 可以通过 deep 选项,让侦听器监听对象中每个属性的变化
 
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
   <style>
       [v-cloak] {
           display: none;
       }
   </style>
</head>
<body>
   <div id="app">
       <h2 v-cloak>今天天气很{{info}}</h2>
       <button @click="changeWeather">切换天气</button>
   </div>
   <script src="./lib/vue.js"></script>
   <script>
       Vue.config.productionTip = false
       new Vue({
           el: "#app",
           data:{
              isHot: true
           },
           methods: {
               changeWeather() {
                   this.isHot = !this.isHot
               }
           },
           computed: {
               info() {
                   return this.isHot ? '炎热' : '凉爽'
               }
           },
           watch: {
               isHot: {
                   immediate: true, // 初始化时让 handler 调用一下
                   // handler 什么时候调用? 当 isHot 发生改变时
                   handler(newValue,oldValue) {
                       console.log('isHot被修改了:', newValue, oldValue)
                   }
               }
               /* 
                   简写 
                   isHot(newValue,odlValue) {
                       console.log('isHot被修改了:', newValue, oldValue)
                   }
               */
           }
       })
   </script>
</body>
</html>

计算属性【computed】

 - 定义: 要用的属性不存在,要通过已有属性计算得来
- 原理: 底层借助了 Object.defineProperty() 方法提供的 getter 和 setter

- get 函数什么时候被执行?
    - 初次读取时会执行一次
    - 当依赖的数据发生变化时会被再次调用

- 优势: 与 methods 实现相比,内部有缓存机制, 效率更高【调用方便】

- 计算属性最终会被渲染到 vm 实例上,直接读取使用即可
- 如果计算属性要被修改,那么必须写 set 函数去响应修改后的结果,且 set 中要引起计算时依赖的数据发生改变
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="app">
        值1: <input type="text" v-model="num1"><br/><br/>
        值2: <input type="text" v-model="num2"><br/><br/>
        结果: <span v-cloak>{{count}}</span>
    </div>
    <script src="./lib/vue.js"></script>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: "#app",
            data: {
                num1: '',
                num2: ''
            },
            computed: {
                count: {
                    // get 有什么作用? 当 count 被读取时,get 就会被触发调用,且返回值就作为 count的值
                    // get 什么时候被调用? 1. 初次读取 count 时, 2. 所依赖的数据发生变化时
                    get() {
                        console.log('被调用了')
                        let result = parseFloat(this.num1) + parseFloat(this.num2) || ''
                        return result
                    },
                    // set 什么时候调用? 当 count 被修改时
                    set(value) {
                        console.log('set',value)
                        const arr = value.split('-')
                        this.num1 = arr[0]
                        this.num2 = arr[1]
                    }
                }
                /* 
                    简写版
                    count() {
                        console.log('被调用了')
                        let result = parseFloat(this.num1) + parseFloat(this.num2) || ''
                        return result
                    }
                */
            }
        })
    </script>
</body>
</html>

样式绑定

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }

        .red {
            color: red;
        }

        .italic {
            font-style: italic;
        }

        .small {
            font-size: 16px;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 为元素绑定数组的类样式 -->
        <h1 :class="['red','small']" v-cloak>{{msg}}</h1>
        <hr />
        <!-- 在数组中使用三元表达式 -->
        <h1 v-cloak :class="['red',flag ? 'italic':'']">{{msg}}</h1>
        <hr />
        <!-- 传递对象作为类样式 -->
        <h1 v-cloak :class="classObj">{{msg}}</h1>
        <hr/>

        <!-- 动态绑定 style 行内样式 -->
        <h1 v-cloak :style="{color: 'red'}">{{msg}}</h1>
    </div>
    <script src="./lib/vue.js"></script>
    <script>
        Vue.config.productionTip = false

        const vm = new Vue({
            el: "#app",
            data: {
                msg: '花开富贵',
                flag: false,
                classObj: { red: true, italic: true }
            }
        })
    </script>
</body>

</html>

生命周期说明

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <h2 v-cloak>当前的值是:{{n}}</h2>
        <button @click="n++">点击+1</button>
        <button @click="bye">销毁vm</button>
    </div>
    <script src="./lib/vue.js"></script>
    <script>
        Vue.config.productionTip = false
        new Vue({
            el: "#app",
            data: {
                n: 1
            },
            methods: {
                bye() {
                    console.log('bye~')
                    this.$destroy()
                }
            },
            // 将要创建 vm 实例之前调用
            beforeCreate() {
                console.log('beforeCreate')
            },
            // 创建完毕 vm 实例后调用
            created() {
                console.log('created')
            },
            // 将要挂载实例时调用
            beforeMount() {
                console.log('beforeMount')
            },
            // 实例挂载完毕时调用
            mounted() {
                console.log('mounted')
            },
            // 将要更新实例之前调用
            beforeUpdate() {
                console.log('beforeUpdate')
            },
            // 实例更新完毕后调用
            updated() {
                console.log('updated')
            },
            // 实例将要销毁前调用
            beforeDestroy() {
                console.log('beforeDestroy')
            },
            // 实例销毁后调用
            destroyed() {
                console.log('destroyed')
            },
        })
    </script>
</body>
</html>
创建阶段的生命周期钩子函数:
- 在 new Vue() 实例之前 会进行 初始化操作:【init Events & Lifecycle】事件、生命周期,但是数据代理并未开始

    - beforeCrate: 实例刚在内存中被创建出来,此时并未初始完毕【没有 data 和 methods 属性】
        - 此时会进入【init injections & reactivity】初始化:数据检测、数据代理

    - created: 实例已经在内存中创建完毕了,此时 vm 中有了data 和 methods 属性也准备就绪,
        【此阶段 Vue 实例开始解析模板,生成虚拟DOM,但是页面还不能显示解析好的内容】

    - beforeMount: 此时已经完成了对模板的编译,但是并未挂载到页面中
        【页面中呈现的是未经过Vue编译的DOM结构,所有对DOM的操作,最终都不会生效】
        【将内存中的虚拟DOM转为真实的DOM插入页面】

    - mounted: 此时,已经将编译好的模板,挂载到了页面指定的容器中进行显示
        【至此初始化过程结束,一般在此执行: 开启定时器,发送网络请求,订阅消息,绑定自定义事件等】

- 更新期间的生命周期钩子函数:
- beforeUpdate: 状态更新之前被执行,此时 data 中的状态值是最新的,但是界面上的数据显示还是之前的
    【还没有进行重新渲染DOM操作,根据新的数据,生成新的虚拟DOM进行比较,最终完成页面的更新[Model--->View 的更新]】

- updated: 实例更新完毕之后调用此函数,此时 data 中的状态值和界面上显示的数据,都已经完成了更新操作【最新值】

- 销毁期间的生命周期钩子函数:
- beforeDestroy: 实例销毁之前调用,此时Vue的实例仍然是可以使用的
    【在此阶段:一般用于做, 关闭定时器、取消订阅消息、解绑自定义事件等收尾操作】
- destroy: Vue 实例销毁后调用


- 常用的生命周期钩子函数:
- mounted: 发送 Ajax 请求,启动定时器、绑定自定义事件、发布订阅消息 等初始化操作
- beforeDestroy: 清除定时器、解绑自定义事件、取消发布订阅消息 等收尾操作
    - 注意: 一般是不会在 beforeDestroy 操作数据,因为在此阶段是不会触发更新流程了

章节四 组件化编程

  • 组件的定义:实现应用中局部功能代码和资源的集合
    • 组件的本质是一个名为:VueComponent 的构造函数,且不是被程序员定义的,是 Vue.extend 生成的
    • 我们只需要写上组件名称,Vue 解析时会帮我们创建此组件的实例对象,既 Vue 帮我们执行的 new VueComponent(options)
    • 特别注意: 每次调用 Vue.extend 返回的都是一个全新的 VueComponent
  • 关于 this 指向:
    • 组件配置中:
      • data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【VueComponent】
    • new Vue() 配置中:
      • data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【Vue实例】

脚手架工具进行Vue的项目创建

  • Vue 脚手架是 Vue官方提供的标准化开发工具【VueCLI】

  • 使用步骤:

      1. 如果说下的速度比较慢,可以使用 npm 淘宝镜像 npm config set registry registry.npm.taobao.org
      1. 全局安装 @vue/cli npm install -g @vue/cli【安装完毕后使用=命令: vue-V 检测是否安装ok】
      1. 切换到需要创建项目的目录,使用命令创建项目: vue create 项目名称
      1. 选择使用的 Vue 的版本,进行创建得到构建

ref 属性 学习

 - 被用来给元素或子组件注册引用信息(id的替代者)
 - 应用在 HTML 标签上获取的是真实 DOM 元素,应用在组件标签上就是该组件的实例对象【VueComponent】
- 使用方式:
    - 打标识: <h1 ref="xxx">内容</h1> 或者 <School ref="xxx" />
    - 获取: this.$refs.xxx
  • ref 案例
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button @click="show">点击显示上方的DOM元素</button>
    <School />
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      msg: '欢迎学习Vue'
    }
  },
  methods: {
    show() {
      console.log(this.$refs.title)
    }
  }
}
</script>

<style>

</style>

props 学习

- props 它的功能,让该组件接收外部传递过来的数据
- 1. 传递数据:
    - <Demo name="xxx"/>

- 2. 接收数据:
    - 第一种方式:【只接受】 
        - props: ['name']
        
    - 第二种方式:【限制类型】
        props: {
            name: String
        }

    - 第三种方式:【限制类型、限制必要性、指定默认值】
        props: {
            name: {
                type: String,
                required: true,
                default: 8
            }
        }

- 注意: props 是只读的,不允许修改, Vue 底层会检测到对象props的修改,如若进行修改,就会发出警告
  • props 案例
    • 在你的 components 文件中 新建一个 .vue 的文件【组件】---Student.vue
    • 在你认为是父组件中进行 注册导入
// Student.vue
<template>
  <div class="demo">
    <h1>{{msg}}</h1>
    <h2>学生姓名: {{name}}</h2>
    <h2>学生性别: {{sex}}</h2>
    <h2>学生年龄: {{age}}</h2>
  </div>
</template>

<script>
export default {
  name: "School",
  data() {
    return {
      msg: '奇缘课堂,自学者的快乐'
    }
  },
  // 简单声明接收
  // props: ['name', 'sex', 'age'] 

  // 接收的同时对类型进行限制
  /* props: {
    name:Number,
    age: Number,
    sex: String
  } */

  // 接收的同时对数据: 进行类型限制 + 默认值的指定 + 必要性的限制
  props: {
    name: {
      type: String, // 数据类型
      required: true // 必填项
    },
    age: {
      type: Number,
      default: 18
    },
    sex: {
      type: String,
      required: true
    }
  }
}
</script>

<style>
    .demo {
        background-color: pink;
    }
</style>
// 父组件中 【这里我用了 App.vue】做为父组件
<template>
  <div>
     <!--
         使用注册的组件,并进行传值,这个过程叫做父向子传值
         注意: :agg='22' 使用 :绑定的属性此时这个属性是一个 number 类型的数据
      -->
    <School name="小娟儿" sex="女" :age="22"/>
  </div>
</template>

<script>
// 导入 子组件
import School from './components/School'

export default {
  name: 'App',
  components: { // 注册组件
    School
  }
}
</script>

<style>

</style>

组件间的通信

  • 父子组件之间的数据共享

  • 父向子传递数据

    • 父组件向子组件传递数据需要使用自定义属性
        1. 父组件引入子组件,在子组件上绑定属性传入数据
        1. 子组件通过 props: ['xxx'] 进行接收传递的数据
<!--父组件--> 
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <!-- 绑定自定义属性,向子组件传递数据 -->
    <Student :msg="message" :user="users"/>
  </div>
</template>

<script>
import Student from './components/School'
export default {
  data() {
    return {
      message: 'Hello Vue',
      users: { name: '小娟儿', sex: '女', age: 21 }
    }
  },
  components: {Student}
}
</script>

<style lang="less">
  .app-container {
    padding: 1px 20px 2px;
    background-color: #efefef;
  }
</style>



<!--子组件--> 
<template>
  <div class="demo">
    <h3>Student组件</h3>
    <p>msg 的值是: {{msg}}</p>
    <p>user 的值是: {{user}}</p>
  </div>
</template>

<script>
export default {
    // 子组件通过 props 接收到父组件创建过来的数据
  props: ['msg', 'user']
}
</script>

<style>
    .demo {
        background-color: orangered;
    }
</style>
  • 子向父传递数据
    • 第一种方式: 父组件需要定义一个回调函数,并把这个回调函数传递给子组件,子组件进行接收

    • 第二种方式: 子组件向父组件共享数据需要使用自定义事件

      • 父组件需要定义一个事件,在子组件需要绑定该事件
      • 子组件需要通过, this.$emit('事件名称', 需要传递的数据)
<!--父组件-->
<template>
  <div class="app-container">
    <h1>App 根组件 ---- {{countFromSon}}</h1>
    <!-- 自定义 numchange 事件 -->
    <Student @numchange="getNewCount"/>
  </div>
</template>

<script>
import Student from './components/School'
export default {
  data() {
    return {
      // 用于接收子组件传递过来的数据
      countFromSon: 0
    }
  },
  components: {Student},
  methods: {
    // 获取子组件传递过来的数据
    getNewCount(value) {
      this.countFromSon = value
    }
  },
}
</script>

<style lang="less">
  .app-container {
    padding: 1px 20px 2px;
    background-color: #efefef;
  }
</style>


<!--子组件-->
<template>
  <div class="demo">
    <h3>Student组件----- {{count}}</h3>
    <button @click="add">点击+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    add() {
      this.count += 1
      /* 接收到自定义 numchange 事件,把要进行传递的数据,传递给父组件 */
      this.$emit('numchange',this.count)
    }
  },
}
</script>

<style>
    .demo {
        background-color: orangered;
    }
</style>
  • 全局事件总线 【GlobalEventBus】
    • 定义全局事件总线: 【在 main.js 文件中】
  new Vue({
    render: h => h(App),
      beforeCreate() {
        Vue.prototype.$bus = this // 安装全局事件总线
      }
  }).$mount('#app')
  • 兄弟组件之间的数据传递
    • 在 vue2.x 中,兄弟组价之间的数据共享的方案是: 【GlobalEventBus】
      • 【GlobalEventBus】 的使用步骤:
        • 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
        • 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法去注册一个自定义事件
    • 注意: 最好是在 beforeDestroy() {} 声明周期函数中 进行自定义事件的解绑 beforeDestroy() { this.$off('自定义事件名称') }
  • 兄弟之间数据传递 案例
  • 首先准备两个组件【用于作为兄弟组件】
// StudentA.vue 
<template>
 <div class="demo">
     <h2>StudentA组件</h2>
     <hr/>
     <p>StudentB组件发送的数据是: {{msgFromShool}}</p>
 </div>
</template>

<script>
export default {
   data() {
       return {
           msgFromShool: ''
       }
   },
   created() {
       // 为 bus 绑定自定义事件
       bus.$on('share', (val) => {
           this.msgFromShool = val
       })   
   }
}
</script>

<style lang="less" scoped>
   .demo {
       background-color: aliceblue;
   }
</style>
// StudentB.vue
<template>
  <div class="demo">
    <h2>StudentB组件</h2>
    <button @click="send">点击发送数据给Student组件</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      str: '发送给兄弟组件的数据!!!'
    }
  },
  methods: {
    send() {
      // 通过 eventBus 发送数据
      bus.$emit('share', this.str)
    } 
  }
}
</script>

<style lang="less" scoped>
    .demo {
        background-color: orangered;
    }
</style>

nextTick 学习

- 语法: this.$nextTick(callback)
  - 作用: 在下一次DOM更新结束后执行其指定的回调【需要借助于 ref】
  - 什么时候使用: 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
  
  • nextTick 场景演示以及使用 案例
<template>
 <div class="app-container">
   <h1>App 根组件</h1>
   <input type="text" v-if="inputVisible" @blur="showButton" ref="iptRef">
   <button v-else @click="showInput">展示数据框</button>
 </div>
</template>

<script>
export default {
 data() {
   return {
     inputVisible: false
   }
 },
 methods: {
   showInput() {
     this.inputVisible = true
     this.$nextTick(() => {
       this.$refs.iptRef.focus()
     })
   },
   showButton() {
     this.inputVisible = false
   }
 }
}
</script>

<style lang="less">
 .app-container {
   padding: 1px 20px 2px;
   background-color: #efefef;
 }
</style>

第五章节 学习

  • 配置代理 【解决请求跨域问题】
  • 在 vue.config.js 中写入以下代码即可
    module.exports = {
    // 关闭语法检查
    lintOnSave: false,
    // 开启代理服务器【方式一】
   /*  devServer: {
        proxy: 'http://localhost:6000'
    } */
    // 开启代理服务器【方式二】
    devServer: {
        proxy: { 
          '/api': { // 配置以 '/api' 开头的请求路径
            target: 'http://localhost:6000', // 代理目标的基础路径
            pathRewrite: {'^/api': ''}, // 重写目录路径
            ws: true, // 是否开启 websocket 服务
            changeOrigin: true // 是否代理
          }
        }
    }
}

动态路由和动态组件

  • vue 提供了一个内置的 component 组件,专门用来实现动态组件的渲染

    • 用法: <component :is="表示要渲染的组件名字"></component>
  • 使用 keep-alive 保持状态,可以把内部的组件进行缓存操作

    • 用法: <keep-alive><component :is="表示要渲染的组件名字"></component></keep-alive>
  • keep-alive 对应的生命周期函数:

    • 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数

    • 当组件被激活时,会自动触发组件的 activated 生命周期函数

    • keep-alive 的 include 属性 和 exclude 属性

      • include 属性用来指定: 只有名称匹配的组件才会被缓存
      • exclude 属性是用来排除不被缓存的组件
        • 注意: 这两个属性是不能一起使用的,【二选一】
  <keep-alive include="School,Student">
    <component :is="" />
  </keep-alive>
  • 案例
// Student.vue 组件
<template>
 <div class="Student-container">
     <h2>Student组件</h2>
 </div>
</template>

<script>
export default {

}
</script>

<style scoped>
.Student-container {
   background-color: orangered;
}
</style>
// School.vue 组件
<template>
 <div class="school-container">
   <h2>School组件</h2>
   <p>{{num}}</p>
   <button @click="num++">+1</button>
 </div>
</template>

<script>
export default {
   data() {
       return {
           num: 0
       }
   },
   activated() {
       console.log('组件被激活了, activated')
   },
   deactivated() {
       console.log('组件被缓存了,deactivated')
   },
};
</script>

<style scoped>
.school-container {
   background-color: beige;
}
</style>
// App.vue
<template>
 <div class="app-container">
   <h1>App组件...</h1>
   <hr />

   <button @click="comName='School'">展示 School 组件</button>
   <button @click="comName='Student'">展示 Student 组件</button>

   <div class="box">
     <!-- 渲染 School 和 Student 组件 -->
     <keep-alive>
       <component :is="comName"></component>
     </keep-alive>
   </div>
 </div>
</template>

<script>
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
 components: {
   School,
   Student,
 },
 data() {
   return {
     comName: "School",
   };
 },
};
</script>

<style scoped>
.app-container {
 padding-left: 20px;
 padding-top: 20px;
 padding-right: 20px;
 border: 1px solid #efefef;
 background-color: aliceblue;
}
</style>

插槽的学习和使用

 - 什么是插槽?
  - 插槽【Slot】是 vue 为组件的封装者提供的能力,允许开发者在封装组件时,为了以后的组件扩展部分定为插槽
      - 作用: 让父组件可以向子组件位置插入HTML结构,也是一种组件之间的通信方式【父组件向子组件】
      - 插槽分类: 默认插槽、具名插槽、作用域插槽
  • 默认插槽
  // 父组件中
  <template>
    <Game>
      <div>html结构1</div>
    </Game>
  </template>

  // 子组件中
  <template>
    <div>
      <!-- 定义插槽 -->
      <slot></slot>
    </div>
  </template>
  • 具名插槽
  // 父组件中
  <template>
    <Game>
      <template v-slot:game>
        <div>html结构2</div>
      </template>
    </Game>
  </template>

  // 子组件中
  <template>
    <div>
      <!-- 定义插槽 -->
      <slot name="game"></slot>
    </div>
  </template>
  • 作用域插槽
  <!-- 父组件中-->
  <template>
    <Game>
      <template scope="{games}">
        <!--生成有序列表-->
        <ol>
          <li v-for="(item,index) in games" v-bind:key="index">{{item}}</li>
        </ol>
      </template>
    </Game>
  </template>

  <!--子组件中-->
  <template>
    <div>
      <!-- 定义插槽 -->
      <slot v-bind:games="games"></slot>
    </div>
  </template>
  <script>
    export default {
      name: "Category",
      data() {
        return {
          games: ["王者荣耀", "英雄联盟", "穿越火线"]
        }
      }
    }
</script>

第六章 路由学习

  • 什么是路由?

    • 一个路由就是一组映射关系【key - value】
    • key 为路径,value 可能是 function 或 component
  • 前端路由的工作方式:

      1. 用户点击了页面上的路由连接
      1. 导致了 URL 地址栏中的 Hash 值的变化
      1. 前端路由监听到了 Hash 地址的改变
      1. 前端路由把当前 Hash 地址对应的组件 渲染到浏览器中

vue-router

  • vue-router 是 Vue.js 官方提供的路由解决方案,它只能结合 vue 项目进行使用
  • 能够轻松的实现出 SPA 项目中的组件切换
  • 安装 vue-router: npm install vue-router@3 -S
    • 注意:在 Vue2.x 中安装的是 3版本的,否则会出错
  • 在 main.js 中导入并使用
  import Vue from 'vue'
  import App from './App.vue'

  // 引入 VueRouter
  import VueRouter from 'vue-router'
  // 引入路由对象
  import router from './router'

  Vue.config.productionTip = false
  // 使用 VueRouter
  Vue.use(VueRouter)

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

  • 创建 router 文件【src/router/index.js】配置路由
  // 引入 VueRouter
  import VueRouter from "vue-router"
  // 引入组件
  import About from '../components/About.vue'
  import Home from '../components/Home.vue'

  // 创建并暴露路由对象
  export default new VueRouter({
    routes: [
      { path: '/about', component: About },
      { path: '/home', component: Home }
    ]
  })  
  • 在需要使用的组件中注册路由实现组件切换:

    • <router-link to="/要跳转的路径" />
  • 指定展示的组件位置

    • <router-view></router-view>

嵌套路由

  • 嵌套路就是在路由的基础上又进行了一层的嵌套【使用属性: children】
  • 在scr文件目录中找到 router 文件夹、index.js
// 引入 VueRouter
import VueRouter from "vue-router"
// 引入组件
import About from '../components/About.vue'
import Home from '../components/Home.vue'
import News from '../components/News.vue'
import Message from '../components/Message.vue'

// 创建并暴露路由对象
export default new VueRouter({
 routes: [
   { 
     path: '/about', 
     component: About 
   },
   { 
     path: '/home', 
     component: Home,
     // 使用多级路由【路由嵌套】
     children: [
       {
         path: 'news',
         component: News
       },
       {
         path: 'message',
         component: Message
       }
     ] 
   }
 ]
})

路由命名

  • 作用: 可以简化路由的跳转
  • 使用: 需要给路由命名
  // 引入 VueRouter
  import VueRouter from "vue-router"
  export default new VueRouter({
    name: 'demo' // 命名路由
    path: '/demo',
    component: Demo
  })
  • 简化跳转
 <router-link :to="{name: 'demo'}">跳转内容</router-link>

路由传递参数

  • 路由的 query 参数传递
  <!-- 跳转路由,并携带 query 参数 [字符串写法]-->
  <router-link :to="/home/message/detail?id=8$msg=hello">跳转内容</router-link>
  <!-- 跳转路由,并携带 query 参数 [对象写法] -->
  <router-link :to="{path: '/home/message/detail', query: id: 888, msg:'hello'}">跳转的内容</router-link>

  <!-- 接收参数 -->
  $route.query.id
  $route.query.msg
  
  <template>
  <ul>
    <li>消息编号: {{$route.query.id}}</li>
    <li>消息标题: {{$route.query.title}}</li>
  </ul>
</template>
  • 路由的 params 参数传递
    1. 配置路由,需要 params 参数
  // 引入 VueRouter
  import VueRouter from "vue-router"
  export default new VueRouter({
    name: 'detail'
    path: '/detail/:id/:title', // 使用占位符声明接收 params 参数
    component: Detail
  })
    1. 传递参数
  <!-- 跳转路由,并携带 params 参数 [字符串写法]-->
  <!-- <router-link :to="`/home/message/detail/${item.id}/${item.title}`">{{item.title}}</router-link> -->

  <!-- 使用 对象形式的时候,需要使用, name 属性进行 路径跳转 -->
  <router-link :to="{
    name: 'detail',
    params: {
      id: item.id,
      title: item.title
    }
  }">
    {{item.title}}
  </router-link>
  • 路由的 props 配置
    • 作用: 让路由组件更加方便的接收到参数
  // 引入 VueRouter
  import VueRouter from "vue-router"
  export default new VueRouter({
    name: 'detail'
    path: '/detail',
    component: Detail,
   // props 的第一种写法,值为对象,改对象中所有的key - value 都会以 props 的形式传递给 Detail 组件
    /*  props: {
       a:1,
       b: 'hello'

     // props 的第二种写法,值为 布尔值,若值为true,就会把该路由组件接收到的所有params参数,以prop Detail组件

     // props 的第三种写法,值为函数
     /* props($route) {
       return {id: $route.params.id, title: $route.params.title}
     } */
     props({params:{id,title}}) {
       return {id,title}
     }
  })
  • 组件接收 使用 props
  <template>
  <ul>
    <li>消息编号: {{ id }}</li>
    <li>消息标题: {{ title }}</li>
  </ul>
</template>

<script>
export default {
  name: "Detail",
  props: ["id", "title"],
};
</script>
  • router-link 的replace 属性
    • 作用: 控制路由跳转时操作浏览器历史记录的模式
    • 浏览器的历史记录有两种: push、replace, push 是追加历史记录,replace 是替换当前记录

编程式路由导航

  • 作用: 不借助 router-link 实现路由的跳转,让路由更加的灵活
  // $router 的 API
  this.$router.push('hash 地址')

  this.$router.replace('hash 地址')

  this.$router.back() // 后退
  this.$router.forward() // 前进
  this.$router.go(n) // 前进/后退

路由导航守卫

  • 作用: 对路由进行权限控制

  • 分类: 全局守卫、独享守卫、组件内守卫

  • 全局守卫【前置、后置】 -- 写到 router文件下的,index.js 中

// 全局前置路由守卫【初始化、每次路由切换之前被调用】
router.beforeEach((to,from,next) => {
  // to 是将要访问的路由信息对象
  // from 是将要离开时路由的信息对象
  // next 是一个函数, 调用 next() 表示放行,允许这次路由导航
  /*  
    当前用户没有访问权限,强制其跳转到登录页面: next('/login')
    当前用户没有访问权限,不允许进入后台管理主页: next(false)
  */
  console.log('前置路由守卫',to,from)
  if (to.meta.isAuth) { // 判断是否鉴权
    if (localStorage.getItem('Le') === 'Le168168') {
      next()
    }else {
      alert("身份认证失败,您无权限查看!!!")
    } 
  }else {
    next()
  }
})

// 后置路由守卫【初始化、每次路由切换之后被调用】
router.afterEach((to,from) => {
  console.log('后置路由守卫',to,from)
  document.title = to.meta.title || '奇缘课堂'
})

章节七 Vuex 学习

  • Vuex 是什么?

    • 它是专门在 Vue 中实现集中式状态【数据】管理的一个Vue插件,对于vue应用中多个组件的共享状态进行集中管理【读/写】
    • 也是一种组件间的通信方式,且使用于任意组件间通信
  • 什么时候使用 Vuex?

    • 多个组件依赖于同一状态
    • 来自不同组件的行为需要变更为同一状态
  • 安装:npm install vuex@next --save

  • 搭建 vuex 环境

      1. 创建文件: src/store/index.js
  // 引入 vue
  import Vue from 'vue'
  // 引入 vuex
  import Vuex from 'vuex'
  // 使用 Vuex
  Vue.use(Vuex)

  // 创建 actions 对象 响应于组件中用户动作
  const actions = {
    increment(context, value) {
      // 通过 commit 触发 mutations
      context.commit('Add', value)
    }
  }
  // 创建 mutations 对象 响应于 修改 state 中的数据
  const mutations = {
    Add(state, value) {
      state.num += value
    }
  }
  // 创建 state 对象 响应于具体的数据
  const state = {
    num: 0
  }
  // 准备一个 getters 用于将 state 中的数据进行加工
  // 当 state 中的数据需要经过加工够在进行使用时,可以使用 getters
  // - 组件中读取: $store.getters.bigSum
  const getters = {
     bigSum(state) {
      return state.sum * 10
    }
  }

  // 创建并暴露 Store
  export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
  })
    1. 在 main.js 中创建 vm 时传入 store 配置项
  import Vue from 'vue'
  import App from './App.vue'
  // 引入 store
  import store from './store'

  Vue.config.productionTip = false

  new Vue({
    render: h => h(App),
    store,
  }).$mount('#app')
  - 3. 组件中读取Vuex中的数据: $store.state.sum
  - 4. 组件中修改Vuex中的数据: $store.dispatch('actions中的方法名', 数据)/$store.commit('actions中的方法名', 数据)
  • 案例
  • 在store文件下的 index.js 中
// 该文件用于创建 Vuex 中最为核心的 store
// 引入 Vue
import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
// 使用插件
Vue.use(Vuex)

// 准备 actions 用于响应组件中的动作
const actions = {
  increment(context, value) {
    context.commit('increment',value)
  },
  decrement(context, value) {
    if (context.state.sum === 0) return
    context.commit('decrement', value)
  }
}
// 准备 mutations 用于操作数据
const mutations = {
  increment(state, value) {
    state.sum += value
  },
  decrement(state, value) {
    state.sum -= value
  }
}
// 准备 state 用于存储数据
const state = {
  sum: 0,
  school: 'ABC',
  subject: 'Web前端'
}
// 准备一个 getters 用于将 state 中的数据进行加工
const getters = {
  bigSum(state) {
    return state.sum * 10
  }
}

// 创建并暴露 store
export default new Vuex.Store({
  actions,
  mutations,
  state,
  getters
})
<template>
  <div class="count-container">
    <h1>请求求和为:{{$store.state.sum}}</h1>
    <h2>当前求和放大十倍为:{{$store.getters.bigSum}}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd">请求求和为奇数再加</button>
    <button @click="incrementWait">等一等在加</button>
  </div>
</template>

<script>
export default {
  name: "Count",
  data() {
    return {
      n: 1
    }
  },
  methods: {
    increment() {
      this.$store.dispatch('increment',this.n)
    },
    decrement() {
      this.$store.dispatch('decrement',this.n)
    },
    incrementOdd() {
      if (this.$store.state.sum %2 != 0) {
        this.$store.dispatch('increment',this.n)
      } 
    },
    incrementWait() {
      setTimeout(() => {
        this.$store.dispatch('increment',this.n)
      }, 500)
    }
  }
}
</script>

<style scoped>
button {
  margin-left: 8px;
}
.count-container {
  background-color: orange;
}
</style>
  • mapState 和 mapGetters 方法的使用
  // 引入 mapState 和 maoGetters
  import {mapState, mapGetters} from 'vuex'
  computed: {
    // 借助 mapState 生成计算属性,从 state 中读取数据【对象写法】
    // ...mapState({sum:'sum', school:'school', subject:'subject'})

    ...mapState(['sum','school','subject']),

    // 借助 mapGetters 生成计算属性,从 getters 中读取数据
    ...mapGetters(['bigSum'])
  }
<template>
  <div class="count-container">
    <h1>请求求和为:{{sum}}</h1>
    <h2>当前求和放大十倍为:{{bigSum}}</h2>
    <h3>在{{school}}, 学习{{subject}}</h3>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd">请求求和为奇数再加</button>
    <button @click="incrementWait">等一等在加</button>
  </div>
</template>

<script>
import {mapState,mapGetters} from 'vuex'

export default {
  name: "Count",
  data() {
    return {
      n: 1
    }
  },
  methods: {
    increment() {
      this.$store.dispatch('increment',this.n)
    },
    decrement() {
      this.$store.dispatch('decrement',this.n)
    },
    incrementOdd() {
      if (this.$store.state.sum %2 != 0) {
        this.$store.dispatch('increment',this.n)
      } 
    },
    incrementWait() {
      setTimeout(() => {
        this.$store.dispatch('increment',this.n)
      }, 500)
    }
  },
  computed: {
    // 借助 mapState 生成计算属性,从 state 中读取数据【对象写法】
    // ...mapState({sum:'sum', school:'school', subject:'subject'})

    ...mapState(['sum','school','subject']),

    // 借助 mapGetters 生成计算属性,从 getters 中读取数据
    ...mapGetters(['bigSum'])
  }
}
</script>

<style scoped>
button {
  margin-left: 8px;
}
.count-container {
  background-color: orange;
}
</style>
  • mapActions 和 mapMutations 方法的使用
// 引入 mapState 和 maoGetters
  import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
  methods: {
    // 借助 mapMutations 生成对应的方法,方法中会去调用 commit 去联系 mutations 对象
    // ...mapMutations({increment:'increment', decrement:'decrement'}),
    ...mapMutations(['increment','decrement']),

    // 借助 mapActions 生成对应的方法,方法中会去调用 dispatch  联系 actions 对象
    ...mapActions(['incrementOdd', 'incrementWait'])

    /* incrementOdd() {
      if (this.$store.state.sum %2 != 0) {
        this.$store.dispatch('increment',this.n)
      } 
    },
    incrementWait() {
      setTimeout(() => {
        this.$store.dispatch('increment',this.n)
      }, 500)
    } */
  },
  • 注意: mapActions 和 mapMutations 使用时,若需要传递参数,需要在模板中绑定事件时传递好参数,否则就是事件对象