aoe1231

知之为知之,不知为不知

Vue 2 - 入门

1、Vue简介

1.1、Vue是什么

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

  • 渐进式:可以自底向上逐层的应用。
  • 简单应用:只需一个轻量小巧的核心库;复杂应用:可以引入各式各样的Vue插件。

1.2、谁开发的

尤雨溪。

  • 2013年:受到Angular框架的启发,尤雨溪开发出了一个轻量框架Seed。同年12月,Seed更名为Vue,版本号0.6.0;
  • 2014年:Vue正式对外发布,版本号0.8.0。Taylor otwell 在 Twitter 上发表动态,说自己正在学习Vue.js;
  • 2015年10月27日:正式发布Vue1.0.0 Evangelion(新世纪福音战士);
  • 2016年10月1日:正式发布Vue2.0.0 Ghost in the hell(攻壳机动队);
  • 2020年9月18日,正式发布Vue3.0.0 One Piece(海贼王)。

1.3、Vue的特点

采用组件化的方式,提高代码复用率,让代码更好维护;

声明式编码,让编码人员无需直接操作DOM,提高开发效率;

1.4、Vue引入

可使用 <script type="text/javascript" src="../js/vue.js"></script>

关闭Vue开发环境提示:

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

1.5、Vue小案例

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

<!-- 准备好一个容器 -->
<div id="root">
    <h1>Hello world</h1>
    <h1>年龄:{{name}}</h1>
</div>

<script type="text/javascript" >
    // 创建Vue实例
    const x = new Vue({
        el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
        // 或者为 el:document.getElementById('root')
    
        // data 中用于存储数据,数据提供el所指定的容器使用,值我们暂时先写成一个对象
        data:{
            name:'张三'
        }
    })
</script>

2、模板语法

<div id="root">
    <h1>插值语法</h1>
    <h3>你好,{{name}}</h3>
    <hr/>
    <h1>指令语法</h1>
    <a v-bind:href="school.url"}>点我去尚硅谷学习1</a> // 数据绑定方式1
    <a :href="school.url"}>点我去尚硅谷学习2</a> // 数据绑定方式2
</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>

2.1、插值语法

功能:用于解析标签体内容。

写法:{{xxx}},xxx是js表达式,且可以直接读取到daa中的所有属性;

2.2、指定语法

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件......)

写法:Vue中很多的指令,且形式都是 v-???,此处???可以是bind、if等等。

3、数据绑定

3.1、单向绑定 & 双向绑定

  • 单向绑定(v-bind):数据只能从data流向页面;
  • 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
<div id = "root">
    <!-- 普通写法 -->
    单向数据绑定:<input type="text" v-bind:value="name"><br/>
    双向数据绑定:<input type="text" v-model:value="name"><br/>
    
    <!-- 简单写法 -->
    单向数据绑定:<input type="text" :value="name"><br/>
    双向数据绑定:<input type="text" v-model="name"><br/>
    
    <!-- 如下代码是错误的,因为v-model只能应用在表单元素(输入类元素) -->
    <h2 v-module:x="name">你好啊</h2>
</div>
    

3.2、el 与 data 的两种写法

Vue.config.productionTip = false
const v = new Vue({
    //el:'#root' // el第一种写法
    
    // data的第一种写法
    data:{
        name:'尚硅谷'
    }
    
    // data的第二种写法
    data:function(){
        return {
            name:'尚硅谷'
        }
    }
})
console.log(v)
setTimeout(()=>{
    v.$mount('root'); // el第二种写法
}, 1000);

4、MVVM 模型

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

vm中所有的属性,最后都出现在了vm身上;vm身上所有的属性及vue原型上所有属性,在vue模板中都可以直接使用。

5、数据代理

5.1、Object.defineProperty() 方法

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

        <style>
        </style>
    </head>
    
    <body>
        <script>
            let person = {
                name: '张三', // 这里定义的属性是既能改也能删
                sex: '男',
            }

            let number = 18
            Object.defineProperty(person, 'age', {
                // 方式1:定义value
                value: number, 
                enumerable: true, // 控制属性是否可枚举,默认是false
                writable: true, // 控制属性是否可以被修改,默认值是false
                configurable: true, // 控制属性是否可以被删除,默认是false

                // 方式2:定义get/set
                // 当有人读取person的age属性时,get函数就会被调用,且返回值就是age的值
                get:function() {
                    console.log('有人读取age属性了')
                    return 'hello';
                },

                // 当有人修改person的age属性时,set函数就会被调用,且会收到修改的具体的值
                set:(value)=>{
                    console.log('有人修改了age属性')
                    number = value
                }

                // 方式1和方式2不同同时存在
            })

            console.log(person)

            // 遍历不到 age
            for(let index in person) {
                console.log(person[index])
            }

            console.log(person)
        </script>
    </body>
</html>

5.2、何为数据代理

Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)

Vue中数据代理的好处:更加方便地操作data中的数据。

基本原理:通过Object.defineProperty()把data对象中的所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。

        <!-- 数据代理:通过一个对象代理对另一个对象中属性的操作 (读写)-->
         <script>
            let obj = {x: 100}
            let obj2 = {y: 100}

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

                set(value){
                    obj.x = value;
                }
            })
         </script>

6、事件处理

6.1、事件的基本使用

事件的基本使用:

  • 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
  • 事件的回调需要配置在methods对象中,最终会在vm上;
  • methods中配置的函数,不要用箭头函数,否则this就不是vm了;
  • methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象;
  • @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参。
    <body>
        <div id="root">
            <h2>欢迎来到{{name}}学习</h2>
            <button v-on:click="showInfo1">点我提示信息(不传参)</button>
            <button @click="showInfo2($event, 66)">点我提示信息(传参)</button> <!-- 第二种写法 -->
        </div>
    </body>

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

        const vm = new Vue({
            el: '#root',
            // 只有在data里面才会进行数据代理
            data: {
                name: '尚硅谷',
            },
            methods: {
                showInfo1(event, a, b, c, d) {
                    console.log(event, a, b, c, d)
                    console.log(this === vm)
                    alert('同学你好1')
                },
                showInfo2(event, number) {
                    alert('同学你好2' + number)
                }
            }
        })
    </script>

6.2、事件修饰符

Vue中的事件修饰符:

  • prevent:阻止默认事件;
  • stop:阻止事件冒泡(常用);
  • once:事件只触发依次(常用);
  • capture:使用事件的捕获模式;
  • self:只有event.target是当前操作的元素才是触发事件;
  • passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=dege" />
        <meta name="viewpoint" content="width=device-width, initial-scale=1.0">
        <title>Title</title>

        <!-- 引入 Vue -->
         <script type="text/javascript" src="./js/vue.js"></script>

        <style>
            *{
                margin-top: 20px;
            }
            .demo1{
                height: 50px;
                background-color: skyblue;
            }
            .box1{
                padding: 5px;
                background-color: skyblue;
            }
            .box2{
                padding: 5px;
                background-color: orange;
            }
            .list{
                width: 200px;
                height: 20px;
                background-color: peru;
                overflow: auto;
            }
            .li{
                height: 100px;
            }
        </style>
    </head>
    
    <body>
        <!-- 准备号一个容器-->
        <div id="root">
            <h2>欢迎来到{{name}}学习</h2>
            <!-- 阻止默认行为:跳转到该网址-->
            <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
            <!-- 阻止事件冒泡-->
             <div class="demo1" @click="showInfo">
                <button @click.stop="showInfo">点我提示信息1</button>
             </div>
             <!-- 事件只触发一次-->
            <button @click.once="showInfo">点我提示信息2</button>
            <!-- 使用事件的捕获方式-->
            <div class="box1" @click.capture="showMsg(1)">
                div1
                <div class="box2" @click="showMsg(2)">
                    div2
                </div>
            </div>
            <!-- 只有event.target是当前操作的元素时才触发事件-->
             <div class="demo3" @click="showInfo">
                <button @click="showInfo">点我提示信息3</button>
             </div>
             <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
            <ul class="list" @scroll="demo">
                <li>1</li>
                <li>2</li>
                <li>3</li>
                <li>4</li>
            </ul>
        </div>
    </body>

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

        let vm = new Vue({
            el: '#root',
            data:{
                name: 'atguigu'
            },

            methods:{
                showInfo(e) {
                    // event.preventDefault()
                    // e.stopPropagation()
                    alert("同学你好")
                },
                showMsg(msg) {
                    alert(msg)
                },
                demo() {
                    console.log('@')
                }
            }
        })
    </script>
</html>

6.3、键盘事件

Vue中常见的按键别名:

  • 回车:enter
  • 删除:delete(捕获“删除”和“退格”键)
  • 退出:esc
  • 空格:space
  • 换行:tab(特殊,必须配合keydown去使用)
  • 上:up
  • 下:down
  • 左:left
  • 右:right

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

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

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

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

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

7、计算属性

计算属性:要用的属性不存在,要通过已有属性计算得来。

原理:底层借助了Object.defineProperty()方法提供的getter和setter。

get()函数什么时候执行:① 当初次读取时会执行一次;② 当依赖的数据发生改变时会被再次调用。

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

备注:① 计算属性最终会出现在vm上,直接读取使用即可;② 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生修改。

    <body>
        <div id="root">
            姓:<input type="text" v-model="firstName"><br/>
            名:<input type="text" v-model="lastName"><br/>
            全名:<span>{{firstName.slice(0, 3)}}-{{lastName}}</span>
            全名:<span>{{fullName()}}</span>
        </div>
    </body>
    
     <script type="text/javascript">
        Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示

        new Vue({
            el: '#root',
            // 普通属性
            data:{
                firstName: '张',
                lastName: '三'
            },
            // 计算属性
            computed:{
                // get作用:当读取fullName2时,get就会被调用,且返回值就作为fullName2的值
                // get在初次读取fullName2时被调用,在所依赖的数据发生变化时也会被调用
                fullName2: {
                    get() {
                        console.log('get被调用了')
                        return this.firstName +this.lastName; // this为vm
                    },
                    // 当fullName2被修改时被调用
                    set(value) {
                        console.log(value)
                        const arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            },
            methods:{
                showInfo(e) {
                    console.log(e.target.value)
                },
                fullName() {
                    return '小猪佩奇'
                }
            }
        })
     </script>

计算属性的简写:

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

        new Vue({
            el: '#root',
            // 普通属性
            data:{
                firstName: '张',
                lastName: '三'
            },
            // 计算属性
            computed:{
                // 计算属性的完整写法
                // // get作用:当读取fullName2时,get就会被调用,且返回值就作为fullName2的值
                // // get在初次读取fullName2时被调用,在所依赖的数据发生变化时也会被调用
                // fullName2: {
                //     get() {
                //         console.log('get被调用了')
                //         return this.firstName +this.lastName; // this为vm
                //     },
                //     // 当fullName2被修改时被调用
                //     set(value) {
                //         console.log(value)
                //         const arr = value.split('-')
                //         this.firstName = arr[0]
                //         this.lastName = arr[1]
                //     }
                // }

                // 计算属性的简写(适用于只获取值(get)的情况)
                fullName() {
                    return this.firstName + this.lastName;
                }
            },
            methods:{
                showInfo(e) {
                    console.log(e.target.value)
                },
                fullName() {
                    return '小猪佩奇'
                }
            }
        })
     </script>

8、监视属性

监视属性watch:

  • 当被监视的属性变化时,回调函数自动调用,进行相关操作;
  • 监视的属性必须存在,才能进行监视;
  • 监视属性有两种写法(下面案例有演示)。

深度监视:

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

备注:

  • Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以;
  • 使用watch时根据数据的具体结构,决定是否采用深度监视。

案例1:

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

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

        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;
                }
            },
            // 监视方式1
            watch:{
                isHot:{
                    immediate: true, // 初始化时让handler调用一下
                    // handler 在isHot发生改变时调用
                    handler(newValue, oldValue){
                        console.log('isHot修改了', newValue, oldValue)
                    }
                },
                info:{
                    handler(newValue, oldValue){
                        console.log('info修改了', newValue, oldValue)
                    }
                },
                // 深度监视:监视多级结构中某个属性的变化
                'numbers.a':{
                    handler(){
                        console.log('a被改变了')
                    }
                },
                // 监视多级结构中所有属性的变化
                numbers:{
                    deep: true, // 开启深度监视
                    handler(){
                    }
                },

                // 监视属性的简写形式(当只需要handler时可以)
                isHot(newValue, oldValue){
                    console.log('isHot被修改了3', newValue, oldValue)
                }
            }
        })

        // 监视方式2
        vm.$watch('isHot', {
            immediate: true, // 初始化时让handler调用一下
            // handler 在isHot发生改变时调用
            handler(newValue, oldValue){
                console.log('isHot修改了2', newValue, oldValue)
            }
        })

        // 监视属性的简写形式(不能是箭头函数)
        vm.$watch('isHot', function(newValue, oldValue){
            console.log('isHot修改了4', newValue, oldValue)
        })
    </script>

computed 和 watch 之间的区别:

  • computed 能完成的功能,watch 都可以完成;
  • watch 能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

两个重要的小原则:

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

9、class 与 style 绑定

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=dege" />
        <meta name="viewpoint" content="width=device-width, initial-scale=1.0">
        <title>绑定样式</title>

        <!-- 引入 Vue -->
        <script type="text/javascript" src="./js/vue.js"></script>
        <style>
            .basic{}
            .happy{}
            .sad{}
            .normal{}
            .atguigu1{}
            .atguigu2{}
            .atguigu3{}
        </style>
    </head>
    
    <body>
        <div id="root">
            <!-- 绑定class样式的字符串写法,适用于样式的类名不确定,需要动态指定-->
            <div class="basic" :class="mood" @click="changeMood">{{name}}</div>

            <!-- 绑定class样式的数组写法,适用于绑定的样式个数不确定,名字也不确定-->
            <div class="basic" :class="classArr">{{name}}</div> <!-- 方式1-->
            <div class="basic" :class="['atguigu1', 'atguigu2', 'atguigu3']">{{name}}</div> <!-- 方式2-->

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

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

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

        const vm = new Vue({
            el: '#root',
            data:{
                name: '尚硅谷',
                mood: 'normal', // 常规样式的名称
                classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
                classObj:{
                    atguigu1: false,
                    atguigu2: true
                },
                styleObj:{
                    fontSize: '40px'
                },
                styleObj2:{
                    fontSize: '40px'
                }
            },
            methods:{
                changeMood(){
                    const moods = ['happy', 'sad', 'normal']
                    const index = Math.floor(Math.random() * 3);
                    this.mood = moods[index];
                }
            }
        })
    </script>
</html>

10、条件渲染

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

        <!-- 引入 Vue -->
        <script type="text/javascript" src="./js/vue.js"></script>

        <style>
        </style>
    </head>
    
    <body>
        <div id="root">
            <!-- 使用v-show做条件渲染(适用于切换频率高场景),不展示的DOM元素不会被移除,而是被隐藏掉-->
            <h2 v-show="a">欢迎来到尚硅谷</h2>
            <h2 v-show="1 === 1">欢迎来到尚硅谷</h2>

            <!-- 使用v-if做条件渲染(适用于切换频率低场景,不展示的DOM元素直接被移除)-->
             <h2 v-if="false">欢迎来到{{name}}</h2>
             <h2 v-if="1 === 1">欢迎来到{{name}}</h2>
             
            <h2>当前的n值是:{{n}}</h2>
            <button @click="n++">点我n+1</button>

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

            <!-- 结构是一个整体,不能被打断 -->
            <div v-if="n === 1">Angular</div>
            <div v-else-if="n === 2">React</div>
            <div v-else-if="n === 3">Vue</div>
            <div v-else>XXXXX</div>

            <!-- 模板只能和v-if配合使用,template在解析之后会消失-->
            <template v-if="n === 1">
                <h2 v-show="n === 1">你好</h2>
                <h2 v-show="n === 1">atguigu</h2>
                <h2 v-show="n === 1">北京</h2>
            </template>

            <!-- 使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到-->
        </div>
    </body>

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

        const vm = new Vue({
            el: '#root',
            data:{
                name: 'atguigu',
                a: false,
                n: 0
            }
        })
    </script>
</html>

11、列表渲染

react、vue中的key有什么作用(key的内部原理):

  • 虚拟DOM中key的作用:key是虚拟DOM对象的标记,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】。
  • 对比规则:① 旧虚拟DOM中内容没变,直接使用之前的真实DOM;若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。② 旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到页面。
  • 用index作为key可能会引发的问题:① 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新,界面效果没问题,但效率低;② 如果结构中还包含输入类的DOM,会产生错误的DOM更新,界面有问题。
  • 开发中如何选择key:① 最好使用每条数据的唯一表示作为key,比如id,手机号,身份证号等唯一值;② 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

正确方式:

代码示例:

    <body>
        <div id="root">
            <h2>遍历数组-人员列表</h2>
            <ul>
                <!-- v-for 用于遍历 -->
                <li v-for="p in persons" :key="p.id">
                    {{p.name}}-{{p.age}}
                </li>
            </ul>
            <button @click.once="add">添加一个老刘</button>

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

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

            <h2>遍历指定次数</h2>
            <ul>
                <li v-for="(number, index) of 5">
                    {{number}}-{{index}}
                </li>
            </ul>
        </div>
    </body>

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

        new Vue({
            el: '#root',
            data:{
                persons:[
                    {id:'001', name:'张三', age:18},
                    {id:'002', name:'李四', age:19},
                    {id:'003', name:'王五', age:20}
                ],
                car: {
                    name: '奥迪A8',
                    price: 20,
                },
                str: 'hello'
            },
            methods:{
                add(){
                    const p = {id:'004', name:'老刘', age:40}
                    this.persons.unshift(p)
                }
            }
        })
    </script>

12、列表过滤

    <body>
        <div id="root">
            <h2>人员列表</h2>
            <input type="text" placeholder="请输入名字" v-model="keyword">
            <ul>
                <li v-for="(p, index) of filPersons" :key="index">
                    {{p.name}}-{{p.age}}-{{p.sex}}
                </li>
            </ul>
        </div>
    </body>

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

        // 监视实现
        // new Vue({
        //     el: '#root',
        //     data:{
        //         persons:[
        //             {id:'001', name:'马冬梅', age:18, sex:'女'},
        //             {id:'002', name:'周冬雨', age:19, sex:'女'},
        //             {id:'003', name:'周杰伦', age:20, sex:'男'},
        //             {id:'003', name:'温兆伦', age:20, sex:'男'},
        //         ],
        //         filPersons:[],
        //         keyword: ""
        //     },
        //     watch:{
        //         keyword:{
        //             immediate: true, 
        //             handler(val){
        //                 // filter 会返回新数组
        //                 this.filPersons = this.persons.filter((p)=>{
        //                     return p.name.indexOf(val) !== -1
        //                 })
        //             }
        //         }
        //     },
        // })

        // 计算属性实现
        new Vue({
            el: '#root',
            data:{
                persons:[
                    {id:'001', name:'马冬梅', age:18, sex:'女'},
                    {id:'002', name:'周冬雨', age:19, sex:'女'},
                    {id:'003', name:'周杰伦', age:20, sex:'男'},
                    {id:'003', name:'温兆伦', age:20, sex:'男'},
                ],
                keyword: ""
            },
            computed: {
                filPersons(){
                    this.persons.filter((p)=>{
                        return p.name.indexOf(this.keyword) !== -1
                    })
                }
            }
        })
    </script>

13、列表排序

    <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) of filPerson" :key="p.id">
                    {{p.name}}-{{p.age}}-{{p.sex}}
                </li>
            </ul>
        </div>
    </body>

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

        new Vue({
            el: '#root',
            data:{
                keyword: '',
                sortType: 0, // 0原顺序,1降序,2升序
                persons:[
                    {id:'001', name:'马冬梅', age:19, sex:'女'},
                    {id:'002', name:'周冬雨', age:20, sex:'女'},
                    {id:'003', name:'周杰伦', age:21, sex:'男'},
                    {id:'004', name:'温兆伦', age:22, sex:'男'},
                ],
            },
            computed:{
                filPerson(){
                    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>

14、Vue监测数据的原理

Vue会监视data中所有层次的数据。

如何监测对象中的数据:通过setter实现监视,且要在nuew Vue时就传入要监测的数据。

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

如何监测数组中的数据:通过包裹数组更新元素的方法实现,本质就是做了两件事:

  • 调用原生对应的方法对数组进行更新;
  • 重新解析模板,进而更新页面。

在Vue修改数组中的某个元素一定要用如下方法:

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

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

模拟数据监测:

    <script type="text/javascript">
        // 模拟数据监测
        let data = {
            name: '尚硅谷',
            address: '北京'
        }

        // 创建一个监视的实例对象,用于监视data中属性的变化
        const obs = new Observer(data)
        console.log(obs)

        // 准备一个vm实例对象
        let vm = {}
        vm._data = data = obs

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

15、收集表单数据

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

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

<input type="checkbox"/>

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

备注:v-model的3个修饰符:① lazy:失去焦点再收集数据;② number:输入字符串转为有效的数字;③ trim:输入首尾空格过滤。

    <body>
        <div id="root">
            <!-- 表单提交的默认行为是跳转-->
            <form @submit.prevent="demo">
                <!-- trim 修饰表示去除前后的空格-->
                账号:<input type="text" v-model.trim="userInfo.account"><br/>
                密码:<input type="password" v-model="userInfo.password"><br/>
                <!-- 加number是说明为数字-->
                年龄:<input type="number" v-model.number="userInfo.age"><br/>
                性别:
                男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
                女<input type="redio" name="sex" v-model="userInfo.sex" value="female"><br/>
                爱好:
                学习<input type="checkbox" v-model="userInfo.hobby" value="study">
                打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
                吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
                <br/>
                所属校区
                <select v-model="userInfo.city">
                    <option value="">请选择校区</option>
                    <option value="beigjing">北京</option>
                    <option value="shanghai">上海</option>
                    <option value="shenzhen">深圳</option>
                    <option value="wuhan">武汉</option>
                </select>
                <br/>
                其他信息:
                <!-- lazy 修饰符表示失去焦点才会更新-->
                <textarea v-model.lazy="userInfo.other"></textarea><br/>
                <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
                <button>提交</button>
            </form>
        </div>
    </body>

    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data:{
                userInfo:{
                    account: '',
                    password: '',
                    age: '',
                    sex: 'female',
                    hobby: [],
                    city: '',
                    agree: ''
                }
            },
            methods:{
                demo(){
                    alert('表单提交了' + JSON.stringify(this.userInfo))
                }
            }
        })
    </script>

16、过滤器

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

语法:

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

备注:

  • 过滤器也可以接收额外参数,多个过滤器也可以串联;
  • 并没有改变原本的数据,是产生新的对应的数据。

17、内置指令

  • v-bind:单向绑定解析表达式,可简写为:xxx;
  • v-model:双向数据绑定;
  • v-for:遍历数组/对象/字符串;
  • v-on:绑定事件监听,可简写为@;
  • v-if:条件渲染(动态控制节点是否存在);
  • v-else:条件渲染(动态控制节点是否存在);
  • v-show:条件渲染(动态控制节点是否存在);
  • v-text:向其所在的节点中渲染文本内容,与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}则不会。
  • v-html:向指定节点中渲染包含html结构的内容。与插值语法的区别:① v-html会替换掉节点中所有内容,{{xx}}则不会;② v-html可以识别html结构。注意v-html有安全性问题,在网站上动态渲染任意htl是非常危险的,容易导致XSS攻击,一定要在可信的内容上使用v-html,永不要用在用户提交的内容上。
  • v-cloak:没有值。本质上是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。使用css配合v-cloak可以解决网速慢时页面展现出{{xxx}}的问题。
  • v-once:没有值。v-once所在节点在初次动态渲染后,就视为静态内容了。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
  • v-pre:没有值。可以跳过其所在节点的编译过程,可利用它跳过没有使用指令语法、没有使用插值语法的节点,会加快编译。

18、自定义指令

定义语法:

  • 局部指令:
    new Vue({
        directives:{指令名:配置对象}
    })
    
    或
    
    new Vue({
        directives(){指令名, 回调函数}
    })
  • 全局指令:
    Vue.directive(指令名, 配置对象)
    
    或
    
    Vue.directive(指令名, 回调函数)

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

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

备注:

  • 指令定义时不加v-,但使用时要加v-;
  • 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
    <!-- 需求:
        1、定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍;
        2、定义一个v-fbind,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点 
    -->
    <body>
        <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>

        <div id="root2">
            <input type="text" v-fbind:value="x">
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false
        Vue.directives('fbind', {
            sth(){
                // todo
            }
        })


        new Vue({
            el: '#root',
            data:{
                n: 1
            },
            directives:{
                // 表示在element元素上的v指令,并且绑定了binding值
                // 调用时机:1、指令与元素成功绑定时(一上来);2、指令所用到的数据发生更新时(实际是指令所在的模板被重新解析时)
                big(element, binding){
                    console.log(this) // 注意此处的this为window
                    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()
                    }
                }
            }
        })

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

19、生命周期

生命周期又称生命周期回调函数、生命周期函数、生命周期钩子。它是Vue在关键时刻帮我们调用的一些特殊名称的函数。生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。生命周期函数中的this指向是vm或组件实例对象。

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

关于销毁Vue实例:

  • 销毁后借助Vue开发者工具看不到任何信息;
  • 销毁后自定义事件会失效,但原生DOM事件依然有效;
  • 一般不会再把beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。
    <body>
        <div id="root">
            <h2 v-if="a">你好啊</h2>
            <h2 :style="{opacity}">欢迎学习Vue</h2>

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

            <button @click="bye">点我销毁vm</button>
        </div>
    </body>

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

        const vm = new Vue({
            el: '#root',
            // template:`
            //     <div>
            //         <h2>当前的n值是:{{n}}</h2>
            //         <button @click="add">点我n+1</button>    
            //     </div>
            // `,
            data:{
                a: true,
                opacity: 1,
                n: 0,

                id: 0
            },
            methods:{
                add(){
                    this.n++
                },
                stop(){
                    clearInterval(this.id)
                },
                bye(){
                    console.log('bye')
                    // this.$destroy()
                }
            },

            // Vue 生命周期
            // 0:new Vue() :创建Vue
            // 1、初始化生命周期、事件,但数据代理还未开始
            // 2、执行钩子
            beforeCreate(){
                console.log('beforeCreate')
                console.log(this) // 是vm,初始化生命周期、事件,但数据代理还未开始
                // debugger; // 卡一个断点
            },
            // 3、初始化数据监测、数据代理
            // 4、执行钩子
            created(){
                console.log('created')
                console.log(this) // 是vm
            },
            // 5、开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容
            // 6、执行钩子
            beforeMount(){
                console.log('beforeMount')
                console.log(this) // 是vm,此时页面呈现的是未经vue编译的DOM,所有对DOM的操作都不奏效
            },
            // 7、将内存中的虚拟DOM转为真实DOM呈现到页面
            // 8、执行钩子:Vue完成模板的解析并把初始的真实的DOM元素放入页面后(挂载完毕)调用mounted,只会调用一次
            mounted(){
                console.log(this) // 是vm,此时页面中呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)
                // 至此初始化过程结束,一般在此进行:开启定时器;发送网络请求;订阅消息;绑定自定义事件等初始化操作
                this.id = setInterval(()=>{
                    this.opacity -= 0.01
                    if (vm.opacity <= 0) {
                        vm.opacity = 1
                    }
                }, 16)
            },
            // 9、初始化结束,开始更新流程。
            // 10、当数据发生改变时触发该钩子,此时数据是最新的,但页面是旧的,未保持同步
            beforeUpdate(){
                console.log('beforeUpdate')
            },
            // 11、根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新,即完成了Model->View的更新
            // 12、执行钩子
            updated(){
                // 此时,数据是新的,页面也是新的,保持了同步
            },
            // 13、如果不销毁,则回到第10步,不断更新,如果触发销毁,则往后进行
            // 14。执行钩子
            beforeDestroy(){
                // 此时,vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:
                // 关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
                console.log('beforeDestroy')
                this.stop() // 防止推出前没有停止定时器
            },
            // 15、执行销毁工作
            // 16、执行钩子
            destroyed(){
                console.log('destroyed')
            }
        })

    </script>

20、Vue 组件编程

20.1、模块与组件、模块化与组件化

模块:向外提供特定功能的js程序,一般就是一个js文件。作用是复用js,简化js的编写,提高js运行效率。

组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image)。作用是复用编码,简化项目编码,提高运行效率。

模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化的应用。

组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。

20.2、非单文件组件

非单文件组件:一个文件中包含有n个组件。

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

  • 定义组件(创建组件);
  • 注册组件;
  • 使用组件(写组件标签)。

如何定义一个组件:使用Vue.extend(options)创建,其中options和new Vue(options)时传入的哪个options几乎一样,但区别如下:

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

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

如何注册组件:

  • 局部注册:靠new Vue的时候传入components选项;
  • 全局注册:靠Vue.component('组件名', 组件)

编写组件标签:<school></school>

关于组件名:

  • 一个单词组成:① 首字母小写:school;② 首字母大写:School。
  • 多个单词组成:① kebab-case命名:my-school;② CamelCase命名:MySchool(需要Vue脚手架支持)。

注意:

  • 组件名尽可能回避HTML中已有的元素名称,如h2、H2都不行;
  • 可以使用name配置项指定组件在开发者工具中呈现的名字。

关于组件标签写法:① <school></school> ② <school/>

备注:不使用脚手架时,<school/>会导致后续组件不能渲染。

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

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

            <!-- 编写组件标签-->
            <school></school>
            <hr>
         </div>

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

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

        // 创建局部student组件(简写形式)
        const studentComp = {
            template:`
                <div>
                    <h2>学生姓名:{{studentName}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
            // 一定不要写el配置项,因为最终所有的组件都要被一个vm管理
            data(){
                return {
                    studentName: '张三',
                    age: 18
                }
            }
        }

        // 创建局部school组件
        const schoolComp = Vue.extend({
            template:`
                <div>
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click='showName'>点我提示学校名</button>
                    <student></student>
                </div>
            `,
            // 一定不要写el配置项,因为最终所有的组件都要被一个vm管理
            data(){
                return {
                    schoolName: '尚硅谷',
                    address: '北京昌平',
                }
            },
            methods:{
                showName(){
                    alert(this.schoolName)
                }
            },
            components:{
                student: studentComp
            }
        })

        // 创建全局的组件
        const helloComp = Vue.extend({
            name: 'hello', // 开发者提示工具中的名称
            template:`
                <div?
                    <h2>你好 {{name}}</h2>
                </div>
            `,
            data(){
                return {
                    name: 'Tom'
                }
            }
        })
        const hello = Vue.component('hello', helloComp) // 注册全局组件,所有的vm都能使用

        // 创建vm
        const vm = new Vue({
            el: '#root',
            data: {
                msg: '你好'
            },
            // 注册组件(局部注册)
            components:{
                school: schoolComp,
            }
        })

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

20.3、单文件组件

关于VueComponent:

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的;
  • 我们只需要写<school>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options);
  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent;
  • 关于this指向:① 组件配置中,data函数、methods中的韩素华、watch中的函数、computed中的函数,它们的this均是VueComponent实例对象;② new Vue()配置中,data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是Vue实例对象。
  • VueComponent的实例对象,以后简称vc(也可称之为组件实例对象)。Vue的实例对象,以后简称vm。

单文件组件:一个文件中只包含1个组件。

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

模块化拆分版本:

index.html:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Title</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>

main.js:
import App from './App.vue'

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

App.vue:
<template>
  <div>
    <School></School>
    <Student></Student>
  </div>
</template>

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

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

School.Vue:
<template>
    <div class="demo">
        <h2>学校名称:{{ schoolName }}</h2>
        <h2>学校地址:{{ address }}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    // export const school = Vue.extend({})
    // 简写:
    export default {
        name: 'School', // 与文件名保持一致
        data(){
            return {
                schoolName: '尚硅谷',
                address: '北京昌平'
            }
        },
        methods:{
            showName(){
                alert(this.schoolName)
            }
        }
    }
</script>

<style>
    .demo{
        background-color: orange;
    }
</style>

Student.vue:
<template>
    <div class="demo">
        <h2>学生名称:{{name}}</h2>
        <h2>年龄{{ age }}</h2>
        <button @click="showName">点我提示学生名</button>
    </div>
</template>

<script>
    // export const student = Vue.extend({})
    // 简写:
    export default {
        name: 'Student', // 与文件名保持一致
        data(){
            return {
                name: '张三',
                age: '18'
            }
        },
        methods:{
            showName(){
                alert(this.name)
            }
        }
    }
</script>

<style>
</style>

21、使用Vue脚手架

Vue脚手架是Vue官方提供的标准化开发工具。文档:https://cli.vuejs.org/zh/

21.1、具体步骤

以管理员身份执行以下步骤:

  • 第一步:仅第一次执行。全局安装@vue/cli:npm install -g @vue/cli
  • 第二步:切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  • 第三步:启动项目:npm run serve

备注:

  • 如出现下载缓慢请配置npm淘宝镜像:npm config set registry https://registry.npmmirror.com
  • Vue脚手架隐藏了所有webpack相关的配置,若想看具体的webpack配置,请执行:vue inspect > output.js

生成的项目的index.html:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高渲染级别渲染页面-->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!-- 配置页签图标 public目录中的资源需要使用 BASE_URL-->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 配置网页标题 -->
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <!-- 如果浏览器不支持js,则该标签中的元素就会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>

    <!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

生成的main.js:

/**
 * 该文件是整个项目的入口文件
 */
// 引入Vue
// import Vue from 'vue/dist/vue' // 引入完整版的vue
import Vue from 'vue' // 引入残缺版的vue(缺少模板解析器)
// 引入App组件,它是所有组件的父组件
import App from './App.vue'

Vue.config.productionTip = false

/**
 * 关于不同版本的Vue:
 *  ① vue.js 与 vue.runtime.xxx.js 的区别:
 *    1)vue.js是完整版的Vue,包含:核心功能+模板解析器;
 *    2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模板解析器
 *  ② 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
 */

// 创建Vue的实例对象
new Vue({
  // 下面这行代码完成了:将App组件放入容器中
  // render: h => h(App),
  render(createElement){ // createElement 是一个创建元素的函数
    return createElement('h1', '你好啊')
  }
}).$mount('#app') // $mount()作用是将vue挂在到app元素上,与el:'#app'相同

22、ref属性

ref属性:

  • 被用来给元素或子组件注册引用信息(id的替代者);
  • 应用在html标签上获取真实DOM元素,应用在组件是上是组件实例对象(vc);
  • 使用方式:① 打标识:<h1 ref='xx'></h1>;② 获取:this.$refs.xxx
<template>
    <div>
        <h1 v-test='msg' ref='title'></h1>
        <button @click='showDom'>点我输出上方的DOM元素</button>
        <School ref='sch'/>
        <School/>
        <School/>
    </div>
</template>

<script>
// 引入School组件
import School from './components/School'
export default {
    name: 'App',
    components:{School},
    data(){
        return {
            msg: '欢迎学习Vue'
        }
    },
    methods:{
        showDom(){
            console.log(this.$refs.title) // 获取到的是真实DOM元素
            console.log(this.$refs.sch) // 获取到的是组件实例对象
        }
    }
}
</script>

<style>

</style>

23、props配置

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

  • 传递数据:<Demo name='xxx' />
  • 接收数据:三种方式,下面会演示。

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

App.vue:

<template>
    <div>
        <Student name='李四' sex='女' :age='18'/>
        <br>
        <Student name='王五' sex='男' :age='19'/>
        <br>
    </div>
</template>

<script>
import Student from './components/Student.vue'
export default {
    name: 'App',
    components:{Student},
    data(){
        return {
            msg: '欢迎学习Vue'
        }
    },
}
</script>

<style>

</style>

Student.vue:

<template>
  <div>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <h2>学生年龄:{{myAge}}</h2>
    <button @click='updateAge'>点我修改年龄</button>
  </div>
</template>

<script>
export default {
    name: 'Student',
    data(){
        return {
            myAge: this.age // 因为props优先级更高
        }
    },
    methods:{
        updateAge(){
            this.myAge++
        }
    },

    // props中的属性从外部传入,且无法修改

    // props:['name', 'sex', 'age'] // 外部传入的属性(简单声明接收)

    // 
    // props:{
    //     name: String,
    //     age: Number,
    //     sex: String
    // }

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

<style>
    .student{
        background-color: aqua;
    }
</style>

24、mixin (混合/混入)属性

mixin(混合/混入):可以把多个组件共用的配置提取成一个混入对象。

使用方式:

  1. 定义混合:
    export const hunhe = {
        methods:{
            showName(){
                alert(this.name)
            }
        }
    }
  2. 使用混合:① 全局混合:Vue.mixin(xxx);② 局部混合:mixins:['xxx']

25、插件

插件功能:用于增强Vue。

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

定义插件(plugins.js):

export default {
    install(Vue, a){
        console.log('@@@install', Vue)

        // 全局过滤器
        Vue.filter('mySlice', function(value){
            return value.slice(0, 4)
        })

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

        // 定义混合
        Vue.mixin({
            data(){
                return {
                    x: 100,
                    y: 200
                }
            }
        })

        // 给原型上添加方法(vm和vc都能用了)
        Vue.prototype.hello = () => {alert('你好')}
    }
}

使用插件(main.js):

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins, 1) // 可以加自定义参数
new Vue({
    el: '#app',
    render: h => h(App)
})

School.vue:

<template>
  <div>
    <h2>学校名称:{{name | mySlice}}</h2>
    <h2>学校地址:{{address}}</h2>
    <button @click='test'>点我测试一下hello方法</button>
  </div>
</template>

<script>
export default {
    name: 'School',
    data(){
        return {
            name: '尚硅谷atguigu',
            address: '北京昌平'
        }
    },
    methods:{
        test(){
            this.hello()
        }
    }
}
</script>

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

Student.vue:

<template>
  <div>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <input type='text' v-fbind:value='name'>
  </div>
</template>

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

<style>
    .student{
        background-color: aqua;
    }
</style>

26、scoped 样式

scoped:让样式在局部生效,防止冲突。

School.vue:
<template>
  <div class='demo'>
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
  </div>
</template>

<script>
export default {
    name: 'School',
    data(){
        return {
            name: '尚硅谷atguigu',
            address: '北京昌平'
        }
    },
}
</script>

<style scoped>
    .demo{
        background-color: orange;
    }
</style>

Student.vue:
<template>
  <div class='demo'>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
  </div>
</template>

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

<style scoped>
    .demo{
        background-color: blue;
    }
</style>

27、浏览器本地存储

浏览器通过Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

localStorage.setItem('msg', 'hello!')localStorage.getItem('msg')localStorage.removeItem('msg')

sessionStorage.setItem('msg', 'hello')sessionStorage.getItem('msg')sessionStorage.removeItem('msg')

备注:

  • SessionStorage 存储的内容会随着浏览器窗口关闭而消失;
  • LocalStorage存储的内容,需要手动清除才消失;
  • xxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem()的返回值是null;
  • JSON.parse(null)的结果依然是null。

28、组件的自定义事件

组件的自定义事件是一种组件间的通信方式,适用于:子组件===>父组件。

使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中);

绑定自定义事件方式:如下演示。

App.vue:

<template>
    <div class='app'>
        <h1>{{msg}},学校姓名是:{{schoolName}}</h1>
        <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
        <School :getSchoolName='getSchoolName'/>
        <hr>
        <!-- 写法1:通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
        <Student v-on:atguigu='getStudentName'/> <!-- 使用once表示只触发一次-->
        <!-- 写法2(更灵活):通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
        <Student ref='student' @click.native='show'/>
    </div>
</template>

<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
    name: 'App',
    components:{Student, School},
    data(){
        return {
            msg: '你好',
            schoolName: '',
            studentName: ''
        }
    },
    methods:{
        getSchoolName(name){
            console.log('App 收到了学校名', name)
            this.schoolName = name
        },
        getStudentName(name){
            console.log('App 收到了学生名1', name)
        },
        show(){
            console.log('show')
        }
    },
    mounted(){
        // 延迟绑定
        setTimeout(()=>{
            // 要使用箭头函数,否则this指向会出问题(this指向的是产生事件的那个组件)
            this.$refs.student.$on('atguigu', (name, ...params)=>{
                console.log('App 收到了学生名2', name)
                this.studentName = name
            })
            // this.$refs.student.$once('atguigu', this.getStudentName) // 只触发一次
        }, 3)
    }
}
</script>

<style scoped>
    .app{
        background-color: gray;
    }
</style>

School.vue:

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

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

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

Stuent.vue:

<template>
  <div class='demo'>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <button @click="sendStudentName">把学生名给App</button>
    <button @click='unbind'>解绑atguigu事件</button>
  </div>
</template>

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

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

29、全局事件总线

全局事件总线(GlobalEventBus)是一种组件间通信的方式,适用于任意组件间通信。

安装全局事件总线:

new Vue({
    ...
    beforeCreate(){
        Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm
    }
    ...
})

使用事件总线:

// 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods(){
    demo(data){...}
}
...
mounted(){
    this.$bus.$on('xxx', this.demo)
}
    
// 提供数据
this.$bus.$emit('xxx', 数据)

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

30、消息订阅与发布

使用pubsub.js。一种组件间通信的方式,适用于任意组件间通信。

使用步骤:

  • 安装pubsub:npm i pubsub-js
  • 引入:import pubsub from 'pubsub-js'
  • 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
  • 提供数据:pubsub.publish('xxx', 数据)
  • 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅

31、nextTick

nextTick:

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

32、过渡与动画

Vue封装的过渡与动画:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

图示:

写法:

  1. 准备好样式:
    // 元素进入的样式
    v-enter:进入的起点
    v-enter-active:进入过程中
    v-enter-to:进入的终点
    
    // 元素离开的样式
    v-leave:离开的起点
    v-leave-active:离开过程中
    v-leave-to:离开的终点
  2. 使用<transition>包裹要过渡的元素,并配置name属性:
    <transition name='hello'>
        <h1 v-show='isShow'>你好啊</h1>
    </transition>
  3. 备注:若有多个元素需要过渡,则需要使用:<transition-group>,且每个元素都要指定key值。
<template>
  <div>
    <button @click='isShow = !isShow'>显示/隐藏</button>

    <!-- 一个元素 -->
    <transition name='tname' :appear='true'>
        <h1 v-show='isShow'>你好啊!</h1>
    </transition>

    <!-- 多个元素 -->
    <transition-group name='tname' :appear='true'>
        <h1 v-show='!isShow' key='1'>g1</h1>
        <h1 v-show='isShow' key='2'>g2</h1>
    </transition-group>
  </div>
</template>

<script>
export default {
    name: 'Test',
    data(){
        return {
            isShow: true
        }
    }
}
</script>

<style scoped>
    h1{
        background-color: orange;
    }

    .tname-enter-active{
        animation: atguigu 1s;
    }

    .tname-leave-active{
        animation: atguigu 1s reverse;
    }

    @keyframes atguigu {
        from{
            transform: translateX(-100%);
        }
        to{
            transform: translateX(0px);
        }
    }
</style>
<template>
  <div>
    <button @click='isShow = !isShow'>显示/隐藏</button>
    <transition name='tname' :appear='true'>
        <h1 v-show='isShow'>你好啊!</h1>
    </transition>
  </div>
</template>

<script>
export default {
    name: 'Test2',
    data(){
        return {
            isShow: true
        }
    }
}
</script>

<style scoped>
    h1{
        background-color: orange;
    }

    /*进入的起点 和 离开的终点*/
    .tname-enter, .tname-leave-to{
        transform: translateX(-100%);
    }

    /*进入过程中,离开过程中*/
    .tname-enter-active, .tname-enter-active {
        transition: 0.5s linear;
    }

    /*进入的终点 和 离开的起点*/
    .tname-enter-to, .tname-leave{
        transform: translateX(0);
    }
</style>
<template>
  <div>
    <button @click='isShow = !isShow'>显示/隐藏</button>

    <!-- 多个元素 -->
    <transition-group name='animate__animated animate_bounce' 
        enter-active-class="animate__swing"
        leave-active-class="animate__backOutUp"
        :appear='true'
    >
        <h1 v-show='isShow' key='2'>g2</h1>
    </transition-group>
  </div>
</template>

<script>
//使用 npm install animation.css
import 'animate.css'
export default {
    name: 'Test3',
    data(){
        return {
            isShow: true
        }
    }
}
</script>

<style scoped>
    h1{
        background-color: orange;
    }
</style>

32、配置代理

在vue.config.js中配置:

module.exports = {
    // 方式1
    devServer: {
        proxy: 'http://localhost:5000'
    },
    
    // 方式2
    devServer: {
        proxy: {
            '/api': {
                target: '<url>',
                pathRewrite: {'^/api': '/'},
                ws: true, // 用于支持websocket
                changeOrigin: true // 用于控制请求头中host值
            },
            '/foo': {
                target: '<other_url>'
            }
        }
    }
}

33、插槽

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

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

使用方式:

  • 默认插槽:
    父组件中:
    <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>
  • 作用域插槽:组件在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(game数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
    父组件中:
    <Category>
        <template scope="scopeData">
            <!-- 生成的是ul列表 -->
            <ul>
                <li v-for="g in scopeData.games" :key="g">{{g}}</li>
            </ul>
        </template>
    </Category>
    
    子组件中:
    <template>
        <div>
            <slot :games="games"></slot>
        </div>
    </template>
    <script>
        export default {
            name: 'Category',
            props:['title'],
            // 数据在子组件自身
            data(){
                return {
                    games:['红色警戒', '穿越火线', '劲舞团', '超级玛丽']
                }
            },
        }
    </script>

34、vuex

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

Github地址:https://github.com/vuejs/vuex

什么时候使用vuex:

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

34.1、vuex原理图

34.2、搭建Vuex环境

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

// 准备actions用于响应组件中的动作
const actions = {
    jia: function(context, value){
        context.commit("JIA", value)
    }
}
// 准备mutaions用于操作数据(state)
const mutations = {
    JIA(state, value){
        state.sum += value
    }
}
// 准备state用于存储数据
const state = {
    sum: 98
}

// 准备getters
const getters = {
    bigSum(state){
        return state.sum * 10
    }
}

// 创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})
// main.js
...
// 引入store
import store from './store'
...

// 关闭Vue的生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(vueResource)
Vue.use(Vuex)

new Vue({
    el: '#app',
    render: h => h(App),
    store
})

组件中读取vuex中的数据:$store.state.sum

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

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

34.3、4个map方法的使用

  • mapState方法:用于帮助我们映射state中的数据为计算属性。
    computed:{
        // 借助mapState生成计算属性,sum、school、subject(对象写法)
        ...mapState({sum: 'sum', school:'school', subject:'subject'})
        
        // 借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum', 'school', 'subject'])
    }
  • mapGetters方法:用于帮助我们映射getters中的数据为计算属性。
    computed:{
        // 借助mapGetters生成计算属性,bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'})
        
        // 借助mapGetters生成计算属性,bigSum(数组写法)
        ...mapGetters(['bigSum'])
    }
  • mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数;
    methods:{
        // 靠mapActions生成,incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd', incrementWait:'jiaWait'})
            
        // 靠mapActions生成,incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd', 'jiaWait'])
    }
  • mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数。
    methods:{
        // 靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA', decrement:'JIAN'})
            
        // 靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA', 'JIAN'])
    }

34.4、模块化

// index.js
const options1 = {
    namespaced: true, // 启动命名空间
    state:{},
    actions:{},
    mutations:{},
}
const options2 = {
    namespaced: true, // 启动命名空间
    state:{},
    actions:{},
    mutations:{},
}

export default new Vuex.Store({
    modules:{
        a: options1, // 启用命名空间后可以使用
        b: options2
    }
})

Count.vue:
...
export default {
    name: 'Count',
    computed:{
        ...mapState('a', ['xx', 'xx']) // 启用命名空间可以使用
    },
    methods:{
        ...mapMutations('a', {increment:'JIA', ...})
    }
}
...

35、路由(vue-router)

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

SPA(signle page web application):单页web应用,整个应用只有一个完整的页面,点击页面中的导航链接不会刷新页面,只会做页面的局部更新,数据需要通过ajax请求获取。

路由:一个路由就是一组映射关系(key-value),key为路径,value可能是function或component。

路由分类:

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

vue2中vue-router只能使用版本3:npm i vue-router@3

应用插件:Vue.use(VueRouter)

router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入自己的组件
import MyComp1 from './components/MyComp1'
import MyComp2 from './components/MyComp2'

// 创建并暴露一个路由器
export default router = new VueRouter({
    routes:[
        {
            path:'/mycomp1',
            component:MyComp1
        },
        {
            path:'/mycomp2',
            component:MyComp2
        }
    ],
})

注意:

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

35.1、嵌套(多级)路由

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

routes:[
    {
        path: '/home',
        children:[
            {
                path:'news', // 此处不要写成/news
                component: News
            },
            {
                ...
            }
        ]
    }
]
        
// 跳转
<router-link to='/home/news'>News</router-link>

35.2、路由的query参数

接收参数:

$route.query.id

35.3、命名路由

作用:简化路由的跳转。

35.4、路由的params参数

35.5、路由的props配置

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

35.6、<router-link>的replace属性

作用:控制路由跳转时操作浏览器历史记录的模式。

浏览器的历史记录有两种写入方式:push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push。

如何开启replace模式:<router-link replace ......>dsaddsa</router-link>

35.7、编程式路由导航

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

35.8、缓存路由组件

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

35.9、2个新的生命周期钩子

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

具体名字:

  • activatd:路由组件被激活时触发。
  • deactivated:路由组件失活时触发。

35.10、路由守卫

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

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

全局守卫:

独享路由守卫:

组件内路由守卫:

35.11、路由器的两种工作模式

对于一个url来说,#及其后面的内容就是hash值。

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

hash模式:

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

history模式:

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

36、Vue UI 组件库

 

posted on 2024-09-13 12:43  啊噢1231  阅读(7)  评论(0编辑  收藏  举报

导航

回到顶部