尚硅谷Vue2.0笔记

文章目录

Vue核心

1.初始Vue

Vue是一套构建用户界面的渐进式JavaScript框架

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  2. root容器里的代码依然符合html渲染规范,只不过混入了一些特殊的Vue语法
  3. root容器里的代码称为【Vue模板】
  4. Vue实例和容器是一一对应的
  5. 真实开发中只有一个Vue实例,并且会配合组件一起使用
  6. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
  7. 一旦data中的数据发生改变,那么模板中用到该数据地方也会自动更新
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
  
    <div id="root">
        <h1>Hello,{{name}}</h1>
    </div>

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

        //创建Vue实例
        new Vue({
            el:'#root',  //el用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串
            data:{  //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
                name:'尚硅谷'
            }
        })
    </script>
</body>
</html>

2.模板语法

1.插值语法

  • 功能:用于解析标签体中的内容
  • 语法:{{xxx}},xxx会作为js表达式解析,且可以直接读取到data中的所有属性

2.指令语法

  • 功能:解析标签属性、解析标签体内容、绑定事件
  • 举例:v-bind:href = ‘xxx’,xxx会作为js表达式解析,可以简写为:href=‘xxx’,xxx同样要写js表达式
  • 备注:Vue中有很多指令,且形式都是:v-???,此时我们只是拿v-bind举例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h1>插值语法</h1>
        <!-- 插值语法 {{name}} -->
        <h3>你好,{{name}}</h3>
        <hr>

        <h1>指令语法</h1>
        <!-- 指令语法 v-bind: 可以简写成:-->
        <a v-bind:href="url">点我去{{school.name}}学习</a>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

        new Vue({
            el:'#root',
            data:{
                name:'jack',
                school:{
                    name:'尚硅谷',
                    url:'http://www.atguigu.com'
                }
            }
        })

    </script>
</body>
</html>

3.数据绑定

1.单项绑定(v-bind)

  • 数据只能从data流向页面

2.双向绑定(v-model)

  • 数据不仅能从data流向页面,还可以从页面流向data
  • 双向绑定一般都应用在表单类元素上(如:input、select等)
  • v-model:value可以简写为v-model,因为v-model默认收集的就是value值
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    
    <div id="root">
        <!-- 普通写法 -->
        单项数据绑定:<input type="text" v-bind:value="name"><br>
        双项数据绑定:<input type="text" v-model:value="name">


        <!-- 简写 -->
        单项数据绑定:<input type="text" :value="name"><br>
        双项数据绑定:<input type="text" v-model="name">
        
        <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素) -->
        <!-- <h2 v-bind:x="name">你好啊</h2> -->
    </div>

    

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

        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷'
            }
        })
    </script>
</body>
</html>

3.el和data两种写法

el有两种写法

  1. new Vue时配置el属性
  2. 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值

data有两种写法

  1. 对象式
  2. 函数式

目前哪种写法都可以,以后学到组件时,data必须使用函数式,否则会报错

重要原则:由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h1>你好,{{name}}</h1>
    </div>

</body>    

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

        // const v = new Vue({
        //     // el:'#root',  //第一种写法
        //     data:{
        //         name:'尚硅谷'
        //     }
        // })
        // v.$mount('#root')   //第二种写法

        new Vue({
            el:'#root',
            //data第一种写法:对象式
            // data:{
            //     name:'尚硅谷'
            // }

            //data第二种写法:函数式
            data:function(){
                return {
                    name:'尚硅谷'
                }
            }
        })



    </script>

</html>

4.MVVM模型

  • M:模型(Model)对应data中的数据
  • V:视图(View)模板
  • VM:视图模型(ViewModel)Vue实例对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5h7DkCy-1686448066078)(C:/Users/28726/AppData/Roaming/Typora/typora-user-images/image-20230425180817970.png)]

5.数据代理

1.Object.defineProperty方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

        let number = 18
        let person = {
            name:'张三',
            sex:'男',
        }

        Object.defineProperty(person,'age',{
            // value:18,
            // enumerable:true,    //控制属性是否可以枚举,默认值是false
            // writable:true,  //控制属性是否可以被修改,默认值是false
            // configurable:true,   //控制属性是否可以被修改,默认值是false

            //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
            get:function(){
                return 'hello'
            },

            set(value){
                console.log('有人修改了age属性,且值时',value);
                number = value
            }
        })
   


    </script>
</body>


</html>

2.数据代理

数据代理:通过一个对象代理对另一个对象中的属性的操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>


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


        let obj = {x:100}
        let obj2 = {y:200}

        Object.defineProperty(obj2,'x',{
            get(){
                return obj.x
            },

            set(value){
                obj.x = value
            }
        })
   


    </script>
</body>
</html>

3.Vue中的数据代理

Vue中的数据代理 - 掘金 (juejin.cn)

6.事件处理

1.事件基本使用

  • 使用v-on:xxx@xxx 绑定事件,其中xxx是事件名
  • 事件的回调需要配置在methods对象中,最终会在vm上
  • methods中配置的函数,不要用箭头函数,否则this就不是vm了
  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
  • @click=“demo” 和 @click="demo($event)"效果一样,但后者可以传参
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- <button v-on:click="showInfo">点我提示信息</button> -->
        <!-- 简写 -->
        <button @click="showInfo1">点我提示信息</button>
        <button @click="showInfo2(66,$event)">点我提示信息</button>


    </div>

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

        
        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷'
            },
            methods:{
                showInfo1(event){
                    // console.log(event.target);
                    // console.log(this);//此处是this
                    alert('同学你好1')
                },
                showInfo2(number,event){
                    console.log(number);
                    console.log(event);
                    // console.log(event.target);
                    // console.log(this);//此处是this
                    alert('同学你好2')
                }
            }
        })


    </script>
</body>
</html>

2.事件修饰符

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

    阻止了事件的默认行为,相当于调用了event.preventDefault方法,提交事件将不再重新加载页面

    <form @submit.prevent="onSubmit"></form>
    
  2. stop:阻止事件冒泡(常用)

    阻止了事件冒泡,相当于调用了event.stopPropagation方法,单击事件将停止传递

    <div @click="shout(2)">
        <button @click.stop="shout(1)">ok</button> 
    </div> 
    //只输出1
    
  3. once:事件只触发一次(常用)

    绑定了事件以后只能触发一次,第二次就不会触发

    <button @click.once="shout(1)">ok</button>
    
  4. capture:使用事件的捕获模式

    添加事件监听器时,使用 capture 捕获模式,例如:指向内部元素的事件,在被内部元素处理前,先被外部处理。使事件触发从包含这个元素的顶层开始往下触发

    <div @click.capture="shout(1)">
        obj1
    <div @click.capture="shout(2)">
        obj2
    <div @click="shout(3)">
        obj3
    <div @click="shout(4)">
        obj4
    </div>
    </div>
    </div>
    </div>
    // 输出结构: 1 2 4 3 
    
  5. self:只有event.target是当前操作的元素是才触发事件

    仅当 event.target 是元素本身时才会触发事件处理器,例如:事件处理器不来自子元素

  6. passive:事件的默认行为立即执行。无需等待事件回调之星完毕

    在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符。

    滚动事件的默认行为 (scrolling) 将立即发生而非等待 onScroll 完成,以防其中包含 event.preventDefault()

    <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
    <!-- 而不会等待 `onScroll` 完成  -->
    <!-- 这其中包含 `event.preventDefault()` 的情况 -->
    <div v-on:scroll.passive="onScroll">...</div>
    

3.键盘事件

Vue中常用的按键别名

  • 回车enter
  • 删除delete
  • 退出esc
  • 空格space
  • 换行tab(配合keydown使用)
  • 上up
  • 下down
  • 左left
  • 右right

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

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

  1. 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
  2. 配合keydown使用:正常触发事件

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

Vue.config.KeyCodes.自定义键名 = 键码,可以去自定义按键别名

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- 输入之后按下enter才会触发事件 -->
        <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
    </div>


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

   
        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷'
            },
            methods:{
                showInfo(e){
                    
                    console.log(e.target.value)
                }
            }
        })

    </script>
</body>
</html>

7.计算属性与监视

1.计算属性

插值语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        姓:<input type="text" v-model="firstName"><br><br>
        名:<input type="text" v-model="lastName"><br><br>
        姓名:<span>{{firstName}}-{{lastName}}</span>
    </div>

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

        new Vue({
            el:'#root',
            data:{
                firstName:'张',
                lastName:'三'
            }
        })


    </script>
</body>
</html>

methods实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        姓:<input type="text" v-model="firstName"><br><br>
        名:<input type="text" v-model="lastName"><br><br>
        姓名:<span>{{fullName()}}</span>
    </div>

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

        new Vue({
            el:'#root',
            data:{
                firstName:'张',
                lastName:'三'
            },
            methods:{
                fullName(){
                    return this.firstName+'-'+this.lastName
                }
            }
        })


    </script>
</body>
</html>

计算属性

  1. 定义:要用的属性不存在,要通过已有的属性计算得来
  2. 原理:底层借助了Object.defineproperty方法提供的getter和setter
  3. get函数什么时候执行
    • 初次读取时会执行一次
    • 当依赖的数据发生改变时会再次被调用
  4. 优势:与methods实现相比,内部有缓存机制,效率更高,调试方便
  5. 备注:
    • 计算属性最终会出现在vm上,直接读取使用即可
    • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        姓:<input type="text" v-model="firstName"><br><br>
        名:<input type="text" v-model="lastName"><br><br>
        姓名:<span>{{fullName}}</span>
    </div>

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

        const vm = new Vue({
            el:'#root',
            data:{
                firstName:'张',
                lastName:'三',
            },
            computed:{
                fullName:{
                    // 当有人读取fullName时,get会被调用,且饭hi之就作为fullName的值
                    //get什么时候调用 1.初次读取时调用 2.所依赖的数据发生变化时
                    get(){
                        console.log('get被调用');
                        return this.firstName+'-'+this.lastName
                    },

                    //set什么时候调用 当fullName被修改时
                    set(value){
                        const arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            }
            
              //简写
                // fullName:function(){
                //     return this.firstName+'-'+this.lastName
                // }
         
        })


    </script>
</body>
</html>

2.监视属性

  1. 当被监视的属性发生变化时,回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视
  3. 监视的两种写法:
    • new Vue时传入watch配置
    • 通过vm.$watch监视
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h2>今天天气很{{info}}</h2>
        <button @click="changeWeather">切换天气</button>
    </div>

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

        const vm = new Vue({
            el:'#root',
            data:{
                isHot:true
            },
            computed:{
                info(){
                    return this.isHot?'炎热':'凉爽'
                }
            },
            methods:{
                changeWeather(){
                    this.isHot = !this.isHot
                }
            },

            watch:{
                isHot:{
                    //初始化时让handler调用
                    // immediate:true
                    //handler什么时候调用 当isHost发生改变时
                    handler(newValue,oldValue){
                        console.log('isHot被修改',newValue,oldValue);
                    }
                }
            },


        })

        // vm.$watch('isHot',{
        //             //初始化时让handler调用
        //             // immediate:true
        //             //handler什么时候调用 当isHost发生改变时
        //             handler(newValue,oldValue){
        //                 console.log('isHot被修改',newValue,oldValue);
        //             }
        // })



    </script>
</body>
</html>

当然我们可以在idHot对象面添加一个属性:immediate,当此属性布尔值为真的时候,handler回调函数在初始化的时候就会调用一次。

3.深度监视

  1. Vue中的watch默认不监视对象内部值的改变(一层)
  2. 配置deep:true可以监测对象内部值改变(多层)

备注:

  1. Vue自身可以检测对象内部值的改变,但Vue提供的watch默认不可以
  2. 使用watch时根据数据的具体结构,决定是否采用深度监视
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h2>今天天气很{{info}}</h2>
        <button @click="changeWeather">切换天气</button>
        <hr>
        <h3>a的值是:{{numbers.a}}</h3>
        <button @click="numbers.a++">点我让a+1</button>
        <hr>
        <h3>b的值是:{{numbers.b}}</h3>
        <button @click="numbers.b++">点我让b+1</button>
        <button @click="numbers = {a:777,b:888}">彻底替换numbers</button>

    </div>

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

        const vm = new Vue({
            el:'#root',
            data:{
                isHot:true,
                numbers:{
                    a:1,
                    b:1
                }
            },
            computed:{
                info(){
                    return this.isHot?'炎热':'凉爽'
                }
            },
            methods:{
                changeWeather(){
                    this.isHot = !this.isHot
                }
            },

            watch:{
                isHot:{

                    //handler什么时候调用 当isHost发生改变时
                    handler(newValue,oldValue){
                        console.log('isHot被修改',newValue,oldValue);
                    }
                },
                // 监视多级结果中某个属性的变化
                'numbers.a':{
                    handler(){
                        console.log('a被改变了');
                    }
                },

                //监视多级结构中所有属性变化
                numbers:{
                    deep:true,
                    handler(){
                        console.log('numbers改变了')
                    }
                }
            },


        })
    </script>
</body>
</html>

简写形式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h2>今天天气很{{info}}</h2>
        <button @click="changeWeather">切换天气</button>
    </div>

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

        const vm = new Vue({
            el:'#root',
            data:{
                isHot:true
            },
            computed:{
                info(){
                    return this.isHot?'炎热':'凉爽'
                }
            },
            methods:{
                changeWeather(){
                    this.isHot = !this.isHot
                }
            },

            watch:{
                //正常写法
                // isHot:{
                //     //初始化时让handler调用
                //     // immediate:true,
                //     // deep:true,

                //     //handler什么时候调用 当isHost发生改变时
                //     handler(newValue,oldValue){
                //         console.log('isHot被修改',newValue,oldValue);
                //     }
                // }

                //简写
                isHot(newValue,oldValue){
                    console.log('isHot被修改',newValue,oldValue);
                }
            },


          
        })

        vm.$watch('isHot',function(newValue,oldValue){
            console.log('isHot被修改',newValue,oldValue);
        })




    </script>
</body>
</html>

4.对比

computed和watch之间的区别

  1. computed能完成的功能,watch都可以完成
  2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

重要原则:

  1. 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写出箭头函数,这样this的指向才是vm或组件实例对象

8.class和style绑定

1.class样式

写法:

  • class=‘xxx’ xxx可以是字符串、对象、数组
  • 字符串写法适用于:类名不确定、要动态获取
  • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
  • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用

2.style样式

写法:

  • :style=‘{fontSize:xxx}’ 其中xxx是动态值
  • :style=‘[a,b]’ 其中a、b是样式对象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .basic{
           width: 300px;
           height: 100px;
           border: 1px solid black;
        }
        .happy{
            border:4px solid red;
            background-color: rgba(255, 255, 0, 0.644);
            background: linear-gradient(30deg,yellow,pink,orange,yellow);
        }
        .sad{
            border:4px solid cyan;
            background-color: rgba(12, 23, 0, 0.644);
            background: linear-gradient(30deg,rgb(178, 60, 5),rgb(168, 72, 88),rgb(155, 47, 146),rgb(114, 40, 40));
        }
        .normal{
            border:4px solid rgba(12, 107, 107, 0.608);
            background-color: rgba(12, 23, 0, 0.644);
            background: linear-gradient(30deg,rgb(178, 60, 5),rgb(121, 142, 38),rgb(157, 67, 150),rgb(239, 169, 169));
        }
        .hello1{
            background-color: orchid
        }
        .hello2{
            font-size: 40px;
        }
        .hello3{
            color:red
        }
    </style>

    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <!-- 绑定class样式---字符串写法 适用于:样式的类名不确定,还要动态指定 -->
        <div class="basic" :class="mood" @click="changeMood">{{name}}</div><br><br><br>
        
        
        <!-- 绑定class样式---数组写法 适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic"  :class="classArr">{{name}}</div><br><br><br>

        <!-- 绑定class样式---对象写法 适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="classObj">{{name}}</div><br><br><br>


        <!-- 绑定style样式---对象写法 -->
        <div class="basic" :style="styleObj">{{name}}</div><br><br><br>
        <div class="basic" :style="[styleObj,styleObj2]">{{name}}</div>

    </div>

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

   
        const vm = new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                mood:'normal',
                classArr:['hello1','hello2','hello3'],
                classObj:{
                    hello1:false,
                    hello2:false
                },
                styleObj:{
                    fontSize:'40px',
                    color:'red',
                },
                styleObj2:{
                    backgroundColor:'orange'
                }
            },

            methods:{
                changeMood(){
                    const arr = ['happy','sad','normal']
                    const num = Math.floor(Math.random()*3)
                    this.mood = arr[num]
                }
            }
        })


    </script>
</body>
</html>

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

写法:

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

备注:使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>


    <div id="root">
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>


        <!-- 使用v-show做条件渲染 -->
        <!-- <h2 v-show="false">欢迎来到{{name}}</h2>
        <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->


        <!-- 使用v-if做条件渲染 -->
        <!-- <h2 v-if="false">欢迎来到{{name}}</h2>
        <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->



        <div v-show="n === 1">Angular</div>
        <div v-show="n === 2">React</div>
        <div v-show="n === 3">Vue</div>

        <!-- v-else 和 v-else-if -->
        <!-- <div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div> -->

        <template v-if="n === 1">
            <h2>你好</h2>
            <h2>尚硅谷</h2>
            <h2>北京</h2>
        </template>


    </div>


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


        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                n:0
                
            }
        })


    </script>
</body>
</html>

10.列表渲染

v-for指令

  1. 用于展示列表数据
  2. 语法:v-for="(item,index) in xxx" :key="yyy"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表</h2>
        <ul>
            <li v-for="p in persons" :key="p.id">
                {{p.name}}-{{p.age}}
            </li>
        </ul>

        <!-- 遍历对象 -->
        <h2>汽车信息</h2>
        <ul>
            <li v-for="(value,k) of car" :key="k">
                {{k}}---{{value}}
            </li>
        </ul>

        <!-- 遍历字符串 -->
        <h2>测试遍历字符串</h2>
        <ul>
            <li v-for="(char,index) of str" :key="index">
                {{char}}---{{index}}
            </li>
        </ul>
    </div>

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

   
        new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'001',name:'张三',age:18},
                    {id:'002',name:'李四',age:19},
                    {id:'003',name:'王五',age:20}
                ],
                car:{
                    name:'奥迪A8',
                    price:'70万',
                    color:'黑色'
                },
                str:'hello'
            }
        })
    </script>
</body>
</html>

1.列表过滤

watch实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表</h2>
        <input type="text" placeholder="请输入名字" v-model="keyWord">
        <ul>
            <li v-for="p in filPersons" :key="p.id">
                {{p.name}}-{{p.age}}---{{p.sex}}
            </li>
        </ul>
    </div>

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

   
        new Vue({
            el:'#root',
            data:{
                keyWord:'',
                persons:[
                    {id:'001',name:'马冬梅',age:18,sex:'女'},
                    {id:'002',name:'周冬雨',age:19,sex:'女'},
                    {id:'003',name:'周杰伦',age:21,sex:'男'},
                    {id:'004',name:'温兆伦',age:20,sex:'男'}
                ],
                filPersons:[]
            },

            watch:{
                keyWord:{
                    immediate:true,
                    handler(val){
                        this.filPersons = this.persons.filter((p)=>{
                            return p.name.indexOf(val) !== -1
                        })
                    }
                    
                }
            }
        })


    </script>
</body>
</html>

computed实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue初识</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
<div id="root">
    <!--遍历数组-->
    <h2>人员列表(遍历数组)</h2>
    <input type="text" placeholder="请输入名字" v-model="keyword"/>
    <ul>
        <li v-for="(p,index) in filterPersons" :key="p.id">
            {{p.name}}--{{p.age}}---{{p.sex}}
        </li>
    </ul>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false
    //创建vue实例
    new Vue({
        el: "#root",
        data: {
            keyword: '',
            persons: [
                {id: 1, name: "马冬梅", age: 18, sex: "女"},
                {id: 2, name: "周冬雨", age: 19, sex: "女"},
                {id: 3, name: "周杰伦", age: 20, sex: "男"},
                {id: 4, name: "温兆伦", age: 21, sex: "男"},
            ]
        },
        computed: {
            filterPersons() {
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyword) !== -1
                })
            }
        }
    })
</script>
</body>
</html>

2.列表排序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue初识</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
<div id="root">
    <!--遍历数组-->
    <h2>人员列表(遍历数组)</h2>
    <input type="text" placeholder="请输入名字" v-model="keyword"/>
    <button @click="sortType = 2">年龄升序</button>
    <button @click="sortType = 1">年龄降序</button>
    <button @click="sortType = 0">原顺序</button>
    <ul>
        <li v-for="(p,index) in filterPersons" :key="p.id">
            {{p.name}}--{{p.age}}---{{p.sex}}
        </li>
    </ul>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false
    //创建vue实例
    new Vue({
        el: "#root",
        data: {
            sortType:0, //0原顺序 1降序 2升序
            keyword: '',
            persons: [
                {id: 1, name: "马冬梅", age: 23, sex: "女"},
                {id: 2, name: "周冬雨", age: 19, sex: "女"},
                {id: 3, name: "周杰伦", age: 20, sex: "男"},
                {id: 4, name: "温兆伦", age: 18, sex: "男"},
            ]
        },
        computed: {
            filterPersons() {
                
                const arr =  this.persons.filter((p) => {
                    return p.name.indexOf(this.keyword) !== -1
                })
                //判断是否需要排序
                if(this.sortType){
                    arr.sort((p1,p2)=>{
                        return this.sortType == 1 ? p2.age-p1.age : p1.age-p2.age
                    })
                }
                return arr
            }
        }
    })
</script>
</body>
</html>

11.监视数据

Vue监视数据的原理:

  1. vue会监视data中所有层次的数据

  2. 如何检测对象中的数据

    通过setter实现监视,且要在new Vue时传入要监测的数据

    • 对象中后追加的属性,Vue默认不做响应式处理
    • 如需给后添加的属性做响应式,请使用如下API
      • Vue.set(target,propertyName/index,value)
      • vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据

    通过包裹数组更新元素的方法实现,本质就是做了两件事

    • 调用原生对应的方法对数组进行更新
    • 重新解析模板,进而更新页面
  4. 在Vue修改数组中的某个元素一定要用如下的方法

    • 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    • Vue.set()或vm.$set()

特别注意:Vue.set()和vm.$set()不能给vm或vm根数据对象添加属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h1>学生信息</h1><br>
        <button @click="student.age++">年龄+1岁</button><br>
        <button @click="addSex">添加性别属性:默认值:男</button><br>
        <button @click="student.sex = '未知'">修改性别属性</button><br>

        <button @click="addFriend">在列表首位添加一个朋友</button><br>
        <button @click="updateFirstFriend">修改第一个朋友的名字:张三</button><br>
        <button @click="addHobby">添加一个爱好</button><br>
        <button @click="updateHobby">修改第一个爱好为:开车</button><br>

        <h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h3>
        <h3 v-if="student.sex">性别:{{student.sex}}</h3>
        <h3>爱好:</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>
        <h3>朋友们:</h3>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}}---{{f.age}}
            </li>
        </ul>
    </div>

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

   
        const vm = new Vue({
            el:'#root',
            data:{
                student:{
                    name:'tom',
                    age:18,
                    hobby:['抽烟','喝酒','烫头'],
                    friends:[
                        {name:'jerry',age:29},
                        {name:'tony',age:30}
                    ]
                }
            },
            methods:{
                addSex(){
                    // Vue.set(this.student,'sex','男')
                    this.$set(this.student,'sex','男')
                },
                addFriend(){
                    this.student.friends.unshift({name:"peng",age:32})
                },
                updateFirstFriend(){
                    this.student.friends[0].name = '张三'
                },
                addHobby(){
                    this.student.hobby.push('学习')
                },
                updateHobby(){
                    // this.student.hobby.splice(0,1,'开车')
                    // Vue.set(this.student.hobby,0,'开车')
                    this.$set(this.student.hobby,0,'开车')
                }
            }
        })


    </script>
</body>
</html>

在这里插入图片描述

12.收集表单数据

若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值

若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值

若:<input type="checkbox"/>

  1. 没有配置input的value属性,那么收集的就是checked(勾选或未勾选,是布尔值)
  2. 配置input的value属性:
    • v-model的初始值是非数组,那么收集的就是checked(勾选或未勾选,是布尔值)
    • v-model的初始值是数组,那么收集的就是value组成的数组

备注:

v-model的三个修饰符:

  • lazy:失去焦点再收集数据
  • number:输入字符串转为有效数字
  • trim:输入首尾空格过滤

13.过滤器

定义:对要显示的数据进行特定格式化后再显示(适用于简单逻辑的处理)

语法:

  1. 注册过滤器:Vue.filter(name,callback)或new Vue{filters:{}}
  2. 使用过滤器:{{xxx | 过滤器名}} 或 v-bind:属性=“xxx | 过滤器名”

备注:

  1. 过滤器也可以接受额外的参数、多个过滤器也可以串联
  2. 并没有改变原本的数据,是产生新的对应的数据

14.内置指令

1.v-text

  1. 作用:向其所在的节点中渲染文本内容
  2. 与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}不会
  <div>{{name}}</div>
  <div v-text="name"></div>

2.v-html

  1. 作用:向指定的节点渲染包含html结构的内容
  2. 与插值语法的区别:
    • v-html会替换掉节点中所有的内容
    • v-html可以识别html结构
  3. 严重注意:v-html有安全性问题
	<div id="root">
        <div>{{name}}</div>
        <div v-html="str"></div>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                str:`<h3>你好啊</h3>`
            }
        })
   

3.v-cloak

  1. 本质是一个特殊的属性:Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  2. 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题

4.v-once

  1. v-once所在节点在初始动态渲染之后,就视为静态内容
  2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h2 v-once>初始化n值:{{n}}</h2>
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

        new Vue({
            el:'#root',
            data:{
                n:1
            }
        })

    </script>
</body>
</html>

5.v-pre

  1. 跳过其所在节点的编译过程
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法。会加快编译
 <div id="root">
        <h2 v-pre>Vue其实很简单</h2>
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>

15.自定义指令

定义语法:

  1. 局部指令

    new Vue({
    	directives:{指令名:配置对象}
    })
    或
    new Vue({
    	directives{指令名:配置对象}
    })
    
  2. 全局指令

    Vue.directives(指令名,配置对象)或Vue.directive(指令名,回调函数)
    

配置对象中常用的3个回调函数

  1. bind:指令与元素成功绑定时调用
  2. inserted:指令所在元素被插入页面时调用
  3. update:指令所在模板结构被重新解析时调用

备注:

  1. 指令定义时不加v-,但使用时要加v-
  2. 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
    <!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值方法10倍-->
    <!-- 需求2:定义一个v-fbing指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点-->

    <div id="root">
        <h2>当前的n值是:<span v-text="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>
    
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

   
        new Vue({
            el:'#root',
            data:{
                n:1
            },
            directives:{
                //big函数何时会被调用 1.指令与元素成功绑定时 2.指令所在的模板被重新解析时
                big(element,binding){
                    element.innerText = binding.value * 10
                },
                fbind:{
                    //指令与元素成功绑定时(一上来)
                    bind(element,binding){
                        element.value = binding.value
                    },
                    //指令所在元素被插入页面时
                    inserted(element,binding){
                        element.focus()
                    },
                    //指令所在模板被重新解析
                    update(element,binding){
                        element.value = binding.value
                        element.focus()
                    }
                }
            }
        })


    </script>
</body>
</html>

16.生命周期

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子
  2. 是什么:Vue在关键时刻帮我们调用一些特殊名称的函数
  3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  4. 生命周期函数中的this指向的是vm或组件实例对象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h2 :style="{opacity}">欢迎学习Vue</h2>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

        new Vue({
            el:'#root',
            data:{
                opacity:1
            },
            methods:{
                
            },
            //Vue完成模板解析并把真实DOM元素放入页面后(挂载完毕)调用
            mounted(){
                setInterval(()=>{
                    this.opacity -= 0.01
                    if(this.opacity<=0) this.opacity=1
                },16)
            }
        })

        //通过外部定时器实现(不推荐)
        // setInterval(()=>{
        //     vm.opacity -= 0.01
        //     if(vm.opacity<=0){
        //         vm.opacity=1
        //     }
        // },16)

    </script>
</body>
</html>

常用的生命周期钩子:

  1. mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)
  2. beforeDestroy:消除定时器、绑定自定义事件、取消订阅消息

关于销毁Vue实例:

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

Vue组件化编程

1.非单文件组件

Vue中使用组件的三大步骤:

  1. 定义组件

  2. 注册组件

  3. 使用组件

  4. 如何定义一个组件

    使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

    区别如下:

    • el不要写,为什么?最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
    • data必须写成函数,为什么?避免组件被复用时,数据存在引用关系

    备注:使用template可以配置组件结构

  5. 如何注册组件

    • 局部注册:靠new Vue的时候传入component选项
    • 全局注册:靠Vue.component(‘组件名’,组件)
  6. 编写组件标签

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<student></student>
		</div>
	</body>

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

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

		//第一步:创建student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
			data(){
				return {
					studentName:'JOJO',
					age:20
				}
			}
		})
		
		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好,JOJO!'
			},
			//第二步:注册组件(局部注册)
			components:{
				school,
				student
			}
		})
	</script>
</html>

1.注意点

关于组件名:

  1. 一个单词组成:

    第一种写法(首字母小写):school

    第二种写法(首字母大写):School

  2. 多个单词组成

    第一种写法(kebab-case命名):my-schoole

    第二种写法(CamelCase命名):MySchool(需要Vue脚手架)

  3. 备注

    • 组件名尽可能回避HTML中已有的元素名称
    • 可以使用name配置项指定的组件再开发者工具呈现的名字

关于组件标签:

  1. 第一种写法:
  2. 第二种写法:
  3. 备注:不要使用脚手架时,会到安置后续组件不能渲染

一个简写方式:

const school = Vue.extend(options)可简写为:const school = options

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>

    <div id="root">
        <h1>{{msg}}</h1>
        <school></school>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false    //阻止vue在启动时生成生产提示

   
        //定义组件
        const school = Vue.extend({
            template:`
                <div>
                    <h2>学校名称:{{name}}</h2>
                    <h2>学校地址:{{address}}</h2>
                </div>
            `,
            data(){
                return {
                    name:'尚硅谷',
                    address:'北京'
                }
            }
        })


        new Vue({
            el:'#root',
            data:{
                msg:'欢迎学习Vue'
            },
            components:{
                school:school
            }
        })

    </script>
</body>
</html>

2.组件的嵌套

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生名称:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'JOJO',
					age:20
				}
			}
		})

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

		//定义hello组件
		const hello = Vue.extend({
			template:`
				<h1>{{msg}}</h1>
			`,
			data(){
				return {
					msg:"欢迎学习尚硅谷Vue教程!"
				}
			}
		})

		//定义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>
</html>

在这里插入图片描述

3.VueComponent

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
  2. 我们只需要写或,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实例对象

4.一个重要的内置关系

  1. 一个重要的内置关系:VueComponent.prototype.proto_ === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问Vue原型上的属性和方法
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>一个重要的内置关系</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<school></school>
		</div>
	</body>

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

		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showX">点我输出x</button>
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			methods: {
				showX(){
					console.log(this.x)
				}
			},
		})

		const vm = new Vue({
			el:'#root',
			data:{
				msg:'你好'
			},
			components:{school}
		})
	</script>
</html>

在这里插入图片描述

2.单文件组件

School.vue

<template>
    <!--组件的结构-->
    <div class="demo">
        <h2>学校名称:{{ schoolName }}</h2>
        <h2>学校地址:{{ address }}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    //组件交互代码(数据、方法等)
    export default {
        name:'School',
        data(){
            return {
                schoolName:'尚硅谷',
                address:'北京'
            }
        },
        methods:{
            showName(){
                alert(this.showName)
            }
        }
    }
    
</script>


<style>
    /* 组建的样式 */
    .demo {
        background-color: red;
    }
</style>

Student.vue

<template>
    <!--组件的结构-->
    <div class="demo">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学校年龄:{{ age }}</h2>
    </div>
</template>

<script>
    //组件交互代码(数据、方法等)
    export default {
        name:'Student',
        data(){
            return {
                name:'彭于晏',
                age:32
            }
        }
    }
    
</script>


<style>
    /* 组建的样式 */
    .demo {
        background-color: red;
    }
</style>

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>

<style>


</style>

main.js

import App from './App.vue'

new Vue({
    el:'#root',
    template:`<App></App>`,
    components:{App},
})

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>
    <div id="root">
        
    </div>

    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript" src="./main.js"></script>
</body>
</html>

Vue脚手架

.文件目录
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件

1.ref属性

  1. 被用来给元素或子组件注册引用信息
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象
  3. 使用方式:
<template>
    <div>
        <h1 v-text="msg" ref="title"></h1>
        <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
        <School id="sch"/>
    </div>
</template>


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

    export default {
        name:'App',
        data(){
            return{
                msg:'欢迎学习vue!'
            }
        },
        components:{
            School
        },
        methods:{
            showDOM(){
                console.log(this.$refs.title);//真实DOM
                console.log(this.$refs.sch);//School组建的实例对象(vc)
                console.log(this.$refs.btn);//真实DOM
            }
        }
    }

</script>


2.props配置

功能:让组件接受外部传过来的数据

  1. 传递数据

  2. 接收数据

    • 第一种方式(只接受)

      props:['name']

    • 第二种方式(限制类型)

      props:{name:String}

      • 第三种方式(限制类型、限制必要性、指定默认值)

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

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props到data中一份,然后去修改data中的数据。

3.mixin混入

功能:可以把多个组件共有的配置提取成一个混入对象

使用方式:

  1. 第一步定义混合,例如

    {
    
    ​	data(){......},
    
    ​	methods:{......}
    
    }
    
  2. 第二步使用混合,例如

    • 全局混入:Vue.mixin(xxx)
    • 局部混入:mixins:[‘xxx’]

mixin.js

export const hunhe = {
        methods:{
            showName(){
                alert(this.name)
            }
    }
}
<template>
    <div>
        <h2 @click="showName">学生姓名:{{ name }}</h2>
        <h2>学生性别:{{ sex }}</h2>
    </div>
</template>


<script>

  //引入hunhe
  import {hunhe} from '../mixin'

    export default {
        name:'Student',
        data(){
            return {
                name:'张三',
                sex:'男'
            }
        },

        mixins:[hunhe]
    
        
    }
</script>

<style>
   
</style>

4.插件

功能:用于增强Vue

本质:包含install方法的一个对象,install的第一个参数Vue,第二个以后的参数是插件使用者传递的数据

定义插件:

​ 对象.install = function(Vue,option)

  1. 提娜佳全局过滤器

    Vue.filter(...)
    
  2. 添加全局指令

    Vue.directive(...)
    
  3. 配置全局混入

    Vue.mixin(...)
    
  4. 添加实例方法

    Vue.prototype.$myMethod = function(){...}
    Vue.prototype.$myProperty = xxx
    

使用插件:Vue.user()

5.scoped样式

作用:让样式在局部生效,防止冲突

写法:<style scoped>

6.Todo-list案例

  1. 组件编码流程:
    • 拆分静态组件:组件要按照功能点拆分,命名不要与HTML元素冲突
    • 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
      • 一个组件在用:放在组件自身即可
      • 一些组件在用:放在他们共同的父组件上(状态提升)
    • 实现交互:从绑定事件开始
  2. props适用于:
    • 父组件===>子组件 通信
    • 子组件===>父组件通信(要求父组件给子组件一个函数)
  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的,因为props是不可以修改的
  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

App.vue

<template>
 <div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
     <MyHeader :addTodo="addTodo"/>
      <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
     <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</div>
</template>

<script>
    import MyHeader from './components/MyHeader.vue'
    import MyFooter from './components/MyFooter.vue'
    import MyList from './components/MyList.vue'

    export default {
      name: 'App',
      components: {
       MyHeader,MyFooter,MyList
      },
      data(){
            return {
                todos:[
                    {id:'001',title:'吃饭',done:true},
                    {id:'002',title:'喝酒',done:true},
                    {id:'003',title:'唱歌',done:false},
                    {id:'004',title:'开车',done:true}
                ]
            }
        },
        methods:{
          //添加todo
          addTodo(todoObj){
            this.todos.unshift(todoObj)
          },
          //勾选或取消勾选
          checkTodo(id){
            this.todos.forEach((todo)=>{
              if(todo.id === id) todo.done = !todo.done
            })
          },
          //删除todo
          deleteTodo(id){
            this.todos = this.todos.filter((todo)=>{
                return todo.id !== id
            })
          },
          //全选或全不选
          checkAllTodo(done){
            this.todos.forEach((todo)=>{
              todo.done = done
            })
          },

          //清除所有完成todo
          clearAllTodo(){
            this.todos = this.todos.filter((todo)=>{
              return !todo.done
            })
          }
        }
    }
</script>

<style>
  /*base*/
  body {
    background: #fff;
  }

  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }

  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }

  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }

  .btn:focus {
    outline: none;
  }

  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
 
</style>

MyHeader.vue

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title"  @keyup.enter="add"/>
    </div>
</template>


<script>

    import {nanoid} from 'nanoid'

    export default {
        name:'MyHeader',
        props:['addTodo'],
        data(){
            return {
                title:''
            }
        },
        methods:{
            add(e){
                if(!this.title.trim()) return 
                //将用户输入包装成为todo对象
                const todoObj = {id:nanoid(),title:e.target.value,done:false}
                //通知APP组件添加对象
                this.addTodo(todoObj)
                //清空输入
                this.title = ''

            }
        },
       
    }
</script>


<style scoped>
/*header*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }

  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }

</style>

MyList.vue

<template>
    <ul class="todo-main">
        <MyItem 
        v-for="todoObj in todos" 
        :key="todoObj.id" 
        :todo="todoObj" 
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"/>

    </ul>
</template>


<script>

    import MyItem from './MyItem.vue'


    export default {
        name:'MyList',
        components:{MyItem},
        props:['todos','checkTodo','deleteTodo']
    }
</script>


<style scoped>
  
  /*main*/
  .todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
  }

  .todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
  }
</style>

MyItem.vue

<template>
    <li >
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
    
</template>


<script>
    export default {
        name:'MyItem',
        //声明接受todo对象
        props:['todo','checkTodo','deleteTodo'],
        methods:{
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                this.checkTodo(id)
            },
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    this.deleteTodo(id)
                }
            }
        }
    }
</script>


<style scoped>

 /*item*/
 li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
  }

  li label {
    float: left;
    cursor: pointer;
  }

  li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
  }

  li button {
    float: right;
    display: none;
    margin-top: 3px;
  }

  li:before {
    content: initial;
  }

  li:last-child {
    border-bottom: none;
  }

  li:hover {
    background-color: #ddd;
  }

  li:hover button{
    display: block;
  }
</style>

MyFooter.vue

<template>
    <div class="todo-footer" v-show="total">
        <label>
          <input type="checkbox" :checked="isAll" @change="checkAll"/>
        </label>
        <span>
          <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>


<script>

    export default {
        name:'MyFooter',
        data(){
            return {

            }
        },
        props:['todos','checkAllTodo','clearAllTodo'],
        computed:{
            total(){
                return this.todos.length
            },
            doneTotal(){
                return this.todos.reduce((pre,current)=>{
                    return pre+(current.done?1:0)
                },0)
                
            },
            isAll:{
               
               get(){
                 return this.doneTotal === this.total && this.total>0
               },
               set(value){
                    this.checkAllTodo(value)
               }
            }
        },
        methods:{
           checkAll(e){
            this.checkAllTodo(e.target.checked)
           },

          clearAll(){
            this.clearAllTodo()
          }
        }

    }
</script>


<style scoped>
 /*footer*/
 .todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
  }

  .todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
  }

  .todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
  }

  .todo-footer button {
    float: right;
    margin-top: 5px;
  }

</style>

在这里插入图片描述

7.WebStorage

  1. 存储内容大小一般支持5MB
  2. 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制
  3. 相关API
    • xxxxStorage.setItem(‘key’,‘value’)
      • 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应值
    • xxxxStorage.getItem(‘person’)
      • 该方法接受一个键名作为参数,返回键名对应的值
    • xxxxStorgae.removeItem(‘key’)
      • 该方法接受一个键名作为参数,并把键名从存储中删除
    • xxxxStorage.clear()\
      • 该方法会清空存储中的所有数据
  4. 备注:
    1. SessionStorage存储的内容会随着浏览器的窗口关闭而消失
    2. LocalStorage存储的内容,需要手动清除才会消失
    3. xxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值为null
    4. JSON.parse(null)的结果依然是null
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>localStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>

    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="deleteAllData()">点我清空一个数据</button>




    <script>
        function saveData(){

            let p = {name:'张三',age:20}

            localStorage.setItem('msg','hello')
            localStorage.setItem('boy','彭于晏')
            localStorage.setItem('age',43)
            localStorage.setItem('person',JSON.stringify(p))
        }

        function readData(){

            console.log(localStorage.getItem('msg'))
            console.log(localStorage.getItem('age'))
            const result = localStorage.getItem('person')
            console.log(JSON.parse(result))
        }

        function deleteData(){
            localStorage.removeItem('msg')
            localStorage.removeItem('boy')

       
        }

        function deleteAllData(){
            localStorage.clear()
        }
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>sessionStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>

    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="deleteAllData()">点我清空一个数据</button>




    <script>
        function saveData(){

            let p = {name:'张三',age:20}

            sessionStorage.setItem('msg','hello')
            sessionStorage.setItem('boy','彭于晏')
            sessionStorage.setItem('age',43)
            sessionStorage.setItem('person',JSON.stringify(p))
        }

        function readData(){

            console.log(sessionStorage.getItem('msg'))
            console.log(sessionStorage.getItem('age'))
            const result = sessionStorage.getItem('person')
            console.log(JSON.parse(result))
        }

        function deleteData(){
            sessionStorage.removeItem('msg')
            sessionStorage.removeItem('boy')

       
        }

        function deleteAllData(){
            sessionStorage.clear()
        }
    </script>
</body>
</html>

8.组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件===>父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中<Demo @atguigu='test'<Demo v-on:atguigu='test'

    2. 第二种方式,在父组件中:

      <Demo ref='demo'/>
      
      mounted(){
      	this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件:this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么使用建瓯函数,否则this指向会出问题

App.vue

<template>
  <div class="app">
    <h1>{{ msg }},学生姓名:{{ studentName }}</h1>

    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName=" getSchoolName"/>
    
    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用v-on或@) -->
    <Student v-on:atguigu="getStudentName" @demo="m1"/>

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student" @click.native="show"/>

  </div>
</template>

<script>

import Student from './components/Student.vue'
import School from './components/School.vue'

export default {
  name: 'App',
  components: {
    Student,School
  },
  data(){
    return {
      msg:'你好啊',
      studentName:''
    }
  },
  methods:{
    getSchoolName(name){
      console.log('App收到了学校名:',name);
    },
    getStudentName(name,...params){
      console.log('App收到了学生名:',name.params);
    },
    m1(){
      console.log('demo事件被触发了:');

    }
  },

  mounted(){
    this.$refs.student.$on('atguigu',this.getStudentName)
  }

}
</script>

<style>
    .app {
      background-color: gray;
      padding: 5px;
    }
</style>

School.vue

<template>
    <div class="school">
        <h2>学校名称:{{ name }}</h2>
        <h2>学校地址:{{ address }}</h2>
        <button @click="sendSchoolName">把学校名给App</button>
    </div>

</template>


<script>
export default {
    name:'School',
    data(){
        return {
            name:'尚硅谷',
            address:'北京'
        }
    },
    props:[' getSchoolName'],
    methods:{
        sendSchoolName(){
            this.getSchoolName(this.name)
        }
    }
}
</script>


<style>
.school {
    padding: 5px;
    background-color: aqua;
}
</style>

Student.vue

<template>

``  <div class="student">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学校性别:{{ age }}</h2>
        <button @click="sendStudentName">把学生名给App</button>
        <button @click="unbind">解绑atguigu事件</button>
        <button @click="death">销毁当前Student组件的实例</button>
    </div>

</template>


<script>
 export default {
    name:'Student',
    data(){
        return {
            name:'张三',
            address:'男'
        }
    },
    methods:{
        sendStudentName(){
            //触发Student实例身上的atguigu事件
            this.$emit('atguigu',this.name)
            this.$emit('demo')
        },
        unbind(){
            this.$off('atguigu')//解绑一个自定义事件
            // this.off(['atguigu','demo'])//解绑多个自定义事件
            this.$off() //解绑所有自定义事件

        },
        death(){
            this.$destroy()//销毁当前Student组件的实例,销毁后所有Student实例的自定义事件全不奏效
        }
    }
 }
</script>


<style scoped>
    .student {
        padding: 5px;
        margin-top: 30px;
        background-color: pink;
    }
</style>

9.全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    new Vue({
      render: h => h(App),
      beforeCreate(){
        this.prototype.$bus = this  //安装全局事件总线
      }
    }).$mount('#app')
    
    
  3. 使用事件总线:

    1. 接收数据:A组件想要接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

      methods(){
      	demo(data){......}
      }
      mounted(){
      	this.$bus.$on('xxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件

        beforeDestroy(){
            this.$bus.$off('hello')
        }
    

10.消息订阅与发布

Student.vue

<template>

``  <div class="student">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学校性别:{{ age }}</h2>
        <button @click="sendStudentName">把学生名给School组件</button>
    </div>

</template>


<script>

import pubsub from 'pubsub-js'
 export default {
    name:'Student',
    data(){
        return {
            name:'张三',
            address:'男'
        }
    },
    methods:{
        sendStudentName(){
            // this.$bus.$emit('hello',this.name)
            pubsub.publish('hello',666)
        }
    }
 }
</script>


<style scoped>
    .student {
        padding: 5px;
        margin-top: 30px;
        background-color: pink;
    }
</style>

School.vue

<template>
    <div class="school">
        <h2>学校名称:{{ name }}</h2>
        <h2>学校地址:{{ address }}</h2>

    </div>

</template>


<script>

import pubsub from 'pubsub-js'
export default {
    name:'School',
    data(){
        return {
            name:'尚硅谷',
            address:'北京'
        }
    },
    mounted(){
        // this.$bus.$on('hello',(data)=>{
        //     console.log('我是School组件,我收到了数据',data);
        // })

        this.pubId = pubsub.subscribe('hello',function(msgName,data){
            console.log('有人发布了hello消息,hello消息的回调执行了',data);
        })
    },
    beforeDestroy(){
        // this.$bus.$off('hello')
        pubsub.unsubscribe(this.pubOd)
    }
}
</script>


<style>
.school {
    padding: 5px;
    background-color: aqua;
}
</style>

11.$nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次DOM更新结束后执行其指定的回调
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行

12.过渡与动画

  1. 作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名

  2. 图示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y45dbEcf-1686448066084)(…/%E5%9B%BE%E7%89%87/image-20230608230944042.png)]

  3. 写法:

    1. 准备好样式:

      1. 元素进入的样式
        • v-enter:进入的起点
        • v-enter-active:进入的过程
        • v-enter-to:进入的终点
      2. 元素离开的样式
        • v-leave:离开的起点
        • v-leave-active:离开的过程
        • v-leave-to:离开的终点
    2. 使用<transition>包裹要过渡的元素,并配置name属性

              <Transition name='hello'>
                  <h1 v-show="isShow">你好啊</h1>
              </Transition>
      
    3. 备注:若有多个元素需要过渡,则需要使用:<transition-group>,且每个元素都要指定key值

Vue中Ajax

1.vue脚手架配置代理

1.方法一

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

  //开启代理服务器(方式一)
  // devServer: {
  //   proxy: 'http://localhost:5000'
  // },

说明:

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

2.方法二

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


  //开启代理服务器(方式二)
  devServer: {
    proxy: {
      '/atguigu': { //匹配所有以'/atguigu'开头的请求路径
        target: 'http://localhost:5000',//代理目标的基础路径
        pathRewrite:{'^/atguigu':''},
        ws:true,//用于支持websocket
        // ws: true,
        // changeOrigin: false
      },
      '/demo': {
        target: 'http://localhost:5001',
        pathRewrite:{'^/demo':''},
        ws:true,//用于支持websocket
        // ws: true,
        // changeOrigin: false
      },
    }
  }

说明:

  1. 优点:可以配置多个代理,且可以灵活控制请求是否走代理
  2. 缺点:配置略微繁琐,请求资源时必须加前缀
    在这里插入图片描述

2.guthub案例

App.vue

<template>
  
  <div class="container">
    <Search/>
    <List/>
  </div>

</template>

<script>

  import axios from 'axios'
  import Search from './components/Search.vue'
  import List from './components/List.vue'

  export default {
    name: 'App',
    components:{
      Search,List
    }
  }
</script>

<style>

 
</style>

Search.vue

<template>
<section class="jumbotron">
      <h3 class="jumbotron-heading">Search Github Users</h3>
      <div>
        <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
        <button @click="searchUsers">Search</button>
      </div>
    </section>
</template>

<script>

    import axios from 'axios'   

    export default {
        name:'Search',
        data(){
            return {
                keyWord:''
            }
        },
        methods:{
            searchUsers(){
                this.$bus.$emit('updateListData',{isFirst:false,isLoading:true,errMsg:'',users:[]})
                axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
                    response => {
                        //请求成功后更新List数据
                        console.log('请求成功');
                        this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})


                    },
                    error => {
                        //请求失败后
                        this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})

                    }
                )
            }
        }
    }
</script>

<style>

</style>

List.vue

<template>
    <div class="row">
      <!-- 展示用户列表 -->
      <div class="card" v-for="user in info.users" :key="user.login" v-show="info.users.length">
        <a :href="user.html_url" target="_blank">
          <img :src="user.avatar_url" style='width: 100px'/>
        </a>
        <p class="card-text">{{user.login}}</p>
      </div>

      <!-- 展示欢迎词 -->
      <h1 v-show="info.isFirst">欢迎使用!</h1>
      <!-- 展示加载中 -->
      <h1 v-show="info.isLoading">加载中....</h1>
      <!-- 展示错误信息 -->
      <h1 v-show="info.errMsg">{{ info.errMsg }}</h1>
    </div>

</template>

<script>
    export default {
        name:'List',
        data(){
          return {
           info:{
            isFirst:true,
            isLoading:false,
            errMsg:'',
            users:[
              
            ]
           }
          }
         
        },
        mounted(){
            this.$bus.$on('updateListData',(dataObj)=>{
              this.info = {...this.info,...dataObj}
            })
          }
    }
</script>

<style scoped>
 .album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
  }

  .card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
  }

  .card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
  }

  .card-text {
    font-size: 85%;
  }
</style>

在这里插入图片描述

3.vue-resource

4.slot插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是组件间通信的方式,适用于父组件=>子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    • 默认插槽

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

      父组件中:
              <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>
      
      
    • 作用域插槽

      父组件中:
      		<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>
      
      

1.默认插槽

App.vue

<template>
	<div class="container">
		<Category title="美食" >
			<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
		</Category>

		<Category title="游戏" >
			<ul>
				<li v-for="(g,index) in games" :key="index">{{g}}</li>
			</ul>
		</Category>

		<Category title="电影">
			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
		</Category>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name:'App',
		components:{Category},
		data() {
			return {
				games:['植物大战僵尸','红色警戒','空洞骑士','王国']
			}
		},
	}
</script>

<style scoped>
	.container{
		display: flex;
		justify-content: space-around;
	}
</style>

Category.vue

<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
		<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title']
	}
</script>

<style scoped>
	.category{
		background-color: skyblue;
		width: 200px;
		height: 300px;
	}
	h3{
		text-align: center;
		background-color: orange;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
</style>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44bDBcVd-1686448066086)(../%E5%9B%BE%E7%89%87/image-20230609135149395.png)]

2.具名插槽

App.vue

<template>
	<div class="container">
		<Category title="美食" >
			<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
      <a slot="footer" href="http://www.atguigu.com">更多美食</a>
		</Category>

		<Category title="游戏" >
			<ul slot="center">
				<li v-for="(g,index) in games" :key="index">{{g}}</li>
			</ul>
      <div class="foot" slot="footer">
        <a href="http://www.atguigu.com">单机游戏</a>
        <a href="http://www.atguigu.com">网络游戏</a>
      </div>

		</Category>

		<Category title="电影">
			<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      <template  v-slot:footer>
          <div class="foot">
            <a href="http://www.atguigu.com">经典</a>
            <a href="http://www.atguigu.com">热门</a>
            <a href="http://www.atguigu.com">推荐</a>
          </div>
          <h4>欢迎前来观影</h4>
      </template>

		</Category>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name:'App',
		components:{Category},
		data() {
			return {
				games:['植物大战僵尸','红色警戒','空洞骑士','王国']
			}
		},
	}
</script>

<style scoped>
	.container,.foot {
		display: flex;
		justify-content: space-around;
	}

  h4 {
    text-align: center;
  }


</style>

Category.vue

<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
		<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
		<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title']
	}
</script>

<style scoped>
	.category{
		background-color: skyblue;
		width: 200px;
		height: 300px;
	}
	h3{
		text-align: center;
		background-color: orange;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
</style>

在这里插入图片描述

3.作用域插槽

App.vue

<template>
	<div class="container">
		<Category title="游戏" >
			<template scope="jojo">
				<ul>
					<li v-for="(g,index) in jojo.games" :key="index">{{g}}</li>
				</ul>
			</template>
		</Category>

		<Category title="游戏" >
			<template scope="jojo">
				<ol>
					<li v-for="(g,index) in jojo.games" :key="index">{{g}}</li>
				</ol>
			</template>
		</Category>

		<Category title="游戏" >
			<template scope="jojo">
				<h4 v-for="(g,index) in jojo.games" :key="index">{{g}}</h4>
			</template>
		</Category>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name:'App',
		components:{Category}
	}
</script>

<style>
	.container,.foot{
		display: flex;
		justify-content: space-around;
	}
	h4{
		text-align: center;
	}
</style>

Category.vue

<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
		<slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
        data() {
			return {
				games:['植物大战僵尸','红色警戒','空洞骑士','王国']
			}
		},
	}
</script>

<style scoped>
	.category{
		background-color: skyblue;
		width: 200px;
		height: 300px;
	}
	h3{
		text-align: center;
		background-color: orange;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
</style>

Vuex

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

  2. 何时使用

    • 多个组件依赖于同一个状态
    • 来自不同组件的行为需要变更同一状态

1.搭建vuex环境

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

    //该文件用于创建Vuex中最为核心的store
    //引入vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from '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
    })
    

2.基本使用

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

    //该文件用于创建Vuex中最为核心的store
    //引入Vuex
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    
    //准备actions——用于响应组件中的动作
    const actions = {
        jia(context,value){
            context.commit('JIA',value)
        },
        jian(context,value){
            context.commit('JIAN',value)
        },
        jiaOdd(context,value){
            if(context.state.sum%2){
                context.commit('JIA',value)
            }
        },
        jiaWait(context,value){
            setTimeout(()=>{
                context.commit('JIA',value)
    
            },500)
        }
    }
    
    //准备Mutations——用于操纵数据(state)
    const mutations = {
        JIA(state,value){
            state.sum += value
        },
        JIAN(state,value){
            state.sum -= value
        }
    }
    
    //准备state——用于存储函数
    const state = {
        sum:0,//当前和
    }
    
    
    
    
    //创建store
    export default new Vuex.Store({
        actions,
        mutations,
        state,
    })
    
    //导出/暴露store
    
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

3.getter配置项

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工

  2. 在index.js中追加getter配置

    //该文件用于创建Vuex中最为核心的store
    //引入Vuex
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    
    //准备actions——用于响应组件中的动作
    const actions = {
        jia(context,value){
            context.commit('JIA',value)
        },
        jian(context,value){
            context.commit('JIAN',value)
        },
        jiaOdd(context,value){
            if(context.state.sum%2){
                context.commit('JIA',value)
            }
        },
        jiaWait(context,value){
            setTimeout(()=>{
                context.commit('JIA',value)
    
            },500)
        }
    }
    
    //准备Mutations——用于操纵数据(state)
    const mutations = {
        JIA(state,value){
            state.sum += value
        },
        JIAN(state,value){
            state.sum -= value
        }
    }
    
    //准备state——用于存储函数
    const state = {
        sum:0,//当前和
    }
    
    //准备getters——用于将state中的数据进行加工
    const getters = {
        bigSum(state){
            return state.sum*10
        }
    }
    
    
    
    
    //创建store
    export default new Vuex.Store({
        actions,
        mutations,
        state,
        getters
    })
    
    //导出/暴露store
    
    
  3. 组件中读取数据:$store.getter.bigSum

4.四个mapper方法

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

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

            computed:{
    
                // bigSum(){
                //     return $store.getters.bigSum
                // },
          
    
                //借助mapGetters生成计算属性,从getters中读取数据(对象写法)
                ...mapGetters({bigSum:'bigSum'}),
    
                //借助mapGetters生成计算属性,从getters中读取数据(数组写法)
                ...mapGetters({bigSum:'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)函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    
    
  5. 备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象

index.js

//该文件用于创建Vuex中最为核心的store
//引入Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)


//准备actions——用于响应组件中的动作
const actions = {
    jia(context,value){
        context.commit('JIA',value)
    },
    jian(context,value){
        context.commit('JIAN',value)
    },
    jiaOdd(context,value){
        if(context.state.sum%2){
            context.commit('JIA',value)
        }
    },
    jiaWait(context,value){
        setTimeout(()=>{
            context.commit('JIA',value)

        },500)
    }
}

//准备Mutations——用于操纵数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}

//准备state——用于存储函数
const state = {
    sum:0,//当前和
    school:'尚硅谷',
    subject:'前端'
}

//准备getters——用于将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}




//创建store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

//导出/暴露store

Count.vue

<template>
<div>
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和放大10倍后为:{{ bigSum }}</h3>
    <h3>我在{{ school }},学习{{ subject }}</h3>
    <select v-model.number="n">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
</div>
</template>


<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
    export default {
        name:'Count',
        data(){
            return {
                n:1,//用户选择数字


            }
        },
        methods:{
            // increment(){
            //     this.$store.commit('JIA',this.n)
            // },
            // decrement(){
            //     this.$store.commit('JIAN',this.n)
                
            // },

            //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
            ...mapMutations({increment:'JIA',decrement:'JIAN'}),

            //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
            // ...mapMutations(['JIA','JIAN']),



            // incrementOdd(){
               
            //         this.$store.dispatch('jiaOdd',this.n)
                
            // },
            // incrementWait(){
                
            //         this.$store.dispatch('jiaWait',this.n)

            // },
            
            ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
        
        },
        computed:{
            // sum(){
            //     return $store.state.sum
            // },
            // bigSum(){
            //     return $store.getters.bigSum
            // },
            // school(){
            //     return $store.state.school
            // },
            // subject(){
            //     return $store.state.subject
            // },

            //借助mapState生成计算属性,从state中读取数据(对象写法)
            ...mapState({sum:'sum',school:'school',subject:'subject'}),

            //借助mapState生成计算属性,从state中读取数据(数组写法)
            //...mapState(['sum','school','subject']),

            //借助mapGetters生成计算属性,从getters中读取数据(对象写法)
            ...mapGetters({bigSum:'bigSum'}),

            //借助mapGetters生成计算属性,从getters中读取数据(数组写法)
            ...mapGetters({bigSum:'bigSum'})
        }
    }
</script>

<style lang="css">
    button {
        margin-left: 5px;
    }
</style>

5.多组件共享数据

index.js

//该文件用于创建Vuex中最为核心的store
//引入Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)


//准备actions——用于响应组件中的动作
const actions = {
    jia(context,value){
        context.commit('JIA',value)
    },
    jian(context,value){
        context.commit('JIAN',value)
    },
    jiaOdd(context,value){
        if(context.state.sum%2){
            context.commit('JIA',value)
        }
    },
    jiaWait(context,value){
        setTimeout(()=>{
            context.commit('JIA',value)

        },500)
    }
}

//准备Mutations——用于操纵数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    },
    ADD_PERSON(state,value){
        state.personList.unshift(value)
    }
}

//准备state——用于存储函数
const state = {
    sum:0,//当前和
    school:'尚硅谷',
    subject:'前端',
    personList:[
        {id:'001',name:'张三'}
    ]
}

//准备getters——用于将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}




//创建store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

//导出/暴露store

Count.vue

<template>
<div>
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和放大10倍后为:{{ bigSum }}</h3>
    <h3>我在{{ school }},学习{{ subject }}</h3>
    <h3 style="color: red;">下方组件的总人数是:{{ personList.length }}</h3>
    <select v-model.number="n">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
</div>
</template>


<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
    export default {
        name:'Count',
        data(){
            return {
                n:1,//用户选择数字


            }
        },
        methods:{
  


            //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
            ...mapMutations({increment:'JIA',decrement:'JIAN'}),

            //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
            // ...mapMutations(['JIA','JIAN']),



            ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
        
        },
        computed:{

            //借助mapState生成计算属性,从state中读取数据(对象写法)
            // ...mapState({sum:'sum',school:'school',subject:'subject'}),

            //借助mapState生成计算属性,从state中读取数据(数组写法)
            ...mapState(['sum','school','subject','personList']),

            //借助mapGetters生成计算属性,从getters中读取数据(数组写法)
            ...mapGetters({bigSum:'bigSum'})
        }
    }
</script>

<style lang="css">
    button {
        margin-left: 5px;
    }
</style>

Person.vue

<template>
    <div>
        <h1>人员列表</h1>
        <h3 style="color: red;">上方组件的求和为:{{ sum }}</h3>
        <input type="text" placeholder="请输入名字" v-model="name">
        <button @click="add">添加</button>
        <ul>
            <li v-for="p in personList" :key="p.id">{{ p.name }}</li>
        </ul>
    </div>
</template>


<script>
import { mapState } from 'vuex';
import {nanoid} from 'nanoid'
    export default {
        name:'Person',
        data(){
            return {
                name:''
            }
        },
        computed:{
            // personList(){
            //     return this.$store.state.personList
            // }
                ...mapState(['personList']),
                sum(){
                return this.$store.state.sum
            }
        },
        methods:{
            add(){
                const obj = {id:nanoid,name:this.name}
                this.$store.commit('ADD_PERSON',obj)
                this.name = ''
            }
        }
    }

</script>

6.模块化+命名控件

  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']),
    
    
  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'})
    
    
  6. 开启命名空间后,组件中调用commit

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

src/store/index.js

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引入count
import countOptions from './count'
//引入person
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
   
//创建并暴露store
export default new Vuex.Store({
    modules:{
        countAbout:countOptions,
        personAbout:personOptions,
    }
})

src/store/count.js

export default{
    namespaced:true,
    actions:{
        addOdd(context,value){
            console.log("actions中的addOdd被调用了")
            if(context.state.sum % 2){
                context.commit('ADD',value)
            }
        },
        addWait(context,value){
            console.log("actions中的addWait被调用了")
            setTimeout(()=>{
                context.commit('ADD',value)
            },500)
        }
    },
    mutations:{
        ADD(state,value){
            state.sum += value
        },
        SUBTRACT(state,value){
            state.sum -= value
        }
    },
    state:{
        sum:0, //当前的和
        name:'JOJO',
        school:'尚硅谷',
    },
    getters:{
        bigSum(state){
            return state.sum * 10
        }
    }
}

src/store/person.js

import axios from "axios"
import { nanoid } from "nanoid"

export default{
    namespaced:true,
    actions:{
        addPersonWang(context,value){
            if(value.name.indexOf('王') === 0){
                context.commit('ADD_PERSON',value)
            }else{
                alert('添加的人必须姓王!')
            }
        },
        addPersonServer(context){
            axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(
                response => {
                    context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
                },
                error => {
                    alert(error.message)
                }
            )
        }
    },
    mutations:{
        ADD_PERSON(state,value){
            console.log('mutations中的ADD_PERSON被调用了')
            state.personList.unshift(value)
        }
    },
    state:{
        personList:[
            {id:'001',name:'JOJO'}
        ]
    },
    getters:{
        firstPersonName(state){
            return state.personList[0].name
        }
    }
}

count.vue

<template>
	<div>
		<h1>当前求和为:{{sum}}</h1>
		<h3>当前求和的10倍为:{{bigSum}}</h3>
		<h3>我是{{name}},我在{{school}}学习</h3>
		<h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
		<select v-model.number="n">
			<option value="1">1</option>
			<option value="2">2</option>
			<option value="3">3</option>
		</select>
		<button @click="increment(n)">+</button>
		<button @click="decrement(n)">-</button>
		<button @click="incrementOdd(n)">当前求和为奇数再加</button>
		<button @click="incrementWait(n)">等一等再加</button>
	</div>
</template>

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

	export default {
		name:'Count',
		data() {
			return {
				n:1, //用户选择的数字
			}
		},
		methods: {
			...mapMutations('countAbout',{increment:'ADD',decrement:'SUBTRACT'}),
			...mapActions('countAbout',{incrementOdd:'addOdd',incrementWait:'addWait'})
		},
		computed:{
			...mapState('countAbout',['sum','school','name']),
			...mapGetters('countAbout',['bigSum']),
			...mapState('personAbout',['personList'])
		}
	}
</script>

<style>
	button{
		margin-left: 5px;
	}
</style>

person.vue

<template>
	<div>
		<h1>人员列表</h1>
		<h3 style="color:red">Count组件求和为:{{sum}}</h3>
        <h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
		<input type="text" placeholder="请输入名字" v-model="name">
		<button @click="add">添加</button>
        <button @click="addWang">添加一个姓王的人</button>
        <button @click="addPerson">随机添加一个人</button>
		<ul>
			<li v-for="p in personList" :key="p.id">{{p.name}}</li>
		</ul>
	</div>
</template>

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:'Person',
		data() {
			return {
				name:''
			}
		},
		computed:{
			personList(){
				return this.$store.state.personAbout.personList
			},
			sum(){
				return this.$store.state.countAbout.sum
			},
            firstPersonName(){
                return this.$store.getters['personAbout/firstPersonName']
            }
		},
		methods: {
			add(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.commit('personAbout/ADD_PERSON',personObj)
				this.name = ''
			},
            addWang(){
                const personObj = {id:nanoid(),name:this.name}
				this.$store.dispatch('personAbout/addPersonWang',personObj)
				this.name = ''   
            },
            addPerson(){
                this.$store.dispatch('personAbout/addPersonServer')
            }
		},
	}
</script>

Vue-router

1.简介

1.理解

vue的一个插件库,专门用来实现SPA应用

2.对SPA应用的理解

  1. 单页Web应用
  2. 整个应用只有一个完整的页面
  3. 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
  4. 数据需要通过ajax请求获取

2.路由

1.什么是路由

  1. 一个路由就是一组映射关系
  2. key为路径,value可能是function或component

2.路由分类

  1. 后端路由:
    1. 理解:value是function,用于处理客户端提交的请求
    2. 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
  2. 前端路由:
    1. 理解:value是component,用于展示页面内容
    2. 工作过程:当浏览器的路径改变时,对应的组件就会显示

3.基本路由

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.user(VueRouter)

  3. 编写router配置项

    // 该文件专门用于创建整个应用的路由器
    import VueRouter from 'vue-router'
    
    //引入组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建一个路由器并暴露
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    })
    
    
    
    
    
  4. 实现切换(active-class可配置高亮样式)

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

            <router-view></router-view>
    

4.注意点

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

5.嵌套路由

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

    // 该文件专门用于创建整个应用的路由器
    import VueRouter from 'vue-router'
    
    //引入组件
    import About from '../pages/About'
    import Home from '../pages/Home'
    import News from '../pages/News'
    import Message from '../pages/Message'
    
    
    //创建一个路由器并暴露
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home,
                children:[
                    {
                        path:'news',
                        component:News
                    },
                    {
                        path:'message',
                        component:Message
                    }
                ]
            }
    
    
    
        ]
    })
    
    
    
    
    
  2. 跳转(要写完整路径)

             <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
    

6.路由传参

  1. 传递参数

        <!-- 跳转路由并携带query参数,to的字符串写法 -->
                <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp; -->
               
                <!-- 跳转路由并携带query参数,to的对象写法 -->
                <router-link :to="{
                    path:'/home/message/detail',
                    query:{
                        id:m.id,
                        title:m.title
                    }
                }">
                    {{ m.title }}
                </router-link>
    
  2. 接受参数

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

7.命名路由

  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>
      
      

8.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
    

9.props配置

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

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

}

10.replace属性

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

11.编程式路由导航

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

  2. 具体编码:

    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() //可前进也可后退
    
    

12.缓存路由组件

  1. 作用:让步展示的路由组件保持挂载,不被销毁

  2. 具体编码:

      <keep-alive include="News">
          <router-view></router-view> 
      </keep-alive>
    
     <!-- 缓存多个路由组件 -->
                <keep-alive include="['News','Message']">
                        <router-view></router-view> 
                </keep-alive>
      
    

13.两个新生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
  2. 具体名字:
    • activated路由组件被激活时触发
    • deactivated路由组件失活时触发

14.路由守卫

  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('暂无权限查看')
    		}
    	}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(localStorage.getItem('school') === 'atguigu'){
            next()
        }else{
            alert('暂无权限查看')
        }
    }
    
    
  5. 组件内守卫

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

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

  1. 对于一个url来说,什么是hash值?——#及其后面的内容就是hash值

  2. hash值不会包含在HTTP请求中,即:hash值不会带给服务器

  3. hash模式:

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

    1. 地址干净
    2. 兼容性和hash模式相比略差
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404问题

class=“active” to=“/home/news”>News


## 6.路由传参

1. 传递参数

```vue
    <!-- 跳转路由并携带query参数,to的字符串写法 -->
            <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp; -->
           
            <!-- 跳转路由并携带query参数,to的对象写法 -->
            <router-link :to="{
                path:'/home/message/detail',
                query:{
                    id:m.id,
                    title:m.title
                }
            }">
                {{ m.title }}
            </router-link>
  1. 接受参数

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

7.命名路由

  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>
      
      

8.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
    

9.props配置

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

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

}

10.replace属性

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

11.编程式路由导航

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

  2. 具体编码:

    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() //可前进也可后退
    
    

12.缓存路由组件

  1. 作用:让步展示的路由组件保持挂载,不被销毁

  2. 具体编码:

      <keep-alive include="News">
          <router-view></router-view> 
      </keep-alive>
    
     <!-- 缓存多个路由组件 -->
                <keep-alive include="['News','Message']">
                        <router-view></router-view> 
                </keep-alive>
      
    

13.两个新生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
  2. 具体名字:
    • activated路由组件被激活时触发
    • deactivated路由组件失活时触发

14.路由守卫

  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('暂无权限查看')
    		}
    	}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(localStorage.getItem('school') === 'atguigu'){
            next()
        }else{
            alert('暂无权限查看')
        }
    }
    
    
  5. 组件内守卫

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

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

  1. 对于一个url来说,什么是hash值?——#及其后面的内容就是hash值

  2. hash值不会包含在HTTP请求中,即:hash值不会带给服务器

  3. hash模式:

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

    1. 地址干净
    2. 兼容性和hash模式相比略差
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404问题
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值