vue复习

76 阅读9分钟

数据绑定

<!DOCTYPE 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>Document</title>
</head>
<body>
    <!-- mustache 可以做运算 取值 输出 三元 不能写js语法-->
    <!-- 只要使用vue的数据就需要先声明 在使用 -->
    <div id="app">
        {{msg}} {{1+1}} {{info.a}} {{ {} }}
        {{flag?1:2}}  {{ (function(){return 100})() }}
        {{msg+'123'}}
    </div> 
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
        let vm = new Vue({
            data:{
                info:{},
                msg:'hello',
                flag:true
            }
        });
        vm.$mount('#app')
        // 1.vm.$el 指代的就是当前的元素
        // 2.vm.$nextTick 延迟执行 dom操作时必备
        // 3.vm.$watch 监控数据变化
        // 4.vm.$data 当前数据对象
        // 5.vm.$options所有的选项
        // 6.vm.$set
        // 7.vm.$mount挂载  单元测试 在内存中挂载vue实例 此时只能用$mount属性
        vm.$watch('msg',function (newVale,oldValue) { // 可以观察某个数据 发生变化后触发此函数
            console.log(newVale,oldValue)
        });
        vm.msg = 'zf';
        vm.msg = 'xxx'; // dom更新是异步的
        vm.$nextTick(function () { 
            console.log(vm.$el.innerHTML);
        });
    </script>
</body>
</html>

常用指令

<!DOCTYPE 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>Document</title>
</head>
<body>
    <!-- v-for 指令:v-开头 有特定功能的 操作dom元素-->
    <!-- 数据都需要循环来操作 {} ([] 5) string -->
    <!-- for(let a of arr) -->
    <!-- vue 2.5+ 版本要求循环的时候 必须增加key属性 为了做domdiff -->
    <!-- v-bind 是动态绑定属性 所有的指令 中的值 都是我们的变量 如果要是字符串需要加双引号 -->
    <!-- template vue自带的标签 无意义的标签 template上不能增加key属性 需要给真实的元素添加key -->
    <!-- v-if/ v-else / v-show  -->
    <!-- v-on 绑定事件 可以简写成 @ 符号 并且事件参数是$event -->
    <div id="app">
        <template v-for="(a,index) in arr">
            <!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
            <li :key="index+'_1'" :a="index+'_1'">{{a}} {{index}}</li>
            <li :key=`${index}_2` :a=`${index}_2`>{{a}} {{index}}</li>
        </template>
        <template v-for="(value,key) in {a:1}">
                <!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
                <li>{{value}} {{key}}</li>
         </template>
         <template v-for="(value,key) in 5">
                <!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
                <li>{{value}} {{key}}</li>
         </template>
         <!-- v-if v-show的区别 if处理dom是否增加到页面上 show style的操作 (show 不支持template写法) -->
         <div v-show="false">
            你好
         </div>
         <div v-show="true">
            不好
         </div>
         {{this.flag}}
         <button v-on:click="fn($event)">切换</button>
         <!-- 尽量不要给动态的数据 不要用key来渲染  可能会导致浪费性能-->
         <!-- [香蕉,苹果,橘子]  点击 翻转 -->
         <!-- <li x>香蕉</li>  <li j>橘子</li>
         <li p>苹果</li>  <li p>苹果</li>
         <li j>橘子</li>  <li x>香蕉</li> -->

         <div v-if="flag">
            <label >test1</label>
            <input type="text" key="1">
         </div>
         <div v-else>
            <label >test2</label>
            <input type="text" key="2">
         </div>
          <button @click="fn">切换</button>
          <!--  只渲染一次 渲染后会产生缓存 下次更新时 会直接从缓存中获取 v-once 可以有效的防止重新渲染-->
          <div v-once>{{flag}}</div>
          <!-- innerHTML 会导致 xss攻击 防止xss攻击 就不要把用户输入的内容直接显示出来  -->
          <input type="text" :value="element" @input="(e)=>{element=e.target.value}">
          <!-- 语法糖  -->
          <input type="text" v-model="element">
          <div v-html="element"></div>
    </div>
    <script src="./node_modules/vue/dist/vue.js"> </script>
    <script>
        // 所有的数据 都会合并到vm的实例上 但是会被data覆盖掉 不要声明相同的名字
        let vm = new Vue({
            el:'#app',
            data:{
                flag:true,
                arr:[1,2,3],
                element:'<h1>hello</h1>'
            },
            methods:{ // 方法要放到methods中
                fn(e){
                    console.log(this);
                    this.flag=!this.flag
                },
            }
        });
    </script>
</body>
</html>

自定义指令和过滤器

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    .content {
        width: 100px;
        height: 100px;
        background: pink;
    }
</style>

<body>
    <div id="app">
        <!-- 如果是多次可复用的操作dom节点,可以使用自定义指令,如果是单次操作dom节点,更建议使用$ref -->
        <!-- 指令的作用就是操作dom,有特定功能 .a是修饰符-->
        <div v-direcitve_demo.a="'red'">xxx</div>
        <!-- popup -->
        <div v-click_outside='hide'>
            <input type="text" @focus='show'>
            <div class="content" v-if='isShow'>
                content
                <button>点我呀</button>
            </div>
        </div>
        <!-- 一刷新页面就获取焦点 -->
        <input type="text" v-focus='xx'>
        <!-- 过滤器,| 竖线叫管道符 -->
        <div :id="xx | abc"></div>
        <div>{{xx | abc}}</div>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
        /**
         * @description: 全局指令,不需要每个组件都引用,全局引用即可
         * @param {type} 指令名
         * @param {type} 对象或函数
         * @author: dlb
         */
        Vue.directive('direcitve_demo', {
            // bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
            // inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
            // update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
            // componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
            // unbind
            bind: (el, binding, vnode, oldVnode) => {
                console.log('el', el);
                console.log('binding', binding);
                console.log('vnode', vnode);
                console.log('oldVnode', oldVnode);
                el.style.color = `${binding.value}`
            }
        })
        let vm = new Vue({
            // 局部过滤器
            filters: {
                abc: (value) => {
                    return 666
                }
            },
            // 局部指令,因为有可能是多个指令,所以要加个s
            directives: {
                // 如果是直接绑定的方法默认是bind和update(两个的合体)
                click_outside(el, binding, vnode) {
                    // 绑定给document 捕获事件发生在谁的身上
                    document.addEventListener('click', (e) => {
                        if (!el.contains(e.target)) {
                            console.log(binding.expression, '失去焦点', vnode.context);
                            // 在vnode的上下文里可以拿到vue实例,从而调用方法 
                            vnode.context[binding.expression]()
                        }
                    })
                },
                focus: {
                    // bind: (el, binding, vnode) => {
                    //     // 保证vue渲染完成再次性就可以达到效果了
                    //     Vue.nextTick(() => {
                    //         el.focus()
                    //     })
                    // },
                    // 被绑定元素插入父节点时调用
                    inserted: (el, binding, vnode) => {
                        el.focus()
                    }
                }
            },
            el: '#app',
            data: {
                isShow: false,
                xx: 'hello'
            },
            methods: {
                show() {
                    this.isShow = true
                },
                hide() {
                    this.isShow = false
                }
            }
        })
    </script>
</body>
</html>
```'

## computed、methods、watch的区别

> methods中的方法,页面中用到的任意一个变量改变了,都会重新执行

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <div id="example">
            <!-- 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护 -->
            {{ message.split('').reverse().join('') }} <br />
            <!-- 复杂运算应该放入到computed上 -->
            {{messageHandler}}<br />
            <!-- computed和methods的区别 -->
            {{firstName+lastName}}<br />
            <!-- methods -->
            {{getName()}}
        </div>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    message: 'ABCDEF',
                    firstName: '懒',
                    lastName: '斌'
                }
            },
            // 对于任何复杂逻辑,你都应当使用计算属性
            computed: {
                messageHandler() {
                    return this.message.split('').reverse().join('')
                }
            },
            methods: {
      			// 使用methods的方法,只要页面中任意变量修改了,这个方法都会执行,对性能比较浪费
                getName() {
                    console.log('getName');
                    return this.firstName + this.lastName
                }
            },
            mounted() {}
        })
    </script>
</body>
</html>

如果是methods里面的方法

// computed是基于Object.defineProperty的,某个属性变了,会自动触发set/get方法,更新试图,
            // 如果属性没变,就不会触发set/get方法
            computed: { //computed会有缓存,如果依赖的数据不发生变化,不会重新执行方法
                // 虽然getName是个方法,但是实际上是个属性
                getName: {
                    // 计算属性有get和set方法,如果不配置的话默认是get方法
                    get() {
                    	// 里面不支持定时器等异步方法
                        console.log('getName');
                        // 并且computed里的方法,只有当依赖的变量变了,才会从新执行
                        return this.firstName + this.lastName
                    },
                    // 给getName赋值的时候会触发这个方法
                    set(val) {
                        console.log('set', val);
                        //这里可以做一些逻辑操作
                        this.message = 'iiiii'
                        //不需要有返回值
                    }
                },
                // 对于任何复杂逻辑,你都应当使用计算属性
                messageHandler() {
                    return this.message.split('').reverse().join('')
                }
            },

1.在控制台手动修改方法里的变量值,方法会执行,页面渲染也会改变 2.在控制台手动修改页面用到了但是方法里没用到的变量值,这个方法也会重新执行,这里就等于是浪费了渲染性能了 3.所以在这种情况下,使用methods里的方法就不是太合适

如果是computed里面的方法

1.在控制台手动修改方法里的变量值,方法会执行,页面渲染也会改变 2.在控制台手动修改页面用到了但是方法里没用到的变量值,这个方法不会重新执行,性能上会好一些 3.computed是基于Object.defineProperty的,某个属性变了会自动触发set/get方法并更新试图,如果没变就不会触发

methods和computed的区别

  1. methods:里面定义的是方法,不会进行缓存

  2. computed:里面定义的是值,会进行缓存

  3. {{fn()}} 这种写法虽然从语法上不会报错,但是错误的写法,尽量不要这么写

watch监控

// 什么时候用watch,什么时候用computed
// watch:简单的事情,例入数据变化了就发送请求
// computed:用在只是计算结果
watch: {// 监控
  firstName: {
    handler(val, oldVal) {
      // 在里面加定时器是可以正常生效的
      setTimeout(()=>{
        this.fullName = val + 999
      },2000)
    },
    deep: true, // 深度监听
    immediate: false, //立即执行
  }
},

watch和computed在使用场景上的区别

  1. watch:简单的事情,例入数据变化了就发送请求
  2. computed:用在只是计算结果

动画animate的使用

触发动画的方式

1.v-if、v-show,会触发过渡效果

2.路由切换

实现动画的方式

1.css添加动画

  • animation
  • transition 2.js添加动画
  • 使用自带的钩子
  • 使用velocity动画库

动画分类

1.单个动画

2.多个动画

动画的阶段

实现原理是,每个对应的状态会加上对应的类名

  • v-enter进入之前/v-leave离开前

  • v-enter-active进入中/v-leave-active离开中

    主要控制过渡时间和效果

  • v-enter-to进入之后/v-leave-to离开后

调试的实际效果是,-to的类名和-active的类名基本上同时出现同时消失,个人觉得没啥用,离开后的样式会恢复默认的

具体使用

1.使用transition/transition-group标签包裹

<transition>
    <div class="box" v-if='show'>
        content
    </div>
</transition>
// 过渡动画样式
.v-enter {
  opacity: 0;
  transform: scale(0.1, 0.1);
}
.v-enter-active {
  transition: all 2s linear;
}
.v-enter-to {
  opacity: 1;
  transform: scale(1,1);
}
.v-leave {
  opacity: 1;
}
.v-leave-active {
  transition: all 5s linear;
}
.v-leave-to {
  opacity: 0;
}

2.如果给transition加name属性的,则样式也要以定义的name开头,在需要多组动画时可以有对应关系

<transition name='fade'></transition>
.fade-enter {
  opacity: 0;
  transform: scale(0.1, 0.1);
}

使用动画库anmate.css

安装和引入

github地址 官网地址

yarn add animate.css -s
<link rel="stylesheet" href="./node_modules/animate.css/animate.css">

使用方法

1.需要先给需要动画的盒子加animated类名,并且transition的name也要用库里的名字

<!-- name='bounce'可以指定动画名字 -->
<transition name='bounce'>
    <div class="box animated" v-if='show'>
        content
    </div>
</transition>
// 进入样式
.bounce-enter-active{
    animation: bounceIn 1s ease-in;
}
// 离开样式
.bounce-leave-active{
    animation: bounceOut 1s ease-in;
}

2.不写样式的方式,在transition上直接配置使用的类名,以animate__类名的方式

<!-- 指定进入和离开的动画类名 -->
<transition enter-active-class='animate__bounceIn' leave-active-class='animate__bounceOut'>
    <!-- 设置过渡时间 -->
    <div class="box animated" v-show='show' style="animation-duration: 50000ms">
        content
    </div>
</transition>

这两种方式都能使用animate.css达到了一个弹跳的动画效果

使用js动画

触发动画的钩子,在方法里用js控制动画效果

<transition @beforeEnter="beforeEnter" 
            @enter="enter" 
            @afterEnter="afterEnter"
            @enterEancelled="enterCancelled" 
            @beforeLeave="beforeLeave" 
            @leave="leave" 		 
            @afterLeave="afterLeave"
            @leaveCancelled="leaveCancelled">
            <div class="box animated" v-if='show' style="animation-duration: 5000ms">
                content
            </div>
        </transition>
methods: {
    beforeEnter: function (el) {
        console.log(el,'el');
        el.style.background = 'red'
    },
    // 当与 CSS 结合使用时
    // 回调函数 done 是可选的
    enter: function (el, done) {
        setTimeout(() => {
            el.style.background = 'yellow'
        })
        setTimeout(() => {
            done()
        })
    },
    afterEnter: function (el) {
        el.style.background = 'pink'
    },
    enterCancelled: function (el) {},
    beforeLeave: function (el) {},
    // 当与 CSS 结合使用时
    // 回调函数 done 是可选的
    leave: function (el, done) {},
    afterLeave: function (el) {},
    // leaveCancelled 只用于 v-show 中
    leaveCancelled: function (el) {}
}

使用vue推荐的第三方js动画库Velocity.js

中文文档 1.安装、引入

cnpm install velocity-animate -s
<script src="./node_modules/_velocity-animate@1.5.2@velocity-animate/velocity.min.js"></script>

2.使用,在vue的动画钩子的回调里使用就可以了,html中也是那几个动画钩子

beforeEnter(el){
    el.style.opacity = 0;
    el.style.color="purple"
},
enter(el,done){
	//     标签  动画属性     Velocity 动画配置项,参数是固定的
    Velocity(el,{opacity:1},{duration:1000,complete:done});
},
afterEnter(el){
    el.style.color="blue"
},  
leave(el,done){
    // 在离开的时候 还需要自己手动重置回去
    Velocity(el,{opacity:0},{duration:1000,complete:done});
}

多个动画的实现

应用场景:用在列表循环,注意要用transition-group标签包裹,其他的都一样

<style>
    .box {
        width: 200px;
        height: 30px;
        border-bottom: 1px solid #000;
    }
</style>
<link rel="stylesheet" href="./node_modules/animate.css/animate.css">
<body>
    <div id="app">
        <input type="text" v-model='filter'>
        <transition-group enter-active-class='animate__bounceIn' leave-active-class='animate__bounceOut'>
            <div class="box animated" v-for='item in list2' :key='item'>
                {{item.title}}
            </div>
        </transition-group>
    </div>
    <script src="./node_modules/vue/dist/vue.min.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                filter: '',
                list: [{
                    title: '标题1'
                }, {
                    title: '标题2'
                }, {
                    title: '标题3'
                }, {
                    title: '标题4'
                }]
            },
            computed: {
                list2() {
      				// filter() 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
                    return this.list.filter((e) => {
      					// 查找字符串是否包含
                        return e.title.includes(this.filter)
                    })
                }
            }
        })
    </script>
</body>
</html>

使用css3的keyframes

其实就是通过控制类名的存在与否是否展示动画

<template>
  <div class="statistical">
    <template v-for="(item, i) in arr">
      <div
        :key="item"
        class="item"
        @touchstart="startAnimation(i)"
        @touchmove="stopAnimation"
        :class="{ animate__tada: showAnimate === i }"
      >
        <div class="name">{{ item.name }}</div>
        <div class="bottom">{{ item.number }}</div>
        <i :style="'background:' + item.color"></i>
      </div>
    </template>
  </div>
</template>
<script>
export default {
  data () {
    return {
      showAnimate: true,
    }
  },
  methods: {
    // 显示动画
    startAnimation (i) {
      this.long = true
      this.showAnimate = i
      setTimeout(() => {
        if (this.showAnimate) this.showAnimate = false
      }, 350)
      setTimeout(() => {
        this.$emit('toEcharts', i)
      }, 500)
    },
    // 停止动画
    stopAnimation () {
      this.showAnimate = false
    }
  }
}
</script>
@keyframes tada {
  from {
    transform: scale3d(1, 1, 1);
  }
  10%,
  20% {
    transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
  }
  30%,
  50%,
  70%,
  90% {
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
  }
  40%,
  60%,
  80% {
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
  }
  to {
    transform: scale3d(1, 1, 1);
  }
}
.animate__tada {
  animation-duration: 0.7s;
  animation-fill-mode: both;
  animation-name: tada;
}

组件的应用

组件的初始渲染流程生命周期

每个生命周期,做什么事情(在特定的时间下执行)

beforeCreate 创建之前

1.new Vue或者new组件,这个钩子就执行了,有this,会初始化this的生命周期(parentparent、children),可以拿到部分的事件方法以及原型方法onon、emit

2.在new的时候,会最先调用,这个钩子一般不会做太多功能,底层中可以做一些链的操作

created 创建

1.响应式的数据变化、观察完成了

2.dom还没创建完毕,获取不到真实dom元素,this.$el拿不到,打印会是undefined

beforeMount 挂载之前,创建dom

1.用响应式的数据变化和模板结合渲染成一个真实的页面,替换掉原来的div

2.必须要挂载了元素才会走(如果Vue实例里没设置el是不会执行此钩子的)

3.会检测template属性(有没有模板),如果有template就不会去渲染el挂载的标签的内容了,而是用template中的内容去替换,template属性叫内部模板,el对应标签的内容叫外部模板,具体的话是将template渲染成一个render函数

render函数(渲染函数,不算是生命周期)

1.beforeMount走完之后走render

2.render的参数 createElement 创建一个虚拟dom,再渲染成真实dom

3.返回createElement('div',{attrs:{id:123}},'hello'),表示创建一个div,属性是id=123,内容是hello,就会在页面渲染一个div标签

4.同时写template和render会渲染render中的内容

5.template本质上也是通过render进行渲染

mounted 挂载之后

1.可以获取到真实的元素 this.$el

2.在操作dom时,建议增加this.$nextTick

nextTick原理,先用promise.then异步执行,如果还未完成继续setImmediate,如果还未完成继续MutationObserver,还不行setTimeout,最终要等到数据更新完,dom渲染完,才会执行里面的回调

3.一般在这个钩子做数据请求,因为这里可以获取dom元素

数据变化触发的生命周期钩子

beforeUpdate 数据更新之前

1.在更新之前再去做某件事(例如,在更新之前再做一次修改)

updated 数据更新之后

1.更新后不能再修改值了(会死循环),这个钩子不能更改数据

销毁时触发的钩子

beforeDestroy 销毁前

1.一般情况下,用来清除定时器和移除绑定的方法事件

2.触发方式:组件销毁的时候,路由切换的时候,手动销毁的时候(vm.$destroy,用得很少)

destroyed 销毁后

1.把属性、数据、响应式的变化都去掉,但不会影响当前页面渲染

2.用得少

缓存用到的生命周期钩子(被 keep-alive 缓存的组件激活时调用)

activated 缓存前

deactivated 缓存后

为什么要有组件

1.复用,方便维护,拆分功能,

2.每个组件间作用域是隔离的,组件间互不干扰

组件的定义

1.组件就是一个自定义标签,可以代表一些特定的功能

2.主要封装css html js

组件的类型

  1. 全局组件
  • 全局组件是component,局部组件是components
  • 在任何组件中可以直接使用,而且不需要引入,在组件的模板中用,组件的数据必须是函数类型
<div id="app">
    <!-- 上面这种是不符合 w3c规范的 hr img br... -->
    <my-button :info="xxx"></my-button>
</div>
<!-- 组件的类型 全局组件 局部组件 函数式组件 异步组件 -->
<!-- 为什么要有组件 :复用,方便维护。拆分方便 ,每个组件间作用域是隔离的,组件间互不干扰,组件间的数据传递,组件就是一个自定义的标签,可以代表一些特定的功能,主要封装 css html js .vue文件-->
<script>
    // 数据来源 data props
    // 全局组件 在任何组件中可以直接使用,而且不需要引入,在组件的模板中用
    Vue.component('my-button',{ //每个组件都有自己的声明周期
        props:['info'],
        data(){ // 组件的数据必须是函数类型
             return {msg:"点我啊"} // 保证数据不会互相影响 所以通过一个函数返回一个唯一的对象
        },
        template:`<button>{{msg}}-{{info}}</button>`
    })
    let vm = new Vue({ // 根实例必须数据是对象类型,但是自定义的组件必须是函数类型
        el:'#app',
        data(){
           return { xxx:'试试'}
        }
    });
    // 父组件传递给子组件通过属性传递
</script>
  1. 局部组件
  • 需要在用到的组件引入

生命周期先走子组件,然后父组件,最后走根组件

可以使用attr获取所有没有用到的属性,通过vbind批量传递到孙子组件,但是属性用了的就获取不到了,用一个少一个,包含了父作用域中不作为prop被识别(且获取)attribute绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attr获取所有没有用到的属性,通过v-bind批量传递到孙子组件,但是属性用了的就获取不到了,用一个少一个,包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件——在创建高级别的组件时非常有用。

可以通过inheritAttrs属性控制是否需要在dom上显示传递的属性

<div id="app">
    <my-button :info="xxx" a="1" b="2" c="3"></my-button>
</div>
<script>
    let vm = new Vue({
        el:'#app',
        data:{
            xxx:'xxx'
        },
        components:{
            'my-button':{
                inheritAttrs:false, // 不在dom上显示传递的属性
                props:['info'],
                data(){ 
                    return {msg:"点我啊"}
                },
                 // this.$attrs 表示所有没有被用的属性,批量传递属性
                // mounted(){
                //     console.log(this.$attrs)
                // },  
                template:`<button>{{msg}}-{{info}}-<my v-bind="$attrs"></my></button>`,
                components:{
                    'my':{
                        props:['a','b','c'],
                        mounted(){
                            console.log(this.$attrs);
                        },
                        template:`<span>试试啊 {{a}} {{b}} {{c}}</span>`
                    }
                }
            }   
        }
    });
</script>
  1. 函数式组件

  2. 异步组件

  • 路由分割,点个按钮去加载个组件

组件间的通信 props events parent ref eventBus vuex

父传子

使用修饰符传递事件
  1. 直接给子组件加事件是不会触发的,需要加上.native修饰符

  2. native修饰符相当于给子组件内最外层的标签加事件,下面只要点击到div的范围就会触发show

<div id="app">
    <my-button @click.native='show'></my-button>
</div>
<script>
    Vue.component('my-button', {
        template: `<div><button>点我</button></div>`
    })
    let vm = new Vue({
        el: '#app',
        data: {

        },
        methods: {
            show() {
                alert('show')
            }
        }
    })
</script>
使用$listeners传递事件

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

1.子组件内,所有外面传进来的属性,存在$attrs中

2.子组件内,所有外面传进来的方法,存在$listenters中

<div id="app">
    <my-button @click='show' @mouseup='show2'></my-button>
</div>
<script>
    Vue.component('my-button', {
    // 一个一个传递方法
        template: `<div><button @click='$listeners.click' @mouseup='$listeners.mouseup'>点我</button></div>`
    })
    let vm = new Vue({
        el: '#app',
        data: {

        },
        mounted(){
            console.log(this.$listeners);
        },
        methods: {
            show() {
                alert('show')
            },
            show2() {
                alert('show2')
            }
        }
    })
</script>

3.如果一个个写$listeners下的方法很麻烦,可以用语法糖v-on

  • 下面写法效果和上面完全一样的
    template: `<div><button v-on='$listeners'>点我</button></div>` // 把事件整体传入
  • 还有种写法,一样的可以触发在父组件中绑定的事件,也会执行show方法
   <div id="app">
    <!-- 其实给组件绑定事件就相当于 this.$on('click',show)  这里$on就相当于发布 -->
    <my-button @click='show' @mouseup='show2'></my-button> 
</div>
<script>
    Vue.component('my-button', {
        // template: `<div><button v-on='$listeners'>点我</button></div>`, // 把事件整体传入
        // 这里$emit相当于订阅
        template: `<div><button @click='$emit('click')'>点我</button></div>`, //触发自己身上的click事件
        template: `<div><button @click='test'>点我</button></div>`,
        methods: {
            test() {
                this.$emit('click')
            }
        }
    })
    let vm = new Vue({
        el: '#app',
        data: {

        },
        mounted() {
            console.log(this.$listeners);
        },
        methods: {
            show() {
                alert('show')
            },
            show2() {
                alert('show2')
            }
        }
    })

props

1.Prop 是你可以在组件上注册的一些自定义属性。当一个值传递给一个 prop 属性的时候,它就变成了那个组件实例的一个 变量

<blog-post data="My journey with Vue"></blog-post>
props:{
	data:{ // 数据名称,组件实例中访问这个值,就像访问 data 中的值一样。
    	type:String, // 校验类型
        default:'', // 默认值
        required:false, // 是否必填项
        // 自定义验证函数会将该 prop 的值作为唯一的参数代入。该函数返回一个 falsy 的值 
        validator(){
        	
        }
    }
}

2.使用 v-bind 绑定一个全是属性的对象,可以传递整个对象到子组件props,和一个个传一样的效果,但是使用是要用$attrs获取,用在给ui框架的组件设置属性是非常方便,直接渲染在标签上了

<div id="app">
    <blog-post v-bind="posts"></blog-post>
</div>
<script>
    Vue.component('blog-post', {
        template: `
                    <div class="blog-post">
                        <h3>{{$attrs.id}}</h3>
                        <div>{{$attrs.title}}</div>
                        <div>{{$attrs.maxlength}}</div>
                        <div>{{$attrs.disabled}}</div>
                        <div>{{$attrs.style}}</div>
                        <div>{{$attrs.text}}</div>
                    </div>
                `,
        mounted() {
            console.log(this.post);
        }
    })
    let vm = new Vue({
        el: '#app',
        data: {
            posts: {
                id: 1,
                title: 'My journey with Vue',
                maxlength: 30,
                disabled: false,
                style: 'width:816px',
                text: '123',
                text2: '234',
            }
        }
    })
</script>

3.在html中建议不要给标签的大写,属性名字建议单词连接用-代替驼峰

slot 插槽

1.父组件,在标签中间写内容。子组件,用slot标签接收

<div id="app">
    <panel>
        <span style="color:red">999</span>
    </panel>
</div>
<script>
    Vue.component('panel', {
        template: `<div>123
                        slot有name,不写的话默认是default
                        <slot name='default'>name没匹配上就会展示标签中间的内容</slot>
                    </div>`
    })
    let vm = new Vue({
        el: '#app',
        data: {

        }
    })
</script>

2.具名插槽,slot有name属性,不写的话默认是default,如果修改了那么的话,父组件调用的地方要用template包裹并且定义v-slot:名字(可以简写#名字)

<div id="app">
    <panel name='default2'>
        //<template v-slot:default2>
         <template #default2>
            <span style="color:red">999</span>
        </template>
    </panel>
</div>
<script>
    Vue.component('panel', {
        template: `<div>123
                        <slot name='default2'>name没匹配上就会展示标签中间的内容</slot>
                    </div>`
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

3.作用域插槽,让插槽内容能够访问子组件中的数据

<div id="app">
    <panel name='default2'>
        <!-- 作用域插槽,让插槽内容能够访问子组件中的数据 -->
        // <template v-slot:default2='data2'>
        <template #default2='data2'>
            <span style="color:red">999{{data2}}</span>
        </template>
    </panel>
</div>
<script>
    Vue.component('panel', {
        template: `<div>123
                        <slot v-bind:data2='demo2' name='default2'>name没匹配上就会展示标签中间的内容							</slot>
                    </div>`,
        data() {
            return {
                demo2: '邓良斌'
            }
        }
    })
    let vm = new Vue({
        el: '#app',

    })
</script>
parentparent和children

1.可以使用parentparent和children直接获取参数和方法,官网不太建议直接这样使用

2.每个组件有一个唯一标识,_uid,它是组件自身的,是组件的id号(官网我都没找到文档)

mixins

1.混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

2.选项合并,当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

3.混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。

extend

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

在 vue 项目中,我们有了初始化的根实例后,所有页面基本上都是通过 router 来管理,组件也是通过 import 来进行局部注册,所以组件的创建我们不需要去关注,相比 extend 要更省心一点点。但是这样做会有几个缺点: 1、组件模板都是事先定义好的,如果我要从接口动态渲染组件怎么办? 2、所有内容都是在 #app 下渲染,注册组件都是在当前位置渲染。如果我要实现一个类似于 window.alert() 提示组件要求像调用 JS 函数一样调用它,该怎么办? 这时候,Vue.extend + vm.$mount 组合就派上用场了。

我们照着官方文档来创建一个示例:

import Vue from 'vue'
const testComponent = Vue.extend({
  template: '<div>{{ text }}</div>',
  data: function () {
    return {
      text: 'extend test'
    }
  }
})

然后我们将它手动渲染:

// 这时候,我们就将组件渲染挂载到 body 节点上了。
const extendComponent = new testComponent().$mount()
// 我们可以通过 $el 属性来访问 extendComponent 组件实例:
document.body.appendChild(extendComponent.$el)

可以用extend做一个 alert 组件来实现类似于原生的全局调用。

provideprovide inject

1.provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

2.这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

Vue.component("children", {
    template: `<div>{{test}}子组件<demo2></demo2></div>`,
    inject: ['test'],
    mounted() {
        console.log(this.test, '子组件可以拿到');
    },
    components: {
        demo2: {
            template: `<div>孙组件{{test}}</div>`,
            inject: ['test'],
            mounted() {
                console.log(this.test, '孙组件可以拿到');
            },
        }
    }
})
let vm = new Vue({
    el: '#app',
    // 允许一个祖先组件向其所有子孙后代注入一个依赖,
    // 不论组件层次有多深,并在其上下游关系成立的时间里始终生效
    provide: {
        test: "1239999"
    },
})

ref

1.可以直接获取组建的实例 2.ref 的三种情况 如果给dom 就是一个dom 如果给 v-for 出来的就是数组 如果给组件 那就是组件的实例

<div id="app">
    <!-- ref 的三种情况 如果给dom 就是一个dom 如果给 v-for 出来的就是数组 如果给组件 那就是组件的实例 -->
     <div ref="div" v-for="i in 3"></div>
     <parent ref="parent"></parent>
 </div>
 <script>
     Vue.component("parent",{
       data(){
         return {isShow:false}
       },
       methods:{
         show(){
             this.isShow = true;
         },
         hide(){
             this.isShow = false;
         }
       },
        template:`<div> <span v-if="isShow">parent</span> </div>`
     });
     let vm = new Vue({
         el:'#app',
         mounted() {
             console.log(this.$refs.div)
             this.$refs.parent.show();
             setTimeout(()=>{
                 this.$refs.parent.hide();
             },1000)
         },
     })
 </script>
evenBus兄弟组件通信
<div id="app">
    <!-- 这里接收的组件要先渲染,发送的组件要后渲染,不然不会触发 -->
    <son2></son2>
    <son1></son1>
</div>
<script>
    // 平级组件通信 可以通过共同的父亲传参
    // 一个全局的发布订阅的方式
    // eventBus 比较适合 简单的数据流
    // 通信的数据 (vuex中)
    // new一个vue作为总线
    Vue.prototype.$bus = new Vue();
    Vue.component('son1', {
        template: `<div>大哥</div>`,
        mounted() {
            console.log('发送');
            // 使用$emit方法,触发当前实例上的事件,用以发送
            this.$bus.$emit('busDemo', '二弟,饺子好吃')
        }
    })
    Vue.component('son2', {
        template: `<div>二弟</div>`,
        mounted() {
            console.log('接收');
            // 使用$on方法,监听当前实例上的自定义事件,用以接收
            this.$bus.$on('busDemo', (data) => {
                console.log(data, '接收内容');
            })
        }
    })
    let vm = new Vue({
        el: '#app'
    })
</script>