vue学习笔记

2年前 (2022) 程序员胖胖胖虎阿
174 0 0

VUE学习笔记

认识Vue

    <body>
        <!-- 
            初识Vue:
                1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
                2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
                3.root容器里的代码被称为【Vue模板】;
                4.Vue实例和容器是一一对应的;
                5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
                6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
                7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

                注意区分:js表达式 和 js代码(语句)
                        1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
                                    (1). a
                                    (2). a+b
                                    (3). demo(1)
                                    (4). x === y ? 'a' : 'b'

                        2.js代码(语句)
                                    (1). if(){}
                                    (2). for(){}
        -->

        <!-- 准备好一个容器 -->
        <div id="demo">
            <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
        </div>

        <script type="text/javascript" >
            Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

            //创建Vue实例
            new Vue({
                el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
                data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
                    name:'atguigu',
                    address:'北京'
                }
            })

        </script>
    </body>

1、Vue的模板语法

重点说明

如果在标签中使用属性绑定值,是字符串,动态绑定则是会去解析表达式。例如

//字符串 
<h1 value="1"></h1> 
//动态绑定是解析表达式 数字1
 <h1 :value="1"></h1> 
Vue模板语法有2大类:
            1.插值语法:
                    功能:用于解析标签体内容
                    写法:{{xxxx}},xxx会作为表达式去解析,且可以自动读取到data中的属性
            2.指令语法:
                    功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)
                    举例:v-bind:href="xxxxxx" 或  简写为:
                    备注:Vue中有很多的指令,此处我们只是拿v-bind举个例子
<body>
    <div id="root">
        <h2>插值语法</h2>
        <h4>{{msg}}</h4>
        <h4>{{msg.toUpperCase()}}</h4>
        <h2>指令语法</h2>
        <!-- 静态属性直接写 或者绑定使用v-bind后使用字符串 -->
        <a :href="'http://www.baidu.com'">动态绑定属性是字符串</a>
        <!-- 属性是动态的必须要使用v-bind:href="" 双引号中的内容是data中的变量-->
        <a v-bind:href="url">百度一下</a>
    </div>
    <script>      
        new Vue({           
            el:"#root", 
            data:{
                msg:"插值语法Insert",
                url:"http://www.baidu.com",
            },
        })
    </script>
</body>

2、数据绑定及双向绑定原理

Vue中有2种数据绑定的方式:
                    1.单向绑定(v-bind):数据只能从data流向页面。
                    2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
                        备注:
                                1.双向绑定一般都应用在表单类元素上(如:input、select等)
                                2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
 <div id="root">
        <p>{{msg}}</p>
       单项数据绑定(v-bind):<input type="text" :value="msg"><br>
       <!-- v-model:value="msg" 针对表单类元素-->
       双向数据绑定(v-model):<input type="text" v-model="msg"><br>
       双向数据绑定(v-model):<input type="text" v-model:value="msg">
    </div>
    <script>
        new Vue({
            el:"#root",
            data:{
                msg:"",
            }
        });

v-model的底层原理:

v-model指令在表单input、textarea、select等元素上创建双向数据绑定,本质是语法糖,v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
    text和textarea元素使用value属性和input事件
    checkbox和radio使用checked属性和change事件
    select字段将value作为prop并将change作为事件
以input表单作为案例
<!-- v-model 的底层原理  通过vm.msg向input标签的value单向传递   反向传递:通过input事件对msg进行赋值 $event为事件的对象 target.value获取输入的值-->
   
        <!-- :value动态绑定value值  触发input事件 -->
       <input type="text" v-bind:value="msg" v-on:input="msg=$event.target.value">
       <textarea  cols="30" rows="10" :value="msg" @input='msg=$event.target.value'></textarea>
       <!-- :checked动态绑定 触发change事件 -->
        <input type="checkbox" :checked='msg' @change="msg=$event.target.value">
        <input type="radio" :checked='msg' @change="msg=$event.target.value">
        <input type="radio" :checked='!msg' @change="msg=!$event.target.value">

        <!-- :value值  触发change事件 -->
        <select :value="msg" @change="msg=$event.target.value">
            <option value="0">0</option>
            <option value="1">1</option>
        </select>
    </div>
    <script>
        var app = new Vue({
            el:"#app",
            data:{
                msg:"",
            },

如果在自定义组件中,v-model默认会利用名为value的prop和名为input的事件

<div id="app">
        <p>{{msg}}</p>
        <!-- 默认是给my-input 动态绑定value值  并添加一个input事件 -->
        <!-- <my-input :value="msg" v-on:input="msg=$event"></my-input> -->
        <my-input v-model="msg"></my-input>
    </div>
    

    <script>
        Vue.component("my-input",{
            props:["value"],
            template:`
            <div>
                <p>我是组件</p>
                <input type="text" :value="value" v-on:input="$emit('input',$event.target.value)">
            </div>
            `
        });
        var app =new Vue({
            el:"#app",
            data:{
                msg:"初始值",
            }
        })

3、data和el的两种写法

data与el的2种写法:
                    1.el有2种写法:
                            (1).new Vue时候直接传递el属性 ---- 常用
                            (2).先new Vue 再通过vm.$mount('#root')指定el属性 ---- 不常用 
                    //写template会替代el中的全部内容,替换时必须有根节点包含。如果没有template会默认el中的内容为渲染对象
                    2.data有2种写法:
                            (1).对象式: 非组件化编码时可以写对象,也可以写函数。
                            (2).函数式:组件化编码必须使用函数式的data,组件数据可以复用。
            Vue中的一个最最重要的原则:
                    由Vue所调用的函数,都不要写成箭头函数,一旦写了箭头函数,this就不对了(或许是undefined,或许是Window)
<script>
        // 第一种写法
        const vm = new Vue({
            el:"#root",
            data:{
                msg:"MVVM",
            },
        });       
        // 第二种写法
        const vm = new Vue({
             data(){
                return {
                    msg:"MVVM"
                }
            },
            template:`
            <div>
                <h1>{{msg}}</h1>    
            </div>
            `
        })
        vm.$mount('#root')
        
        setTimeout(()=>vm.msg="hello",1000)

        // data的第一种写法  对象
        // data的第二种写法  函数 返回一个对象(组件中写的多)
    </script>

4、MVVM模型

MVVM模型
                        1. M:模型(Model) :data中的数据
                        2. V:视图(View) :模板代码
                        3. VM:视图模型(ViewModel):Vue实例
            观察发现:
                        1.data中所有的属性,最后都出现在了vm身上。
                        2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。

5、数据代理

5.1 Object.defineProperty方法

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

备注:应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。

语法:Object.defineProperty(obj, prop, descriptor)

obj:要定义属性的对象

prop:要定义或修改的属性的名称或 Symbol

descriptor:要定义或修改的属性描述符,简写就是默认的value值。

描述符默认值汇总

  • 拥有布尔值的键 configurableenumerable(调用Object.keys()获取不到) 和 writable 的默认值都是 false
  • 属性值和函数的键 valuegetset 字段的默认值为 undefined
        let _data = { msg: "小明" };
        let vm = {};
//通过对象方法给vm对象赋值属性msg
        Object.defineProperty(vm, "msg", {
            set(value) {
                // value值就是外部赋值的值
                console.log(value);
                // this指向vm对象
                console.log(this);
               // 调用一次进行赋值会无限递归
                this.msg = value;
            },
            get() {
                return _data.msg;
            }
        })

        vm.msg = "123"
<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
        <script type="text/javascript" >
            let obj = {x:100}
            let obj2 = {y:200}
//通过Getter读取obj.x的值,修改obj2的新增x属性是,实际调用Setter是对obj.x的赋值
            Object.defineProperty(obj2,'x',{
                get(){
                    return obj.x
                },
                set(value){
                    obj.x = value
                }
            })
        </script>

5.2关于Vue中的数据代理:

1.什么是数据代理?
(1).配置对象data中的数据,会被收集到vm._data中,然后通过,Object.defineProperty让vm上拥有data中所有的属性。
(2).当访问vm的msg时,返回的是_data当中同名属性的值
(3).当修改vm的msg时,修改的是_data当中同名属性的值
2.为什么要数据代理?
为了更加方便的读取和修改_data中的数据,不做数据代理,就要:vm._data.xxx访问数据
3.扩展思考?—— 为什么要先收集在_data中,然后再代理出去呢?
更高效的监视数据(直接收集到vm上会导致监视效率太低)
4.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
vue底层,当data的数据变化,去更新dom,双向绑定dom元素的data变化也更新dom,本质是数据变化

验证data中的数据就是vm下的_data

 const data = {
      name: "尚硅谷",
      address: "宏福科技园",
    };

    const vm = new Vue({
      el: "#root",
      data
    });
//控制台上
vm._data === data //true

数据代理的原理

// 现在获取msg只能通过vm._data获取
// 如何实现通过vm.msg进行获取,将data下的键值挂载到vm下,每改变data中msg的值,vm.msg也能同步改变?

        // 模拟Vue实例下vm的_data参数对象,配置对象data中的数据,会被收集到vm._data中
        let _data = { msg: "小明" };
        // vm模拟Vue实例化的对象
        let vm = {};
        // 实现方式:为vm对象添加属性值
        Object.defineProperty(vm, "msg", {
            set(value) {
                // value值就是外部赋值的值
                console.log(value);
                // this指向vm对象
                console.log(this);
                //实现双向绑定 修改任意的一个值 两个都会发生改变
                _data.msg=value;
            },
            get() {
                return _data.msg;
            }
        })
        // 外部改变_data.msg的值
        _data.msg = "123"
        console.log(vm.msg);

6、事件处理

事件的基本使用:
                            1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
                            2.事件的回调需要配置在methods对象中,最终会在vm上;
                            3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
                            4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
                            5.@click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参;

6.1事件的修饰符

Vue中的事件修饰符:

​ 1.prevent:阻止默认事件(常用);

​ 2.stop:阻止事件冒泡(常用);

​ 3.once:事件只触发一次(常用);

​ 4.capture:使用事件的捕获模式,默认是事件冒泡方式

​ 5.self:只有event.target是当前操作的元素时才触发事件;

​ 6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

1.Vue中常用的按键别名:

​ 回车 => enter

​ 删除 => delete (捕获“删除”和“退格”键)

​ 退出 => esc

​ 空格 => space

​ 换行 => tab (特殊,必须配合keydown去使用)

​ 上 => up

​ 下 => down

​ 左 => left

​ 右 => right

​ 2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

​ 3.系统修饰键(用法特殊):ctrl、alt、shift、meta

​ (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。

​ (2).配合keydown使用:正常触发事件。

​ 4.也可以使用keyCode去指定具体的按键(不推荐)

​ 5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为

<div id="root">
        <h2>欢迎来到{{school}}学习</h2>

        <!-- 绑定事件---标准方式 -->
        <button v-on:click="show1">点我提示:信息1(v-on绑定)</button> <br /><br />

        <!-- 绑定事件---简写方式 -->
        <button @click="show1">点我提示:信息1(@绑定)</button> <br /><br />

        <!-- 绑定事件---传递参数 -->
        <button @click="show2($event,666)">点我提示:信息2 + 传递的参数</button> <br /><br />

        <!-- 绑定事件---阻止默认行为,prevent叫事件修饰符 -->
        <a href="https://www.baidu.com" @click.prevent="show3">点我提示:信息3 (阻止默认行为)</a> <br /><br />

        <!-- 绑定事件---阻止冒泡,事件修饰符可以连写,且顺序可以随意改变 -->
        <div @click="show4">
            <a href="https://www.baidu.com" @click.stop.prevent="show4">点我提示:信息4 (阻止冒泡)</a>
        </div><br />
                
                <!-- 只有event.target是当前操作的元素时才触发事件; 点击里面时不会触发外层事件,阻止冒泡 -->
            <div class="demo1" @click.self="showInfo">
                <button @click="showInfo">点我提示信息</button>
            </div>
//组合键
<input type="text" placeholder="按下回车提示输入" @keydown.ctrl.13="showInfo">
        <!-- 键盘事件 -->
        <input @keyup.enter="show5" type="text" placeholder="按下回车提示数据">
        <!-- <input @keyup.13="show5" type="text" placeholder="按下回车提示数据"> -->
        <!-- <input @keyup.37="show5" type="text" placeholder="按下左箭头提示数据"> -->
        <!-- <input @keyup.arrow-left="show5" type="text" placeholder="按下左箭头提示数据"> -->
        <!-- <input @keyup.left="show5" type="text" placeholder="按下左箭头提示数据"> -->
        <!-- <input @keyup.16="show5" type="text" placeholder="按下shift提示数据"> -->

    </div>

    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: { //配置数据
                school: '尚硅谷',
            },
            methods: { //用于配置方法
                show1(event) { //此处的show1一定一定不要写成箭头函数,否则this就是Window了
                    //console.log(this) //this是vm
                    //console.log('信息1',event.target.innerText)
                    alert('信息1')
                },
                show2(event, number) {
                    console.log(event)
                    alert('信息2---' + number)
                },
                show3(event) {
                    //event.preventDefault(); //靠程序员手动阻止默认行为
                    alert('信息3')
                },
                show4(event) {
                    // event.stopPropagation(); //靠程序员手动阻止冒泡
                    alert('信息4')
                },
                show5(event) {
                    // if(event.keyCode !== 13) return //靠程序员自己判断按键
                    // console.log(event.keyCode) //输出按键编码值
                    // console.log(event.key) //输出按键名称
                    alert(event.target.value)
                }
            }
        })
    </script>

7、计算属性computed和数据监视watch

7.1 计算属性

计算属性:

​ 1.定义:要用的属性不存在,要通过已有属性计算得来,默认是getter读取

​ 2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

​ 3.get函数什么时候执行?

​ (1).初次读取时会执行一次。

​ (2).当依赖的数据发生改变时会被再次调用。

​ 4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

​ 5.备注:

​ 1.计算属性最终会出现在vm上,直接读取使用即可。

​ 2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

姓名案例:

  1. 使用插值语法实现

    <div id="root">
                姓:<input type="text" v-model="firstName"> <br/>
                名:<input type="text" v-model="lastName"><br/>
                <span>全名:{{firstName + '-' + lastName}}</span>
            </div>
    
            <script type="text/javascript" >
                new Vue({
                    el:'#root',
                    data:{
                        firstName:'张',
                        lastName:'三'
                    }
                })
            </script>
  2. methods方法实现

            <div id="root">
                姓:<input type="text" v-model="firstName"> <br/>
                名:<input type="text" v-model="lastName"><br/>
                <span>全名:{{getFullName()}}</span>
            </div>
    
            <script type="text/javascript" >
                new Vue({
                    el:'#root',
                    data:{
                        firstName:'张',
                        lastName:'三'
                    },
                    methods:{
                        getFullName(){
                            console.log('getFullName被调用了')
                            return this.firstName + '-' + this.lastName
                        }
                    }
                })
            </script>
  3. computed方法实现

            <div id="root">
                姓:<input type="text" v-model="firstName"> <br/><br/>
                名:<input type="text" v-model="lastName"><br/><br/>
                <span>全名:{{fullName}}</span><br/><br/>
                全名: <input type="text" v-model="fullName">
            </div>
    
            <script type="text/javascript" >
                const vm = new Vue({
                    el:'#root',
                    data:{
                        firstName:'张',
                        lastName:'三',
                    },
                    computed:{
                        /* 
                            1.fullName是谁在调用?---Vue
                            2.fullName什么时候调用?初次渲染会调用、当依赖的属性值发生变化
                        */
                        //简写---相当与只指定了get,没有指定set   只能实现单向数据绑定由fullName--》全名的input
                        fullName(){ 
                            console.log('fullName')
                            return this.firstName + '-' + this.lastName
                        }
                        
                        //完整写法----set和get都指定了   实现双向数据绑定
                        /* fullName:{
                            set(value){ //fullName被修改时set被调用,set中的this是vm,set会收到修改的值
                                const arr = value.split('-')
                                this.firstName = arr[0]
                                this.lastName = arr[1]
                            },
                            get(){ //fullName被读取时get被调用,get中的this是vm
                                console.log('get')
                                return this.firstName + '-' + this.lastName
                            }
                        } */
                    }
                })
                
                console.log(vm)
            </script>

methods和computed对比

        <div id="root">
            <h2>{{x}}</h2>
            <h2>{{y()}}</h2>
            <h2>{{z}}</h2>
            <button @click="y">点我调用y方法</button> <br/><br/>
            <button @click="y()">点我调用y方法</button> <br/><br/>
            展示x的值:<input type="text" v-model="x">
        </div>
        
        <script type="text/javascript" >
            const vm = new Vue({
                el:'#root', //指定vm为哪个容器服务
                data:{ //驱动页面显示的数据都放在这里
                    x:100 //x最终会通过数据代理的方式放在vm身上
                },
                methods:{ //所有用到的函数都配置在这里
                    y(){ //y直接被放在vm身上
                        console.log('y被调用了')
                        return 200
                    }
                },
                computed:{
                    z:{ //z直接被放在vm身上了
                        set(value){ //z的值被改变时,set执行,set中的this是vm,set会收到修改的值
                            console.log('有人修改z了,修改的值为:',value)
                        },
                        get(){ //z的值被读取时,或z依赖的值发生变化时,get执行,get中的this是vm,前提是:页面中要用到z
                            console.log('get被调用了')
                            return this.x*1 +1
                        }
                    }
                }
            })

            console.log(vm)
        </script>

7.2 监视方法

  1. watch方法实现

    监视属性watch:
                1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
                2.属性必须存在,才能进行监视!!
                3.监视的两种写法:
                    (1).new Vue时传入watch配置
                    (2).通过vm.$watch监视
        深度监视:
                            (1).Vue中的watch默认不监测对象内部值的改变(一层)。
                            (2).配置deep:true可以监测对象内部值改变(多层)。
                    备注:
                            (1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
                            (2).使用watch时根据数据的具体结构,决定是否采用深度监视。
    
    <div id="root">
       <input type="text" v-model="firstName"> <br>
       <input type="text" v-model="lastName"><br>
        <span>全名:{{fullName}}</span><br>
    </div>
    <script>
       const vm = new Vue({
           el:"#root",
           data:{
               firstName:'张',
               lastName:'三',
               fullName:''
           },
           watch:{
            /* 
                        1.watch中的firstName什么时候调用?data中的firstName被改变的时调用
                        2.watch中的firstName的this是谁?---vm
            */
            // 监测姓-----精简写法
            //    firstName(newVal,oldVal){
            //        console.log('firstName被修改',newVal,oldVal);
            //        this.fullName = newVal+'-'+this.lastName;
            //    },
            //监测名-----精简写法
            //    lastName(newVal,oldVal){
            //        console.log('lastName被修改',newVal,oldVal);
            //        this.fullName=this.firstName+'-'+newVal
            //    },
            //监测姓-----完整写法  
            //    完整写法
               firstName:{
                   immediate:true,
                    handler(newVal,oldVal){
                        this.fullName=newVal+'-'+this.lastName
                    }
               }

           }

       });
       //监测名-----完整写法
       vm.$watch('lastName',{
           immediate:true,//若immediate为true则handler在初始化时,就会调用一次,以后就看firstName的改变了
           handler(newVal,oldVal){
               setTimeout(()=>{
                   this.fullName = this.firstName+'-'+newVal;
               },2000)
           },
           deep:true
       })
//简写
      vm.$watch('lastName',(newVal,oldVal)=>{
               setTimeout(()=>{
                   this.fullName = this.firstName+'-'+newVal;
               },2000)
           }
       })
    </script>
    computed和watch之间的区别:
        1.只要是computed能完成的功能,watch都可以完成
        2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
    备注:
        1.所有被Vue所调用(管理)的函数,都不要写箭头函数 ----- 例如:watch中的函数、computed中的函数
        2.所有不是被Vue所调(管理)的函数,都要写成箭头函数 --- 例如:定时器的回调、ajax的回调等等
        3.watch就是Vue给我提供的一个监测数据改变的手段,至于数据发生改变后,要做什么,得看具体的业务了逻辑。
            例如:
                需要新的值、旧的值作比较,决定接下来要干什么
                不要值,只要数据改变了,就要发请求等等

8、绑定样式

    绑定样式:
                    1. class样式
                                写法:class="xxx" xxx可以是字符串、对象、数组。
                                        字符串写法适用于:类名不确定,要动态获取。
                                        对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
                                        数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
                    2. style样式
                                :style="{fontSize: xxx}"其中xxx是动态值。
                                :style="[a,b]"其中a、b是样式对象。
<div id="root">
        <!-- class的字符串写法,类名不确定动态获取 -->
        <h2 class="box" :class="myStyle">{{title}}</h2>
        <!-- 对象 -->
        <h2 class="box" :class="{classB:hasB,classC:hasC}">{{title}}</h2>
        <!-- 三元 -->
        <h2 class="box" :class="hasB?'classB':''">{{title}}</h2>
        <!-- 数组 -->
        <h2 class="box" :class="[a,b,c]">{{title}}</h2>
        <!-- 绑定style -->
        <h2 class="box" :class="[a,b,c]" :style="{fontSize:size+'px'}">{{title}}</h2>
        <h2 class="box" :class="[a,b,c]" :style="obj">354563</h2>
        <h2 class="box" :class="[a,b,c]" :style="style">354563</h2>

    </div>
    <script>
        new Vue({
            el:"#root",
            data:{
                title:"2021-07-11",
                myStyle:'classA',
                hasB:true,
                hasC:true,
                a:"classA",
                b:"classB",
                c:"classC",
                size:40,
                style:"border:10px solid",
                obj:{
                    fontSize:"16px",
                    color:"blue",
                }

            }
        })
    </script>

9、条件渲染

条件渲染:
1.v-if
写法:
(1).v-if="表达式" 
(2).v-else-if="表达式"
(3).v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
<body>
    <div id="root">
        <h2>今天天气很{{isHot ? '炎热':'凉爽'}}</h2>
        <button @click="isHot=!isHot">切换天气</button>
    <!-- v-show 条件渲染 -->
        <div v-show="isHot">
            <img src="https://s3.ax1x.com/2020/12/13/reC1IA.jpg" alt="">
            <span>建议:心境自然就会凉</span>
        </div>
        <div v-show="!isHot">
            <img src="https://s3.ax1x.com/2020/12/13/reCaqg.jpg" alt="">
            <span>建议:妈妈告诉你要穿秋裤了</span>
        </div>

            <!-- v-if  v-else-if v-else 必须是连续的-->
        <div v-if="isHot">
                <img src="https://s3.ax1x.com/2020/12/13/reC1IA.jpg" alt=""><br/>
                <span>建议:心境自然就会凉</span>
            </div>

        <div v-else>
                <img src="https://s3.ax1x.com/2020/12/13/reCaqg.jpg" alt=""><br/>
                <span>建议:妈妈告诉你要穿秋裤了</span>
            </div>

    </div>
    <script>
    const vm = new Vue({
            el:"#root",
            data:{
               isHot:true,

            }
        })
    </script>
</body>

10、列表渲染

10.1 基本列表

v-for指令:
                        1.用于展示列表数据
                        2.语法:v-for="(item, index) in xxx" :key="yyy"
                        3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<body>
    <div id="root">
        <!-- v-for遍历数组 -->
        <ul>
            <li v-for="(p,index) in persons" :key="p.id">{{p.name}}--{{p.sex}}--{{p.age}}</li>
        </ul>
        <!-- v-for遍历对象 -->
        <ol>
            <li v-for="(value,key) in car" :key="value">{{value}}</li>
        </ol>
        <!-- v-for遍历字符串 -->
        <ol>
            <li v-for="(value,index) in str" :key="value">{{value}}---{{index}}</li>
        </ol>
        <!-- v-for遍历数字 -->
        <ol>
            <li v-for="(value,index) in num" :key="value">{{value}}---{{index}}</li>
        </ol>
    </div>
    <script>
        const vm = new Vue({
            el: "#root",
            data: {
                persons: [{
                    id: '001',
                    name: '老刘',
                    age: 20,
                    sex: '男'
                },
                {
                    id: '002',
                    name: '老李',
                    age: 19,
                    sex: '女'
                },
                {
                    id: '003',
                    name: '老王',
                    age: 18,
                    sex: '男'
                },
                {
                    id: '004',
                    name: '老张',
                    age: 17,
                    sex: '女'
                },
                ],
                car: {
                    name: '奔驰c63',
                    price: '60万',
                    color: '灰色'
                },
                str: 'abcde',
                num:5,

            }
        })
    </script>
</body>

使用template进行v-for遍历:template会不显示

[v-forv-if 一同使用]:

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中

   <div id="app">
        <template v-for="n in 5">
            <p>{{n}}------你好</p>    
        </template>
    </div>    

    <script>
        var app = new Vue({
            el:"#app",
        });
    </script>

10.2 key的原理

面试题:react、vue中的key有什么作用?(key的内部原理)
                        
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。

如果input输入框不加key属性,input输入框会复用,出现混乱的现象

<div id="root">
            <!-- 遍历数组 -->
            <h2>人员列表(遍历数组)</h2>
            <button @click.once="add">添加一个老刘</button>
            <ul>
                <li v-for="(p,index) of persons" :key="index">
                    {{p.name}}-{{p.age}}
                    <input type="text">
                </li>
            </ul>
        </div>

        <script type="text/javascript">
            Vue.config.productionTip = false
            
            new Vue({
                el:'#root',
                data:{
                    persons:[
                        {id:'001',name:'张三',age:18},
                        {id:'002',name:'李四',age:19},
                        {id:'003',name:'王五',age:20}
                    ]
                },
                methods: {
                    add(){
                        const p = {id:'004',name:'老刘',age:40}
                        this.persons.unshift(p)
                    }
                },
            })
        </script>

10.3 列表过滤

<body>
    <!-- 
        想要对数据加工后再展示,且不想破坏原数据,最好用计算属性。
    -->
    <!-- 准备好一个容器-->
    <div id="root">
        <h2>人员列表</h2>
        <input v-model="keyWord" type="text" placeholder="请输入姓名">
        <ul>
            <li v-for="(p,index) in fmtPersons" :key="p.id">
                {{p.name}}--{{p.sex}}--{{p.age}}岁
            </li>
        </ul>
    </div>

    <script type="text/javascript">
        var app = new Vue({
            el: '#root',
            data: {
                keyWord: '',
                persons: [{
                        id: '001',
                        name: '马冬梅',
                        age: 35,
                        sex: '女'
                    },
                    {
                        id: '002',
                        name: '周冬雨',
                        age: 20,
                        sex: '女'
                    },
                    {
                        id: '003',
                        name: '周杰伦',
                        age: 41,
                        sex: '男'
                    },
                    {
                        id: '004',
                        name: '温兆伦',
                        age: 25,
                        sex: '男'
                    },
                ]
            },

            //使用computed过滤,优势:不影响原数据
            computed: {
                //fmtPersons是挂载在Vue实例上的属性
                fmtPersons() {
                    const {
                        persons,
                        keyWord
                    } = this
                    return persons.filter(p => p.name.indexOf(keyWord) !== -1)
                }
            }

            //在watch修改原数据,会导致原数据的丢失
            /* watch:{
                keyWord(value){
                    const arr = this.persons.filter( p => p.name.indexOf(value) !== -1)
                    this.persons = arr
                }
            } */
        })
    </script>

显示过滤排序后的结果

1、可以使用计算属性
2、使用方法v-for=‘item in set(data)’
<ul v-for="set in sets">
  <li v-for="n in even(set)">{{ n }}</li>
</ul>
//数组中嵌套数组不适合使用计算属性
data: {
  sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

10.4 列表排序

<body>
    <div id="root">
        <!-- 
        想要对数据加工后再展示,且不想破坏原数据,最好用计算属性。
    -->
        <input type="text" v-model="keyWord" placeholder="请输入姓名">
        <button @click="sortType=1">年龄升序↑</button>
        <button @click="sortType=2">年龄降序↓</button>
        <button @click="sortType=0">原顺序</button>
        <ul>
            <li v-for="(p,index) in fmtPersons" :key="p.id">
                {{p.name}}---{{p.sex}}---{{p.age}}
            </li>
        </ul>
    </div>
    <script>
        var vm = new Vue({
            el:"#root",
           
            data:{
                keyWord:'',
                sortType:0, //0原顺序 1升序 2降序
                persons: [{
                        id: '001',
                        name: '马冬梅',
                        age: 35,
                        sex: '女'
                    },
                    {
                        id: '002',
                        name: '周冬雨',
                        age: 20,
                        sex: '女'
                    },
                    {
                        id: '003',
                        name: '周杰伦',
                        age: 41,
                        sex: '男'
                    },
                    {
                        id: '004',
                        name: '温兆伦',
                        age: 25,
                        sex: '男'
                    },
                ]
            },
            computed:{
                fmtPersons(){
                    // 1.获取data中的数据
                    const {
                        persons,
                        keyWord,
                        sortType
                    }=this;
                    // 2.根据关键词过滤
                    let arr = persons.filter(p=>p.name.indexOf(keyWord)!==-1)

                    // 3.按需要排序为0时不进行排序
                    if(sortType){
                        arr.sort((a,b)=>{
                            // 排序时进行判断如果为1则升序
                            if(sortType===1){
                                return a.age-b.age;
                            //为2进行降序 
                            }else{
                                return b.age-a.age;
                            }
                        })
                    }
                    return arr;
                }
            },
        });
    </script>
</body>

10.5 列表更新的问题

v-for 遍历数组或对象,VUE无法监测数组或对象中的值的变化,正常情况修改数组元素的值,vue不能动态的更新。原因是:复杂数据类型保存的是对象的引用,当改变对象的键或值,对象的引用并没有发生变化。
vue对于数组的方法作出一层封装,可以改变原数组的方法,vue是可以监测到变化,有push pop  shift unshift reverse splice sort
对于非变更的方法,如filter concat slice 返回新数组 将新的值赋给原数组即可
// 控制台上直接修改app.list[]元素,不会动态刷新
            // 解决方案
            // 1.调用splice方法,Vue进行包裹可直接动态刷新
            // 2.调用$forceUpdate()方法强制进行刷新
            // 3.Vue.set(app.list,indexOf,newValue)
            // 4.app.$set(app.list,indexOf,newValue)
<body>
    <!-- 
        Vue数据绑定的原理
            1. vue会监视data中所有层次对象的属性
            2. 对象中的属性数据通过添加set方法来来实现监视
            3. 数组中也实现了监视: 重写数组一系列更新元素的方法,做了两件事:
                1).调用原生对应的方法对数组进行处理
                2).去更新界面
    -->
    <!-- 准备好一个容器-->
    <div id="root">
        <h2>人员列表</h2>
        <input v-model="keyWord" type="text" placeholder="请输入姓名">
        <button @click="sortType = 1">年龄升序↓</button>
        <button @click="sortType = 2">年龄降序↓</button>
        <button @click="sortType = 0">原顺序</button>
        <button @click="updateMei">更改马冬梅的信息</button>
        <ul>
            <li v-for="(p,index) in fmtPersons" :key="p.id">
                {{p.name}}--{{p.sex}}--{{p.age}}岁
            </li>
        </ul>
    </div>

    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data: {
                keyWord: '',
                sortType: 0, //0原顺序 1升序  2降序
                persons: [{
                        id: '001',
                        name: '马冬梅',
                        age: 35,
                        sex: '女',
                        a: {
                            b: {
                                c: {
                                    d: {
                                        e: 1
                                    }
                                }
                            }
                        }
                    },
                    {
                        id: '002',
                        name: '周冬雨',
                        age: 20,
                        sex: '女'
                    },
                    {
                        id: '003',
                        name: '周杰伦',
                        age: 41,
                        sex: '男'
                    },
                    {
                        id: '004',
                        name: '温兆伦',
                        age: 25,
                        sex: '男'
                    },
                ]
            },

            methods: {
                updateMei() {
                    console.log(this)
                    // 为每个属性添加了get set函数 
                    // this.persons[0].name = '小佩奇' //代码奏效
                    // this.persons[0].age = 99 //代码奏效
                    // this.persons[0].sex = '男' //代码奏效
                    this.persons[0] = {
                        name: '小佩奇',
                        age: 99,
                        sex: '男'
                    } //不奏效
                }
            },

            //使用computed过滤,优势:不影响原数据
            computed: {
                fmtPersons() {
                    const {
                        persons,
                        keyWord,
                        sortType
                    } = this
                    //根据关键词过滤
                    let arr = persons.filter(p => p.name.indexOf(keyWord) !== -1)
                    //若需要排序
                    if (sortType) {
                        //排序
                        arr.sort((a, b) => {
                            if (sortType === 1) return a.age - b.age
                            else return b.age - a.age
                        })
                    }
                    return arr
                }
            }

        })
    </script>
</body>

10.6 模拟数据监测

<script type="text/javascript">
        // 1.data中创建的数据 
        let data = {
            name: 'fyl',
            age: 18
        }
        // 2.创建一个监视的实例对象,用于监视data中属性的变化
        const obs = new Observer(data)
        console.log(obs);

        // 3.准备一个vm实例化对象,收集实例化下的data放入_data中,然后再挂在到vm上
        let vm = {}
        // data拥有get set 下次使用vm._data改变,实际改变data的数据,去解析
        vm._data = data = obs

        //4.类
        function Observer(obj) {
            //汇总对象中所有的属性形成一个数组
            const keys = Object.keys(obj)
            // 遍历加上getter和setter
            keys.forEach(k => {
                Object.defineProperty(this, k, {
                    get() {
                        return obj[k]
                    },
                    set(newVal) {
                        console.log(`obj的${k}被修改了,我要去解析模板,生成虚拟DOM`);
                        obj[k] = newVal
                    }
                })
            })
        }
</script>

10.7 Vue.set的使用

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
this.student.sex = '女' //直接使用不是响应式
//两种方式
Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')

10.8 总结Vue数据监测

Vue监视数据的原理:
                1. vue会监视data中所有层次的数据。

                2. 如何监测对象中的数据?
                                通过setter实现监视,且要在new Vue时就传入要监测的数据。
                                    (1).对象中后追加的属性,Vue默认不做响应式处理
                                    (2).如需给后添加的属性做响应式,请使用如下API:
                                                    Vue.set(target,propertyName/index,value) 或 
                                                    vm.$set(target,propertyName/index,value)

                3. 如何监测数组中的数据?
                                    通过包裹数组更新元素的方法实现,本质就是做了两件事:
                                        (1).调用原生对应的方法对数组进行更新。
                                        (2).重新解析模板,进而更新页面。

                4.在Vue修改数组中的某个元素一定要用如下方法:
                            1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
                            2.Vue.set() 或 vm.$set()   (对象,数组的索引,添加索引的内容)
                        
                
                特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

11、收集表单信息

收集表单数据:
                    若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
                    若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。 不加name属性也可
                    若:<input type="checkbox"/>
                            1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
                            2.配置input的value属性:
                                    (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
                                    (2)v-model的初始值是数组,那么收集的的就是value组成的数组
                    备注:v-model的三个修饰符:
                                    lazy:失去焦点再收集数据
                                    number:输入字符串转为有效的数字
                                    trim:输入首尾空格过滤
<body>
    <!-- 准备好一个容器-->
    <div id="root">
        <form @submit.prevent="submit">
            账号:<input type="text" v-model="userInfo.account">
            <br /><br />
            密码:<input type="password" v-model="userInfo.password">
            <br /><br />
            性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
            女<input type="radio" name="sex" v-model="userInfo.sex" value="female">
            <br /><br />
            爱好:抽烟 <input type="checkbox" v-model="userInfo.hobby" value="smoke">
            喝酒 <input type="checkbox" v-model="userInfo.hobby" value="drink">
            开车 <input type="checkbox" v-model="userInfo.hobby" value="drive">
            <br /><br />
            所属校区:<select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">武汉</option>
            </select>
            <br /><br />
            其他信息:<textarea v-model="userInfo.other" cols="30" rows="10"></textarea>
            <br /><br />
            <input v-model="userInfo.agree" type="checkbox">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
            <br /><br />
            <button>提交</button>
        </form>
    </div>

    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                userInfo: {
                    account: '',
                    password: '',
                    sex: '',
                    hobby: [],
                    city: '',
                    other: '',
                    agree: false,
                }
            },
            methods: {
                submit() {
                    console.log(this.userInfo)
                }
            }
        })
    </script>
</body>

12、过滤器

过滤器:
    定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
    语法:
        1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
        2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
    备注:
        1.过滤器也可以接收额外参数、多个过滤器也可以串联(如time | timeFormater('YYYY_MM_DD') | mySlice)
        2.并没有改变原本的数据, 是产生新的对应的数据
        3.局部有,优先使用局部过滤器
<div id="root">
            <h2>显示格式化后的时间</h2>
            <!-- 计算属性实现 -->
            <h3>现在是:{{fmtTime}}</h3>
            <!-- methods实现 -->
            <h3>现在是:{{getFmtTime()}}</h3>
            <!-- 过滤器实现 -->
            <h3>现在是:{{time | timeFormater}}</h3>
            <!-- 过滤器实现(传参) -->
            <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
            <h3 :x="msg | mySlice">尚硅谷</h3>
        </div>
        <div id="root2">
            <h2>{{msg | mySlice}}</h2>
        </div>
    </body>
    <script type="text/javascript">
        Vue.config.productionTip = false
        //全局过滤器
        Vue.filter('mySlice',function(value){
            return value.slice(0,4)
        })
        
        new Vue({
            el:'#root',
            data:{
                time:1621561377603, //时间戳
                msg:'你好,尚硅谷'
            },
            computed: {
                fmtTime(){
                    return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
                }
            },
            methods: {
                getFmtTime(){
                    return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
                }
            },
            //局部过滤器
            filters:{
                timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
                    // console.log('@',value)
                    return dayjs(value).format(str)
                }
            }
        })
        new Vue({
            el:'#root2',
            data:{
                msg:'hello,atguigu!'
            }
        })
    </script>

13、内置指令及自定义指令

13.1 内置指令

    常用内置指令
        v-text     :  更新元素的 innerText   类似于插值 
            1.作用:向其所在的节点中渲染文本内容。
            2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
         v-html :  更新元素的 innerHTML  内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译
             1.作用:向指定节点中渲染包含html结构的内容。
             2.与插值语法的区别:
                (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
                 (2).v-html可以识别html结构。
             3.严重注意:v-html有安全性问题!!!!
                (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
                (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
         v-cloak :这个指令保持在元素上直到关联实例结束编译(没有值)
             1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
            2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
        v-once指令:
             1.v-once所在节点在初次动态渲染后,就视为静态内容了。
             2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
         v-pre指令:
            1.跳过其所在节点的编译过程。
            2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
         v-if     :  条件渲染(动态控制节点是否存在)
         v-else :  条件渲染(动态控制节点是否存在)
         v-else-if:
         v-show :  条件渲染 (动态控制display)
         v-for  :  遍历数组/对象
         v-on   :  绑定事件监听, 可简写为@   修饰符
         v-bind:xxx : 强制绑定解析表达式, 可简写为 :xxx  修饰符
         v-model: 双向数据绑定  修饰符 .lazy .number .trim
         v-slot(#) : 插槽名
<style>
    [v-cloak]{
        display:none;
    }
</style>
        <!-- 引入Vue -->
    </head>
    <body>
        <!-- 
                v-cloak指令(没有值):
                        1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
                        2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
        -->
        <!-- 准备好一个容器-->
        <div id="root">
            <h2 v-cloak>{{name}}</h2>
        </div>
        <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
    </body>
    
    <script type="text/javascript">
        console.log(1)
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
        
        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷'
            }
        })
    </script>

13.2 自定义指令

    需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
    需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
    自定义指令总结:
    一、定义语法:
        (1).局部指令:                                                                                                    new Vue({directives:{指令名:配置对象} })  或  new Vue({    directives{指令名:回调函数}})                          (2).全局指令:
                Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)
        注意:配置对象使用函数的写法,默认是bind和update里面的内容
    二、配置对象中常用的3个回调:
        (1).bind:指令与元素成功绑定时调用。
        (2).inserted:指令所在元素被插入页面时调用。
        (3).update:指令所在模板结构被重新解析时调用。
        不用 (4)componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
        不用 (5)unbind:只调用一次,指令与元素解绑时调用。
    三、备注:
        1.指令定义时不加v-,但使用时要加v-;多个单词用v-big-number
        2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。指令名多个单词用双引号
<div id="root">
        <h2>{{name}}</h2>
        <h2>当前的n值是:<span v-text="n"></span> </h2>
        <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
        <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
        <button @click="n++">点我n+1</button>
        <hr />
        <input type="text" v-fbind:value="n">
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

    //定义全局指令
    /* Vue.directive('fbind',{
        //指令与元素成功绑定时(一上来)
        bind(element,binding){
            element.value = binding.value
        },
        //指令所在元素被插入页面时
        inserted(element,binding){
            element.focus()
        },
        //指令所在的模板被重新解析时
        update(element,binding){
            element.value = binding.value
        }
    }) */

    new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            n: 1
        },
        directives: {
            //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
            /* 'big-number'(element,binding){
                // console.log('big')
                element.innerText = binding.value * 10
            }, */
            big(element, binding) {
                console.log('big', this) //注意此处的this是window
                // console.log('big')
                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
                }
            },
            // fbind:{
            //     // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
            //     bind(el,binding,vnode,oldVnode){
            //         // 指令所绑定的元素,可以用来直接操作 DOM
            //         console.log(el);
            //         // 一个对象
            //         console.log(binding);
            //         // Vue 编译生成的虚拟节点。
            //         console.log(vnode);
            //         // 上一个虚拟节点
            //         console.log(oldVnode);
            //         el.value = binding.value
            //     },
            //     // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
            //     inserted(el){
            //         el.focus()
            //     },
            //     // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
            //     update(el,binding){
            //         el.value = binding.value
            //     },
            // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
            // componentUpdated() {

            // },
            // 只调用一次,指令与元素解绑时调用。
            // unbind() {

            // }
            // }
        }
    })

14、生命周期

常用的生命周期钩子:
                        1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
                        2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

                关于销毁Vue实例
                        1.销毁后借助Vue开发者工具看不到任何信息。
                        2.销毁后自定义事件会失效,但原生DOM事件依然有效。
                        3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

vue学习笔记

15、非单文件组件

以往是多个html文件有css和js文件,头部可以复制使用,不是很方便,可不可以通过导入的形式,直接使用。那么使用组件

组件:实现应用中局部功能代码和资源的集合 .vue 很多组件

15.1 组件的基本使用

Vue中使用组件的三大步骤:
    一、定义组件(创建组件)
    二、注册组件
    三、使用组件(写组件标签)

    一、如何定义一个组件?
        使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
        区别如下:
            1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
            2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
        备注:使用template可以配置组件结构。

    二、如何注册组件?
        1.局部注册:靠new Vue的时候传入components选项 Vue.extend({}) 或者直接写选项对象{},在components中会调用extend
        2.全局注册:靠Vue.component('组件名',组件)

    三、编写组件标签:
    <school></school>
<!-- 准备好一个容器-->
        <div id="root">
            <hello></hello>
            <hr>
            <h1>{{msg}}</h1>
            <hr>
            <!-- 第三步:编写组件标签 -->
            <school></school>
            <hr>
            <!-- 第三步:编写组件标签 -->
            <student></student>
        </div>

        <div id="root2">
            <hello></hello>
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false

        //第一步:创建school组件
        const school = Vue.extend({
            template:`
                <div class="demo">
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName">点我提示学校名</button>    
                </div>
            `,
            // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
            data(){
                return {
                    schoolName:'尚硅谷',
                    address:'北京昌平'
                }
            },
            methods: {
                showName(){
                    alert(this.schoolName)
                }
            },
        })

        //第一步:创建student组件
        const student = Vue.extend({
            template:`
                <div>
                    <h2>学生姓名:{{studentName}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
            data(){
                return {
                    studentName:'张三',
                    age:18
                }
            }
        })
        
        //第一步:创建hello组件
        const hello = Vue.extend({
            template:`
                <div>    
                    <h2>你好啊!{{name}}</h2>
                </div>
            `,
            data(){
                return {
                    name:'Tom'
                }
            }
        })
        
        //第二步:全局注册组件
        Vue.component('hello',hello)

        //创建vm
        new Vue({
            el:'#root',
            data:{
                msg:'你好啊!'
            },
            //第二步:注册组件(局部注册)
            components:{
                school,
                student
            }
        })

        new Vue({
            el:'#root2',
        })
    </script>

15.2 注意点

    几个注意点:
        1.关于组件名:
            一个单词组成:
                第一种写法(首字母小写):school
                第二种写法(首字母大写):School
        多个单词组成:
            第一种写法(kebab-case命名):my-school
            第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
        备注:
            (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
            (2).可以使用name配置项指定组件在开发者工具中呈现的名字。

        2.关于组件标签:
            第一种写法:<school></school>
            第二种写法:<school/>
            备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

        3.一个简写方式:
            const school = Vue.extend(options) 可简写为:const school = options

15.3 组件的嵌套

<body>
        <!-- 准备好一个容器-->
        <div id="root">
            
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

        //定义student组件
        const student = Vue.extend({
            name:'student',
            template:`
                <div>
                    <h2>学生姓名:{{name}}</h2>    
                    <h2>学生年龄:{{age}}</h2>    
                </div>
            `,
            data(){
                return {
                    name:'尚硅谷',
                    age:18
                }
            }
        })
        
        //定义school组件
        const school = Vue.extend({
            name:'school',
            template:`
                <div>
                    <h2>学校名称:{{name}}</h2>    
                    <h2>学校地址:{{address}}</h2>    
                    <student></student>
                </div>
            `,
            data(){
                return {
                    name:'尚硅谷',
                    address:'北京'
                }
            },
            //注册组件(局部)
            components:{
                student
            }
        })

        //定义hello组件
        const hello = Vue.extend({
            template:`<h1>{{msg}}</h1>`,
            data(){
                return {
                    msg:'欢迎来到尚硅谷学习!'
                }
            }
        })
        
        //定义app组件
        const app = Vue.extend({
            template:`
                <div>    
                    <hello></hello>
                    <school></school>
                </div>
            `,
            components:{
                school,
                hello
            }
        })

        //创建vm
        new Vue({
            template:'<app></app>',
            el:'#root',
            //注册组件(局部)
            components:{app}
        })
    </script>

15.3 VueComponent构造函数

关于VueComponent:
    1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend调用生成的。

    2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
    即Vue帮我们执行的:new VueComponent(options)。

    3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!

    4.关于this指向:
        (1).组件配置中:
            data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
        (2).new Vue(options)配置中:
            data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

    5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
        Vue的实例对象,以后简称vm。

15.4 一个重要的内置关系 vc和vm的原型

    1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
    2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

16、单文件组件

    非单文件组件
    通过Vue.extend去注册组件,在new Vue中的components配置,直接在html中使用<school>
    不易复用,现在将功能模块拆分
    单文件组件
        vue文件 1.webpack解析  2.脚手架
        |-main.js  注册app组件 挂载app 使用template 注册app 配置template(<app/>) 不用在html中写<App/>
        |-App.vue   app中引入components中组件 注册 school和student
        |-index.html 引入main.js 和 Vue
        |-components |-student 
                    有template script export default const student = Vue.extend({})  可简写为 export default{}
                    |-school
    

17、分析脚手架

    脚手架
    不使用render函数 在main.js中写

原先写法

html:
    <body>
        <!-- 准备一个容器 -->
        <div id="root"></div>
        <script type="text/javascript" src="../js/vue.js"></script>
        <script type="text/javascript" src="./main.js"></script>
    </body>
main.js:
    import App from './App.vue'
    new Vue({
        el:'#root',
        template:`<App></App>`,
        components:{App},
    })
App.vue:
<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>
<script>
    //引入组件
    import School from './School.vue'
    import Student from './Student.vue'

    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>

脚手架写法 缺少模板解析器

只在main.js中写
//只引入了实时运行版的vue
//import Vue from 'vue'
//完整版的包含模板解析器 就可以运行
import Vue from 'vue/dist/vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  // render: h => h(App),
  components:{App},
  template:`<App/>`
}).$mount('#app')

为什么:Vue = Vue核心(2/3) + 模板解析器(1/3) 所以用render进行渲染 打包后生成最终代码不需要模板解析器的部分

Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。

//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false

/* 
    关于不同版本的Vue:
    
        1.vue.js与vue.runtime.xxx.js的区别:
                (1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
                (2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

        2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用
            render函数接收到的createElement函数去指定具体内容。
*/

//创建Vue实例对象---vm
new Vue({
    el:'#app',
    //render函数完成了这个功能:将App组件放入容器中   返回的是虚拟dom
  render: h => h(App),
    // render:q=> q('h1','你好啊')

    // template:`<h1>你好啊</h1>`,
    // template:`<App/>`,
    // components:{App},
})

18、常用方法

18.1 ref属性

ref在普通元素上就是真实的Dom节点
组件上就是组件的示例对象(vc)

18.2 props属性

prop接受的值实在data赋值之前

        //简单声明接收
        // props:['name','age','sex'] 

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

        //接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
        props:{
            name:{
                type:String, //name的类型是字符串
                required:true, //name是必要的
            },
            age:{
                type:Number,
                default:99 //默认值
            },
            sex:{
                type:String,
                required:true
            }
        }

18.3 混入Mixin

混入的方式 :方法自身有用自身的 生命周期是都会执行,混入的钩子会提前调用
还可以自定义混入
  1. 功能:可以把多个组件共用的配置提取成一个混入对象
  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

18.4 插件

  1. 功能:用于增强Vue
  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令 混入 过滤器

  1. 定义插件:
对象.install = function (Vue, options) {
    // 1. 添加全局过滤器
    Vue.filter(....)

    // 2. 添加全局指令
    Vue.directive(....)

    // 3. 配置全局混入(合)
    Vue.mixin(....)

    // 4. 添加实例方法
    Vue.prototype.$myMethod = function () {...}
    Vue.prototype.$myProperty = xxxx
}
  1. 使用插件:Vue.use()
import Vue from 'vue'
import loadingComponent from '@/components/Loading/index.vue'
//使用extend创建 组件的vc构造函数
const LoadingConstructor = Vue.extend(loadingComponent)
//实例化vc 挂载在div中
const instance = new LoadingConstructor({
  el: document.createElement('div')
})
//在实例中挂载属性
instance.show = false // 默认隐藏
// 组件中使用props接收
const loading = {
  show(txt = '') { // 显示方法
    instance.show = true
    instance.text = txt || '拼命加载中'
    document.body.appendChild(instance.$el)
  },
  hide() { // 隐藏方法
    instance.show = false
  }
}

export default {
 //install方法 有一个Vue形参 
  install() {
    if (!Vue.$loading) {
      Vue.$loading = loading
    }
  //通过混入 在created中 将实例化$loading 挂载在this上
    Vue.mixin({
      created() {
        this.$loading = Vue.$loading
      }
    })
  }
}

18.5 nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
//解析时机的问题  当this.isEdit数据变化,并没有立即更新dom 而是执行完focus  所以必须在dom更新后进行执行
if(todo.hasOwnProperty('isEdit')){
                    todo.isEdit = true
                }else{
                    // console.log('@')
                    this.$set(todo,'isEdit',true)
                }
                this.$nextTick(function(){
                    this.$refs.inputTitle.focus()
                })

19、事件总线

main.js
    new Vue({
      render: h => h(App),
      beforeCreate(){
        Vue.prototype.$bus = this
      }
    }).$mount('#app')
绑定 触发 销毁
this.$bus.$on('name',callback=>{})
this.$bus.$emit('name',params)
 this.$bus.$off()全部关闭(name)

发布订阅pubsub.js

//首先是先订阅消息hello, 定义触发的回调 第一个参数的回调的函数名,第二个是接受的数据
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
                console.log(this)
                // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
//发送数据方,需要发布消息,触发回调
pubsub.publish('hello',666)
//取消订阅 是取消订阅号
pubsub.unsubscribe(this.pubId)

20、动画和过度

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
  2. 图示:<img src="https://img04.sogoucdn.com/app/a/100520146/5990c1dff7dc7a8fb3b34b4462bd0105" style="width:60%" />
  3. 写法:

    1. 准备好样式:

      • 元素进入的样式:

        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:

        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
          <h1 v-show="isShow">你好啊!</h1>
      </transition>
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。
<transition-group name="hello" appear>
            <h1 v-show="!isShow" key="1">你好啊!</h1>
            <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

使用animate动画库

<transition-group 
            appear
            name="animate__animated animate__bounce" 
            enter-active-class="animate__swing"
            leave-active-class="animate__backOutUp"
        >
            <h1 v-show="!isShow" key="1">你好啊!</h1>
            <h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>

21、vue脚手架配置代理

方法一

​ 在vue.config.js中添加如下配置:

//如果本地存在则不会转发
devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
    devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}//将请求地址中的api1替换为空
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

代码请求写法:

axios.get('http://localhost:8080/demo/cars')  
//vue.config.js
'/demo': {
        target: 'http://localhost:5001',
        pathRewrite:{'^/demo':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值  true:host值不是实际的
}
//请求本地服务器,会转换撑localhost:5001/cars

22、插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件
  2. 分类:默认插槽、具名插槽、作用域插槽
  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
    3. 作用域插槽:

      1. 理解:<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。</span>(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
      2. 具体编码:

        在slot上绑定属性,可以在父组件中使用子组件的数据 ,接受方式:

        scope=“”

        slot-scope=""

        可以结构赋值

        v-slot:default=""

        default=""

        父组件中:
                <Category>
                    <template scope="scopeData">
                        <!-- 生成的是ul列表 -->
                        <ul>
                            <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                        </ul>
                    </template>
                </Category>
        
                <Category>
                    <template slot-scope="scopeData">
                        <!-- 生成的是h4标题 -->
                        <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
                    </template>
                </Category>
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>
                
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>

23、Vuex

1.概念

​ 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.何时使用?

​ 多个组件需要共享数据时

3.搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
        el:'#app',
        render: h => h(App),
        store
    })

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
        jia(context,value){
            // console.log('actions中的jia被调用了',miniStore,value)
            context.commit('JIA',value)
        },
    }
    
    const mutations = {
        //执行加
        JIA(state,value){
            // console.log('mutations中的JIA被调用了',state,value)
            state.sum += value
        }
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state,
    })
  2. 组件中读取vuex中的数据:$store.state.sum
  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
  2. store.js中追加getters配置

    ......
    
    const getters = {
        bigSum(state){
            return state.sum * 10
        }
    }
    
    //创建并暴露store
    export default new Vuex.Store({
        ......
        getters
    })
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数,传参写在事件后@click=xxx(data)

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。
  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    //对象写法
    ...mapState('countAbout',{sum:'sum'),
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    //对象写法
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    ...mapActions('user',['addCash'])
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    ...mapMutations('user',['addCash'])

    ## 24、路由

  7. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  8. 前端路由:key是路径,value是组件。

1.基本使用

  1. 安装vue-router,命令:npm i vue-router
  2. 应用插件:Vue.use(VueRouter)
  3. 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    })
    
    //暴露router
    export default router
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
  5. 指定展示位置

    <router-view></router-view>

2.几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
        {
            path:'/about',
            component:About,
        },
        {
            path:'/home',
            component:Home,
            children:[ //通过children配置子级路由
                {
                    path:'news', //此处一定不要写:/news
                    component:News
                },
                {
                    path:'message',//此处一定不要写:/message
                    component:Message
                }
            ]
        }
    ]
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>

4.路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
                    
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
        :to="{
            path:'/home/message/detail',
            query:{
               id:666,
                title:'你好'
            }
        }"
    >跳转</router-link>
  2. 接收参数:

    $route.query.id
    $route.query.title

5.命名路由

  1. 作用:可以简化路由的跳转。
  2. 如何使用

    1. 给路由命名:

      {
          path:'/demo',
          component:Demo,
          children:[
              {
                  path:'test',
                  component:Test,
                  children:[
                      {
                            name:'hello' //给路由命名
                          path:'welcome',
                          component:Hello,
                      }
                  ]
              }
          ]
      }
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
          :to="{
              name:'hello',
              query:{
                 id:666,
                  title:'你好'
              }
          }"
      >跳转</router-link>

6.路由的params参数

  1. 配置路由,声明接收params参数

    {
        path:'/home',
        component:Home,
        children:[
            {
                path:'news',
                component:News
            },
            {
                component:Message,
                children:[
                    {
                        name:'xiangqing',
                        path:'detail/:id/:title', //使用占位符声明接收params参数
                        component:Detail
                    }
                ]
            }
        ]
    }
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
                    
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
        :to="{
            name:'xiangqing',
            params:{
               id:666,
                title:'你好'
            }
        }"
    >跳转</router-link>

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    $route.params.id
    $route.params.title

7.路由的props配置*

​ 作用:让路由组件更方便的收到参数

{
    name:'xiangqing',
    path:'detail/:id',
    component:Detail,

    //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
    // props:{a:900} 不用props接受会放在$attrs中

    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    // props:true
    
    //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props(route){
        return {
            id:route.query.id,
            title:route.query.title
        }
    }
}

8.<router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

9.编程式路由导航

  1. 作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活
  2. 具体编码:

    //$router的两个API
    this.$router.push({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    
    this.$router.replace({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退

10.缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。
  2. 具体编码:

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>

11.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:

    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

12.路由守卫

  1. 作用:对路由进行权限控制
  2. 分类:全局守卫、独享守卫、组件内守卫
  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
        console.log('beforeEach',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
                next() //放行
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next() //放行
        }
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
        console.log('afterEach',to,from)
        if(to.meta.title){ 
            document.title = to.meta.title //修改网页的title
        }else{
            document.title = 'vue_test'
        }
    })
  4. 独享守卫:

    beforeEnter(to,from,next){
        console.log('beforeEnter',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'atguigu'){
                next()
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next()
        }
    }
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }

13.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:

    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:

    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
index.html :html的入口文件
    引入css文件
    引入index.js   此时的src指向实例化Vue的js文件
    <script type='module' src='js/index.js'></script>
css:
    style.css
js:
index.js
定义实例化的Vue  使用render模板去替换el挂载的DOM节点 
导入root文件,指向components中的root
render:(h){return h(root)}
    
components:
    root.js:定义render的返回值
    先导入页面需要的组件 和数据(如果需要则要导入事件总线)
    {
        template:`
        <div id="app">//如果css中定义了app的样式,id=app需要加上
            <todo-list></todo-list>
            <done-list></done-list>
        </div>
        `,
        components:{
            "todo-list":todolist,
            "done-list":donelist
        }
        
    }
    todolist.js
    donelist.js
    先导入事件总线中需要的数据
    定义组件的功能:获取数据使用computed计算属性  定义的方法 需要去触发总线中的方法 eventBus.$emit("事件总线中的方法",传入的参数)
store
    store.js
    先定义需要的数据,在eventBus上注册子组件需要的方法 this.$on("事件总线中的方法",this.eventBus中的方法)

Jenkins安装

nginx配置

yum install gcc-c++
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel
wget http://nginx.org/download/ngi...
tar zxvf nginx-1.16.1.tar.gz
先进入解压文件夹 ./configure && make && make install
whereis nginx 查看nginx安装路径
在usr/local/nginx/html下
启动
[root@localhost ~]# /usr/local/nginx/sbin/nginx
停止/重启
[root@localhost ~]# /usr/local/nginx/sbin/nginx -s stop(quit、reload)
命令帮助
[root@localhost ~]# /usr/local/nginx/sbin/nginx -h
验证配置文件
[root@localhost ~]# /usr/local/nginx/sbin/nginx -t
配置文件
[root@localhost ~]# vim /usr/local/nginx/conf/nginx.conf

安装yarn

使用 yum 安装 Yarn
Yarn 官方提供的有 Yarn RPM 软件包,在添加 yum 源配置之后可使用 yum 安装:

添加 yum 源配置

curl -sL https://dl.yarnpkg.com/rpm/ya... | sudo tee /etc/yum.repos.d/yarn.repo

使用 yum 安装

sudo yum -y install yarn

查看安装的 Yarn 版本:

yarn -v

Jenkins安装

jekins+gitee+nginx
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat...
sudo rpm --import https://pkg.jenkins.io/redhat...
sudo yum upgrade
sudo yum install epel-release java-11-openjdk-devel
sudo yum install jenkins
sudo systemctl daemon-reload
sudo systemctl start jenkins 启动
查看状态sudo systemctl status jenkins
修改编辑权限 vim /etc/sysconfig/jenkins
service jenkins restart重启

这时访问jenkins有不可以,要开放阿里云8080端口
1.登录成功
为了确保管理员安全地安装 Jenkins,密码已写入到日志中(不知道在哪里?)该文件在服务器上:
/var/lib/jenkins/secrets/initialAdminPassword
请从本地复制密码并粘贴到下面。
d65c81a6952341dfac53d9efaf61e0cc
2.按照默认配置安装插件
3.创建一个管理员账户,完成配置后,就可以登录 Jenkins 了
4.安装插件
  下面来安装nodejs插件

gitee令牌2e724699896926f21b301598d10d91d7

gitee实验jenkins教程

安装rz :yum install -y lrzsz

gcc 版本过低

4.安装gcc

yum install gcc -y #默认安装旧版本4.85

yum -y install centos-release-scl

yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils #安装新版本

切换为新版本

scl enable devtoolset-9 bash #临时切换,退出服务器恢复

echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile #永久切换

jenkins

需要配置webhook gitee需要配置 参考jenkins+gitee实现前端项目自动化部署 - 简书 (jianshu.com)

安装nodejs

  • 第一步,到官网查看最新源码,并下载
cd /home/downloads
wget https://nodejs.org/dist/v10.16.0/node-v10.16.0.tar.gz

下载编译好的node Linux

ln -s /usr/local/src/node-v16.5.0-linux-x64/bin/node /usr/local/bin/node

ln -s /usr/local/src/node-v16.5.0-linux-x64/bin/npm /usr/local/bin/npm

课堂知识内容回顾

1、使用template会将el挂载的节点内容替换. 注意template模板的使用

 <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
    <div id="app"></div>
</body>
<script>
    // 模型层
    var obj = {
        message:"hello,world  1"
    }
    // 视图层 替换id=app区域
    var template = `
        <div class='Find'>{{message}}</div>
    `;

    // 视图模型层
    var app = new Vue({
        el:"#app",
        data:obj,
        template:template
    });

2、冻结属性Object.freeze(data),冻结后不能实时响应数据

3、生命周期

  <div id="app">
        <p title="nihao" ref="hello">{{msg}}</p>
    </div>
    <script>
        var app =new Vue({
            // 内部没有el,可以手动进行$mount("#app")挂载
            // el:"#app",
            data:{
                msg:"你好"
            },
            beforeCreate(){
                // 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用
                console.log("生命周期:beforeCreate");
                console.log('加载data中的msg数据---'+this.msg);
                console.log('获取节点$el---'+this.$el);
                this.initMethod()
            },
            created(){
                // 数据 injections 和reactivity 注入 响应
                // 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数
                console.log("生命周期:created");
                console.log('加载data中的msg数据---'+this.msg);
                console.log('获取节点$el---'+this.$el);
                this.initMethod()
            },
            beforeMount(){
                // 生成虚拟dom  在挂载开始之前被调用:相关的 render 函数首次被调用。
                console.log("生命周期:beforeMount");
                console.log(this.$el);
                console.log(this.$refs);
            },
            mounted(){
                // 真实的dom   实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了
                console.log("生命周期:mounted");
                console.log(this.$el);
                console.log(this.$refs);
            },
            beforeUpdate(){
                // data 数据改变 beforeUpdate函数中的数据没有改变  app.msg = 123     this.msg=123
                console.log("生命周期:beforeUpdate");
                console.log(this._vnode.children[0].children[0].text);
            },
            updated(){
                // 通过patch算法 对比实际dom和渲染虚拟dom 更新数据 
                console.log("生命周期:beforeUpdate");
                console.log(this._vnode.children[0].children[0].text);
            },
            beforeDestroy(){
                // 使用app.$destroy  卸载对象与vue的绑定
                console.log("生命周期:beforeDestroy");

            },
            destroyed(){
                console.log("生命周期:destroyed");

            },
            methods: {
                initMethod(){
                    console.log('初始化方法调用成功');
                }
            },

        }).$mount("#app");

4、组件

组件中的data为什么是一个函数的返回值?

<div id="app">
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>

    <script>
        Vue.component("button-counter",{
            //如果不使用函数返回,复用的组件的data数据是相同的引用,通过函数的返回对象,每次创建的都是一个新的数据引用
            // data:{
            //     count:0
            // },
            data(){
                return {
                    count:0
                }
            },
            created(){
                console.log("我是组件");
                console.log(this);
            },
            template:`
            <button v-on:click="count++">你点击了{{count}}次</button>
            `
        })

        var app = new Vue({
            el:"#app",
            created(){
                console.log("我是根");
                console.log(this);
            },
        })

组件的注册方式

//组件的名称:不使用单文件组件时(.vue):推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)比如my-component-name
如果组件名称是大驼峰或者小驼峰
Vue.component('MyComponent'/'myComponent', {
  // ... 选项 ...
})
可以在父组件中这样调用是等价的
<my-component></my-component>  <MyComponent></MyComponent>/<myComponent></myComponent> 

注册方式:
//全局注册  直接在任意地方使用
Vue.component('my-component', {
  // ... 选项 ...
})

//局部注册  
<div id="app">
       <my-component></my-component>
    </div>
    <script>
        var options = {
            data(){
                return{
                    msg:"我是子组件中的data值"
                }
            },
            template:"<div>{{msg}}</div>"
        }

        new Vue({
            el:"#app",
            components:{
                "my-component":options,//后面接入选项对象
            }
        });
    </script>

组件间的通信方式有3类:

第一类:父子组件通信

第二类:隔代组件通信

第三类:兄弟组件通信

(1) props/$emit 适用父子组件通信

父-->子传值 props

 <div id="app">
        <book v-for="item in bookList" :name="item.name" :author="item.author"></book>
    </div>
    <script>
        Vue.component("book", {
            props: ["name", "author"],
            template: `
            <div>
                <p>书名:{{name}}</p>
                <p>作者:{{author}}</p>
            </div>           
           `
        })
        var app = new Vue({
            el:"#app",
            data:{
                bookList:[
                {
                    name:"西游记",
                    author:"吴承恩"
                },
                {
                    name:"水浒传",
                    author:"施耐庵"
                },
                {
                    name:"红楼梦",
                    author:"曹雪芹"
                },
                {
                    name:"三国演义",
                    author:"罗贯中"
                }
            ]
            }
        })

    </script>
 <div id="app">
        <!-- v-bind  当做表达式解析 -->

        
        <my-book :name="book.name" :price="book.price"></my-book>
        <my-book v-bind="book"></my-book>
    </div>
    
    <script>
        Vue.component("my-book",{
            props:["name","price"],
            template:`
            <div>
                <p>{{name}}</p>
                <p>{{price}}</p>
            </div>
            
            `
        })

        new Vue({
            el:"#app",
            data:{
                book:{
                    name:"西游记",
                    price:15
                }
            }
        });
    

子父传值 通过$emit(组件上的自定义方法,传入父组件的值)

<div id="app">
        <p>{{count}}</p>
        <my-counter v-on:add="add1" :msg="msg"></my-counter>
    </div>
    <script>
        Vue.component("my-counter", {
            props:['msg'],
            data(){
                return{
                    son:"子组件"
                }
            },
            template: `
            <div>            
                <p>{{son}}</p>
                <p>{{msg}}</p>
                 <button v-on:click="btnClick">增加</button>
            </div>
           `,
           beforeCreate() {
                console.log("子组件的beforeCreated");
            },
            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");
            },

           methods: {
               btnClick(){
                console.log("子组件的btn事件");
                this.$emit("add",{
                    addCount:10,
                    name:"二蛋"
                });
               }
           },
        })

        var app = new Vue({
            el: "#app",
            data: {
                count: 0,
                msg:"父子传值"
            },
            beforeCreate() {
                console.log("父组件的beforeCreated");
            },
            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");
            },

            methods: {
                add1(e){
                    console.log(e.name);
                    console.log("根组件count++");
                    this.count+=e.addCount;
                }
            },

        })

(2)ref与$parent/$children 使用父子组件通信

(3)$attrs/$listeners 使用隔代组件通信

(4)provide / inject 使用于隔代组件通信

  <div id="app">
        <p>我是根元素</p>
        <my-one></my-one>
    </div>    
    <script>
        Vue.component("my-one",{
            inject:["msg","getStr"],
            // 访问
            template:`
            <div style="border:1px solid red">
                <p>{{msg}}</p>
                <p>{{getStr()}}</p>
            </div>
            `,
        });
        var app = new Vue({
            el:"#app",
            data:{
                msg:'依赖提供的数据',
            },
            methods: {
                getStr(){
                    console.log("依赖提供的方法");
                    return "依赖方法返回的值"
                }
            },

            provide(){
                return {
                    msg:this.msg,
                    getStr:this.getStr,
                }    
            }  
        })
    </script>

(5)EventBus($emit/$on)适用于父子、隔代、兄弟组件通信

    <div id="app">
        <p>{{msg}}</p>
        <my-one></my-one>
    </div>
    <script>
        // 公共仓库管理msg的状态
        var eventBus = new Vue({
            created() {
                // 为事件总线添加change-msg的事件
                this.$on("change-msg",this.print)
            },
            data:{
                msg:'父组件的msg',
            },    
            methods: {
                print(str){
                    this.msg=str;
                    console.log(this.msg);
                },
            },     
        })
        // 2.子组件的数据不需要通过props进行传递,通过eventBus进行触发总线的change-msg事件,传递的参数在print中接受,通过print修改msg中的值
        Vue.component("my-one",{
            template:`
            <div style="border:1px solid red">
                <p>{{msg}}</p>    
                <button @click="btnClick">修改</button>
            </div>
            `,
            computed:{
                msg(){
                    return eventBus.msg;
                }
            },
            
            methods: {
                btnClick(){
                    // 3.$emit 触发change-msg,调用print方法
                    eventBus.$emit("change-msg","我是子组件修改的")
                }
            },
        })
        // 1.获取msg 直接通过eventBus进行获取
        var app =new Vue({
            el:"#app",
            computed:{
                msg(){
                    return eventBus.msg;
                }
            },
            
        });
    </script>

(6)Vuex 适用于父子、隔代、兄弟组件通信

组件的插槽slot

<div id="app">
        <my-slot>
            <div>你好</div>
            <div>你好</div>
        </my-slot>
    </div>
    <script>
        Vue.component("my-slot",{
            template:`
            <div>
                <p>组件插槽</p>
                <slot></slot>
                <p>组件插槽</p> 
            </div>         
            `
        });
        var app =new Vue({
            el:"#app",
            
        })
    </script>

编译作用域

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

 <div id="app">
        <my-one>
            <!-- 规则谁的模板 数据就是谁的 -->
            <!-- 编译时在app中的msg使用根组件的作用域的数据 -->

            <!-- 应用 根组件的数据通过slot直接渲染在子组件中 不需要通过props属性进行传递 -->
            <div>{{msg}}</div>
        </my-one>
    </div>

    <script>
        Vue.component("my-one",{
            data(){
                return {
                    msg:"我是子组件的"
                }
            },
            template:`
            <div>
                <p>我是子组件</p>
                <slot></slot>
                <p>我是子组件</p>
            </div>
            `,
        });
        
        var app =new Vue({
            el:"#app",
            data:{
                msg:"我是根组件的"
            }
        });
    </script>

后背内容 默认的内容

 <div id="app">
        <my-slot>
            <!-- 如果此处有内容,子组件中的slot默认内容将不会显示 -->
        </my-slot>
    </div>
    <script>
        Vue.component("my-slot",{
            template:`
            <div>
                <p>组件插槽</p>
                <slot>默认内容</slot>
                <p>组件插槽</p> 
            </div>         
            `
        });
        var app =new Vue({
            el:"#app",
            
        })
    </script>

具名插槽

    <div id="app">
        <my-one>
            <template v-slot:head>
                <div>头部</div>
            </template>
            <template #middle>
                <div>中间</div>
            </template>
<!--            <div>尾部</div>  等价于下面的写法-->
             <template v-slot:default>
                <div>尾部</div>
            </template>

        </my-one>
    </div>

    <script>
        Vue.component("my-one", {

            template: `
            <div>
                <slot name="head"></slot>
                <p>我是子组件</p>
                <slot name="middle"></slot>                    
                <p>我是子组件</p>
                <slot></slot>
            </div>
            `,
        });

        var app = new Vue({
            el: "#app",
        });
    </script>

作用域插槽

    <div id="app">
        <!-- 让插槽内容能够访问子组件中才有的数据是很有用 -->
        <current-user>
            <!-- 将包含所有插槽 prop 的对象命名为 slotProps -->
            <template v-slot:default="slotProps">
                {{slotProps.user.firstName}}
            </template>
        </current-user>
    </div>

    <script>
        Vue.component("current-user", {
            data(){
                return{
                    user:{firstName:"姓",lastName:"名"}
                }
            },
// 绑定在 <slot> 元素上的 attribute 被称为插槽 prop
    // 现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
            template: `
            <div>
                <slot v-bind:user="user">{{user.lastName}}</slot>
            </div>
            `,
        });

        var app = new Vue({
            el: "#app",
        });
    </script>
//补充:当被提供的内容只有默认插槽时  组件的标签才可以被当作插槽的模板来使用
<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>
//可以简写为
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>
//结构赋值
 <!-- 结构赋值 别名只能在这里使用 -->
            <template v-slot:foot="{user:person}">
                {{person}}
            </template>
//这里还是只能写user
             <slot name="foot" :user="user">
                    <div>{{user}}</div>
                </slot>

要改变插槽的值

//第一种传入的是对象  子组件中使用user.lastName
 <!-- 将包含所有插槽 prop 的对象命名为 slotProps 相当与data的数据 -->
                <template v-slot:default="slotProps">
                    {{slotProps.user}}
                </template>
            <slot v-bind:user="user.firstName">
                    <div>{{user}}</div>
                </slot>
                <slot v-bind:user="user.lastName">
                    <div>{{user}}</div>
                </slot>
//第二种方式  在组件中直接传入
             <template v-slot:default="slotProps">
                {{slotProps.user.lastName}}
            </template>
             <slot v-bind:user="user">
                    <div>{{user}}</div>
                </slot>

动态组件 keep-alive

使用keep-alive时,组件将缓存

    <div id="app">
        <!-- 切换后保持原页面的数据 -->
        <button @click="msg='my-one'">组件一</button>
        <button @click="msg='my-two'">组件二</button>
        <!-- 所有被keep-alive包裹都有新的两个生命周期 -->
        <!-- <keep-alive> -->
            <component :is="msg"></component>
        <!-- </keep-alive> -->
    </div>

    <script>
        Vue.component("my-one", {
            created() {
                console.log("组件一创建了")
            },
            destroyed() {
                console.log("组件一销毁了")
            },
            activated() {
                console.log("activated生命周期被调用");
            },
            deactivated() {
                console.log("deactivated生命周期被调用");
            },
            template: `
            <div>
               <p>我是组件一</p>
               <input type='text'>
            </div>
            `,
        });
        Vue.component("my-two", {
            created() {
                console.log("组件二创建了");
                
            },
            destroyed() {
                console.log("组件二销毁了");
            },
            data(){
                return {
                    msg:'哈哈'
                }
            },
            template: `
            <div>
               <p>我是组件二</p>
               <p>{{msg}}</p>
               <button @click="msg='你好'">改变</button>
            </div>
            `,
        });

        var app = new Vue({
            el: "#app",
            data:{
                msg:"my-one"
            }
        });
    </script>
    
    内联模板inline-template
<div id="app">
    <p>{{msg}}</p>
    <!-- 不推荐使用inline-template 使用后将当中的内容作为子组件的模板,作用域是子组件 -->
    <my-one inline-template>
        <div style="border:1px solid red">
            <p>{{msg}}</p>
        </div>
    </my-one>
</div>    
<script>

    Vue.component("my-one",{
        data(){
            return {
                msg:"子组件的msg"
            }
        }
       
    });
    var app = new Vue({
        el:"#app",
        data:{
            msg:'父组件的msg',
        },         
    })
</script>


x-template 

<div id="app">

    <p>{{msg}}</p>     
    <my-one> </my-one>
</div>   

<!-- 使用script去写子组件的模板,通过id 赋值给子组件的template -->
<script type="text/x-template" id="one">
    <div style="border:1px solid red">
        <p>{{msg}}</p>
    </div>
</script>
<script>
    
    Vue.component("my-one",{
        template:"#one",
        data(){
            return {
                msg:"子组件的msg"
            }
        }
       
    });
    var app = new Vue({
        el:"#app",
        data:{
            msg:'父组件的msg',
        },         
    })
</script>

混入mixin

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
<div id="app">
    <p>{{msg}}</p>
    <button @click="print">打印</button>
    <p>----------------</p>
    <my-one></my-one>
</div>
<script>
    /* 
        混入就是抽取组件相同的部分
        通过mixins导入组件 语法mixins:[mixin]
    */
    var mixin = {
        created() {
            console.log("混入对象的生命周期");
        },
        // 当数据对象与组件的数据对象冲突时(除了生命周期会合并),以组件为准 方法是挂载在组件上
        //例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
        data(){
            return {
                msg:"混入对象的msg你好"
            }
        },
        methods: {
            print(){
                console.log(this.msg);
            }
        },
    }

    Vue.component("my-one",{
        mixins:[mixin],
        // 同名的钩子函数将合并为一个数组,因此都将被调用 
        // 混入的钩子函数会先于组件的钩子函数执行
        created() {
            console.log("组件自己的生命周期钩子函数");
        },
        data(){
            return {
                msg:"组件的数据你好"
            }
        },
       template:`
       <div>
            <p>我是子组件---{{msg}}</p>
            <button @click="print">打印</button>
        </div>
       `
    })
    var app = new Vue({
        el:"#app",
        mixins:[mixin],
    })

</script>

过滤器 filter

过滤器可以用在两个地方:**双花括号插值和 `v-bind` 表达式** (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示。

当全局过滤器和局部过滤器重名时,会采用局部过滤器。

全局过滤器:在创建 Vue 实例之前全局定义过滤器

// 全局过滤器

     Vue.filter("replaceMinus",function(value){
         return value.replace(/-/g," ");
     })

局部过滤器

var app = new Vue({

        el:"#app",
        data:{
            msg:'hello-world-haha-heihei'
        },
        filters:{
            "replaceMinus":function(value){
                return value.replace(/-/g," ");
            },
            "upperCase":function(value){
                return value.toUpperCase();
            }
        }
    })

 案例:
<div id="app">
    <p>{{msg | replaceMinus}}</p>
    <p>{{msg | upperCase}}</p>
   
</div>
<script>
    //    全局过滤器
    Vue.filter("replaceMinus",function(value){
        return value.replace(/-/g," ");
    })
    var app = new Vue({
        el:"#app",
        data:{
            msg:'hello-world-haha-heihei'
        },
        filters:{
            // "replaceMinus":function(value){
            //     return value.replace(/-/g," ");
            // },
            "upperCase":function(value){
                return value.toUpperCase();
            }
        }
    })

</script>


自定义指令directive

<div id="app">

    <input type="text" v-focus>自定义指令</input>
    <input type="text" v-focu>
</div>
<script>
        
    // 全局指令注册 bg inserted的函数
    Vue.directive('focus',{
        inserted:function(el){
            // el是当前的dom节点
            console.log(el);
            // window对象
            console.log(this);
            el.focus();
        }
    })
  //局部指令注册使用directives
    var app = new Vue({
        el: "#app",
       directives:{
           focu:{
               inserted:function(el){
                //    同上
                   console.log(this);
                   console.log(el);
                   el.focus();
               }
           }
       }
    })

</script>

自定义指令的钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind:只调用一次,指令与元素解绑时调用。


钩子函数参数

指令钩子函数会被传入以下参数:

el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property:

    name:指令名,不包括 v- 前缀。
    value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
    oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
    arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
    modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。

vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

<div id="app">

    <div id="hook-arguments-example" v-demo:foo.a.b="msg"></div>
</div>

<script>
    // 全局注册 bg inserted的函数
    Vue.directive('demo', {
        bind: function (el, binding, vnode) {
            // el是当前的dom节点
            console.log(el);
            // 一个对象包含属性
            console.log(binding);
            // Vue 编译生成的虚拟节点。
            console.log(vnode);
            var s = JSON.stringify
            el.innerHTML =
                //表示demo
                'name: ' + s(binding.name) + '<br>' +
                //表示msg
                'value: ' + s(binding.value) + '<br>' +
                //msg
                'expression: ' + s(binding.expression) + '<br>' +
                //foo
                'argument: ' + s(binding.arg) + '<br>' +
                //{a:true,b:true}
                'modifiers: ' + s(binding.modifiers) + '<br>' +
                'vnode keys: ' + Object.keys(vnode).join(', ')
        }
    })
    var app = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        }
    })

</script>


### [动态指令参数](https://cn.vuejs.org/v2/guide/custom-directive.html#动态指令参数)

创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:

这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。

<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
//通过
Vue.directive('pin', {
bind: function (el, binding, vnode) {

el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'

}
})

new Vue({
el: '#dynamicexample',
data: function () {

return {
  direction: 'left'
}

}
})

渲染函数render

`createElement` 到底会返回什么呢?其实不是一个*实际的* DOM 元素。它更准确的名字可能是 `createNodeDescription`,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“**VNode**”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼

<div id="app">

   
</div>
<div>
    <anchored-heading :level="2">Hello-world!</anchored-heading>
</div>


<script>

    Vue.component('anchored-heading', {
        render: function (createElement) {
            console.log(this.level);
            console.log(this.$slots);
            return createElement(
                'h' + this.level,   // 标签名称
                this.$slots.default // 子节点数组
            )
        },
        props: {
            level: {
                type: Number,
                required: true
            }
        }
    })
    var app = new Vue({
        el: "#app",

        // 通过渲染函数
        render:function(createElement){
            let vnode = createElement(
                "h1",//第一个参数
                {   //第二个参数
                    class:{
                        foo:true,
                    },
                    style:{
                        color:"red",
                    },
                    attrs:{
                        title:"标题属性"
                    }
                },
                "你好")//第三个参数
            console.log(vnode);
            return vnode
        }
    })

</script>

## vue-router

index.html

链接到vue的router中
<router-link to="/about">About</router-link>
<router-link to="/home">Home</router-link>
展示跳转显示的模板位置
<router-view></router-view>

js/index.js

导入router
定义router:router

router/index.js

导入组件About 组件中导入的地址是相对自身文件的
new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        }
    ]
})
版权声明:程序员胖胖胖虎阿 发表于 2022年9月7日 上午9:32。
转载请注明:vue学习笔记 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...