小白零基础学 VUE笔记

本文详细介绍了Vue.js的基础知识,包括初始Vue、模板语法、数据绑定、指令语法、MVVM模式、数据代理、计算属性与监听属性、键盘事件、绑定样式、条件渲染和列表渲染等内容,适合初学者全面学习Vue.js。此外,还涵盖了Vue的生命周期、组件、自定义指令、事件总线、本地存储、路由和插槽等进阶主题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、初始Vue

1、前置工作

(1)给浏览器安装Vue Devtools 插件

(2)标签引入Vue包

(3)阻止vue在启动时生成生产提示Vue.config.productionTip = false

2、初始Vue

(1)想让Vue工作,就必须先创建一个Vue实例,且要传入一个配置对象比如下面的el,data;

(2)root容器里的代码依然符合HTML规范,只不过混入了一些vue语法

(3)root容器里的代码被称为【vue模板】

(4)一个容器对应一个实例,一一对应

(5)插值语法{{xxx}} 中的xxx要写js表达式,且xxx可以自动读取到data中的所以属性。

(6)一旦data中的数据发生改变,那么模板中用到该数据的地方会自动更新

<!-- 准备好一个容器 -->
    <div id="root">
      <h1>hello,{{name}}</h1>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false //阻止vue在启动时生成生产提示
      
      new Vue({
        el: '#root', //(或可以用:document.getElemnetById('root')直接指定)用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
        data: { //data中用于存储数据,数据供el所指定的容器使用,值暂时写成了一个对象,后边应该是个函数
          name: '世界',
          address: '苏州'
        },
      });
    </script>

二、模板语法

1、插值语法

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

写法:{{xxx}},xxx是js表达式,可以直接读取到data中的所以区域

2、指令语法

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

举例:<a v-bind:href="xxx">可以简写为<a :href="xxx">,xxx是js表达式,可以直接读取到data中的所以区域

    <!-- 准备好一个容器 -->
    <div id="root">
      <h1>插值语法</h1>
      <h2>hello,{{name}}</h2>
      <hr>
      <h1>指令语法</h1>
      <a v-bind:href="school.url">点击去百度{{school.name}}</a>
      <a :href="school.url.toUpperCase()">点击去百度</a> <!-- //.toUpperCase(),字母换成大写 -->
    </div>

    <script>
      Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
      
      new Vue({
        el: '#root',
        data: {
          name: 'xjx',
          school: {
            name: '湖科大',
            url: 'https://www.baidu.com/',
          }
          
        }
      })
    </script>

三、数据绑定

vue中有两种数据绑定方式

1、单项绑定:v-bind ,数据只能从data流向页面。

2、双向绑定:v-model ,数据不仅从data流向页面,还可以从页面流向data

备注:

1、双向绑定一般应用在表单类元素上,如input,select

2、v-model:value可以简写成v-model

四、el与data 的两种写法

1、el的两种写法

(1)new Vue时配置el属性

(2)先创建Vue实例对象,随后在通过vm.$mount('#root')指定el的值

2、data的两种写法

(1)对象式:data:{}

(2)函数式:后期学到组件式,必须用函数式,否则会报错.data(){return{}}

注:

由vue管理的函数,一定不要写箭头函数,因为vue中的箭头函数,this是指向windows的,不是指向vue实例对象的。

<script>
    Vue.config.productionTip = false
    /* el的第一种用法 */
    /* new Vue({
      el: '#root',
      data: {
        name: '小席'
      }

    }) */
    /* el的第二种用法 */
    /*  const x = new Vue({
      data: {
        name: '小席'
      }
    })
    x.$mount('#root') */

    //data的第一种写法:对象式
    /* new Vue({
      el: '#root',
      data: {
        name: '小席'
      }

    }) */
    //data的第二种写法:函数式
    new Vue({
      el: '#root',
      data() { //data: function () {}
        console.log(this) //此处this表示Vue的实例对象
        return {
          name: '小席'
        }
      }

    })
  </script>

五、MVVM模式

M:model(模型) data中的数据

V:view(视图) 模板代码

vm:viewmodel(视图模型) vue实例

注:1 data中的所有属性,最后都出现在了vm身上

2 vm的所有属性及vue原型上的所有属性,在vue模板中都可以使用

六 、数据代理

1、Object.defineProperty

(要定义属性的对象,要定义或修改的属性的名称或 Symbol ,要定义或修改的属性描述符)方法

三个参数

 <script>
    Vue.config.productionTip = false
    let number = 18
    let person = {
      name: '小席',
      sex: '女'
    }
    Object.defineProperty(person, 'age', {
      /* value: 18,
      configurable: true, //控制属性是否可以被删除,默认值是false
      enumerable: true, //控制属性是否可以被枚举
      writable: true, //控制属性是否可以被修改 */

      //读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
      get() {
        return number
      },

      //修改person的age属性时,get函数(getter)就会被调用,且会收到修改的具体值
      set(value) {
        number = value
      }
    })
  </script>
2、vue中的数据代理

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

(2)vue数据代理的好处:更加方便的操作data中的数据

(3)基本原理

通过object.defieProperty(),把data对象中的所有属性添加到vm中

为每一个添加到vm上的属性,都指定getter和setter

在getter和setter内部去操作(读写)data中对应的属性

七、计算属性与监听属性

1、计算属性complute

1、定义:要用的属性存在,要通过已有属性计算

2、原理:底层借助object.defineProperty方法提供的getter和setter

3、与model相比,内部有缓存机制,效用更高,调试方便

4、注:

计算属性会直接出现在vm上,直接读取即可

如果计算属性要被修改,必须要写set函数去响应,引起计算时所依赖的数据发生改变

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

    </div>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: '#app',

            data: {
                firstName: '小',
                lastName: '希'

            },
            // 计算属性
            computed: {
                /*   // 完整写法
                  fullName: {
                      //get的作用,当有人读取了fullName时,get就会被调用,且get的返回值是fullName的值
                      //get什么时候被调用?两种情况 1、初次读取fullName,之后都是在缓存里调用   2、所依赖的数据发生变化时
                      get() {
                          console.log('get被调用了')
                          //  console.log(this)  //这里的this是指vm
                          return this.firstName + '-' + this.lastName
                      },
                      //当fullName被修改时set被调用
                      set(value) {
                          console.log('set', value)
                          const arr = value.split('-')
                          this.firstName = arr[0]
                          this.lastName = arr[1]

                      }
                  } */
                //简写:只有被读取,没有被修改时才能用简写。下面的函数就相当于getter
                fullName() {
                    //get的作用,当有人读取了fullName时,get就会被调用,且get的返回值是fullName的值
                    //get什么时候被调用?两种情况 1、初次读取fullName,之后都是在缓存里调用   2、所依赖的数据发生变化时
                    console.log('get被调用了')
                    //  console.log(this)  //这里的this是指vm
                    return this.firstName + '-' + this.lastName
                }
            }
        })
    </script>

计算属性中的setter函数运行原理

2、监听事件watch

1、当被监视的属性发生变化时,回调函数自动调用,进行相关操作

2、监视的属性必须存在,才能进行监视

3、监视的两种写法:

(1)在vue实例对象中传入watch方法配置

(2)通过vm$watch('监听的事件',handle())监视

<div id="app">
        <h2>今天天气很{{info}}</h2>
        <!-- 绑定事件的时候:@xxx="yyy",yyy可以简单写一些表达式。比如下边的可以写为@click="isHot = !isHot" -->
        <button @click="changeWeather">点击切换</button>
    </div>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: '#app',
            data: {
                isHot: true
            },
            computed: {
                info() {
                    return this.isHot ? '热' : '凉快'
                }
            },
            methods: {
                changeWeather() {
                    this.isHot = !this.isHot
                }
            },
            //监听事件
            watch: {
                info: {

                    immediate: true, //初始化时让handle调用一下
                    //当info发生变化时handle被调用
                    handler(newValue, oldValue) {
                        console.log('info被修改了', newValue, oldValue)

                    }
                }
            }
        })
        //监听事件的另一种写法
        vm.$watch('info', {
            immediate: true, //初始化时让handle调用一下
            //当info发生变化时handle被调用
            handler(newValue, oldValue) {
                console.log('info被修改了', newValue, oldValue)
            }
        })

深度监视deep

(1)vue中的watch默认不监视对象内部值的改变(一层)

(2)配置deep:true可以监视对象内部的改变

注:

(1)vue本身可以监视对象内部值的改动,但vue提供的watch默认不可以

(2)使用watch时根据数据的具体结构,决定是否采用深度监视

watch: {
                info: {

                    immediate: true, //初始化时让handle调用一下
                    //当info发生变化时handle被调用
                    handler(newValue, oldValue) {
                        console.log('info被修改了', newValue, oldValue)

                    }
                },
                //监听a(监听多级结构中的某个属性的变化)
                'number.a': {
                    handler() {
                        console.log('a发生变化了')
                    }
                },
                //监听多级结构中的所以属性的变化
                number: {
                    deep: true,
                    handler() {
                        console.log('number发生变化了')
                    }
                }

            }
3、两者的区别

(1)complute能完成的功能,watch都能完成

(2)watch能完成的不一定complute能完成,例如,watch能进行异步操作,setTimeout()

注:

(1)被vue管理的函数,最好都写成普通函数,这样this的指向才能是vm,或组件实例对象

(2)所有不被vue管理的函数(定时器的回调函数、ajax的回调函数、promise的回调函数等),最后写成箭头函数,这样this的指向才能是vm,或组件实例对象

八、键盘事件

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

回车 => enter

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

退出 => esc

空格 => space

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

上 => up

下 => down

左 => left

右 => right

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

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

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

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

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

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

<div id="root">
			<h2>欢迎来到{{name}}学习</h2>
			<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		Vue.config.keyCodes.huiche = 13 //定义了一个别名按键

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

九、绑定样式

1. class样式

写法:class="xxx" xxx可以是字符串、对象、数组。

字符串写法适用于:类名不确定,要动态获取。

对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。

数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

2. style样式

:style="{fontSize: xxx}"其中xxx是动态值。

:style="[a,b]"其中a、b是样式对象。

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

        <!-- 2、绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="classArr">{{name}}</div> <br /><br />

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



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

        <!-- 5、绑定style样式--数组写法 -->
        <div class="basic" :style="styleArr">{{name}}</div>
    </div>

    <script>
        Vue.config.productionTip = false
        new Vue({
            el: '#app',
            data: {
                name: '小希',
                mood: 'normal',
                classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
                classObj: {
                    atguigu1: false,
                    atguigu2: false,
                },
                styleObj: {
                    fontSize: '40px',
                    color: 'red',
                },
                styleObj2: {
                    backgroundColor: 'orange'
                },
                styleArr: [{
                        fontSize: '40px',
                        color: 'blue',
                    },
                    {
                        backgroundColor: 'gray'
                    }
                ]
            },
            methods: {
                changeMood() {

                    const arr = ['happy', 'sad', 'normal']
                    const index = Math.floor(Math.random() * 3)
                    this.mood = arr[index]


                },
            },
        })
    </script>

十、条件渲染

1.v-if

写法:

(1).v-if="表达式"

(2).v-else-if="表达式"

(3).v-else="表达式"

适用于:切换频率较低的场景。

特点:不展示的DOM元素直接被移除。

注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

2.v-show改变的是display

写法:v-show="表达式"

适用于:切换频率较高的场景。

特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

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

 <div id="root">
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
        <!-- 使用v-show做条件渲染 -->
        <!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
        <!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->

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

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

        <!-- v-if与template的配合使用,不改变结构 -->
        <template v-if="n === 1">
            <h2>你好</h2>
            <h2>尚硅谷</h2>
            <h2>北京</h2>
        </template>

    </div>
</body>

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

    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            n: 0
        }
    })
</script>

十一、列表渲染

1、 v-for指令

(1)用于展示列表数据

(2)语法:v-for="(item, index) in xxx" :key="yyy"

(3)可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表(遍历数组)(常用)</h2>
        <ul>
            <!-- index是唯一的编号,自带的 -->
            <li v-for="(p,index) of persons" :key="index">
                {{p.name}}-{{p.age}}
            </li>
        </ul>

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

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

        <!-- 遍历指定次数 -->
        <h2>测试遍历指定次数(用得少)</h2>
        <ul>
            <li v-for="(number,index) of 5" :key="index">
                {{index}}-{{number}}
            </li>
        </ul>
    </div>

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

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

(1)index作为key

<liv-for="(p,index) of persons":key="index">

{{p.name}}-{{p.age}}

</li>

注:如果不定义key,vue会自动将遍历序号作为index

数据会错乱

(2)Id作为key值

数据不会错乱

总结:

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

1. 虚拟DOM中key的作用:

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,

随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:

①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!

②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key

创建新的真实DOM,随后渲染到到页面。

2. 用index作为key可能会引发的问题:

(1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

(2)如果结构中还包含输入类的DOM:

会产生错误DOM更新 ==> 界面有问题。

3. 开发中如何选择key?:

(1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

3、列表过滤

(1)计算属性

根据已有的数组persons筛选计算出用户输入的语句,返回给计算属性filPersons,显示出来。

  <div id="root">
        <input type="text" placeholder="请输入名字" v-model="keyWord">
        <ul>
            <li v-for="(p,index) in filPersons" :key="index">
                {{p.name}}-{{p.age}}-{{p.sex}}

            </li>
        </ul>

    </div>
    <script>
        Vue.config.productionTip = false
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: '男'
                    }
                ],
            },
            computed: {
                filPersons() {
                    return this.persons.filter((p) => { //.filter过滤出 符合条件的
                        return p.name.indexOf(this.keyWord) !== -1//.indexOf是筛选语句。-1表示没有匹配到。其他值表示匹配到,并返回的是匹配到的语句的索引号。
                    })
                }
            }
        })
    </script>

注:filPersons是计算出来的

(2)监视属性

监听输入的内容keyWord,筛选出符合条件的给filPersons数组。

    <div id="root">
        <input type="text" placeholder="请输入名字" v-model="keyWord">
        <ul>
            <li v-for="(p,index) in filPersons" :key="index">
                {{p.name}}-{{p.age}}-{{p.sex}}

            </li>
        </ul>

    </div>
    <script>
        Vue.config.productionTip = false
        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: [],
            },
            // 监视属性过滤 
            watch: {
                keyWord: {
                    immediate: true,//初次显示出persons[]信息
                    handler(val) {//只需要新值,不关心之前输入的语句,所以只要一个参数newvalue,不需oldvalue
                        this.filPersons = this.persons.filter((p) => { //.filter过滤出 符合条件的
                            return p.name.indexOf(val) !== -1
                                //indexOf判断一个字符串中是否包含指定字符。-1是不包含,空的结果为0,其他有的按照位置索引号输出
                        })
                    }
                }
            }
        })
4、列表排序

在列表过滤的基础上,对筛选.indexOf出来的内容根据年龄进行排序.sort。

 <div id="app">
        <input type="text" placeholder="请输入内容" v-model="keyWords">
        <button @click="sortType=1">年龄升序</button>
        <button @click="sortType=2">年龄降序</button>
        <button @click="sortType=0">年龄原顺序</button>
        <ul>


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

        </ul>

    </div>
    <script>
        Vue.config.productionTip = false
        new Vue({
            el: '#app',
            data: {
                keyWords: '',
                sortType: 0, //0表示原顺序,1表示升序,2表示降序
                persons: [{
                    id: '001',
                    name: '周杰伦',
                    age: '34',
                    sex: '男'
                }, {
                    id: '002',
                    name: '周冬雨',
                    age: '21',
                    sex: '女'
                }, {
                    id: '003',
                    name: '马冬梅',
                    age: '30',
                    sex: '女'
                }, {
                    id: '004',
                    name: '任嘉伦',
                    age: '20',
                    sex: '男'
                }]

            },
            /* 监听 */
            computed: {
                filPersons() {
                    /* 筛选出输入的内容与原数组匹配的内容 */
                    const arr = this.persons.filter((p) => {
                        return p.name.indexOf(this.keyWords) !== -1 //如果输入的内容有,返回

                    })
                    /* 判断一下是否需要排序,对上述筛选出的内容进行排序 */
                    if (this.sortType) { //1和2都是true
                        arr.sort((p1, p2) => { //.sort(p1,p2)表示对数组arr进行排序,p1-p2表示升序,p2-p1表示降序
                            return this.sortType === 1 ? p1.age - p2.age : p2.age - p1.age

                        })

                    }
                    return arr

                }

            }
        })
    </script>
5、Vue监视的原理

object.defineProperty

(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修改数组中的某个元素一定要用如下方法:

使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

Vue.set() 或 vm.$set()

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

<div id="root">
		<h1>学生信息</h1>
		<button @click="student.age++">年龄+1岁</button> <br />
		<button @click="addSex">添加性别属性,默认值:男</button> <br />
		<button @click="student.sex='女'">修改性别</button> <br />
		<button @click="addFriends">在列表首位添加一个朋友</button> <br />
		<button @click="updateFriendsFirstName">修改第一个朋友的名字为:张三</button> <br />
		<button @click="addHobby">添加一个爱好</button> <br />
		<button @click="updateHobby">修改第一个爱好为:开车</button> <br />
		<button @click="smoke">过滤掉爱好中的抽烟</button> <br />
		<h3>姓名:{{student.name}}</h3>
		<h3>年龄:{{student.age}}</h3>
		<h3 v-if="student.sex">性别:{{student.sex}}</h3>
		<h3>爱好:</h3>
		<ul>
			<li v-for="(h,index) in student.hobby" :key="index">
				{{h}}
			</li>
		</ul>
		<h3>朋友们:</h3>
		<ul>
			<li v-for="(f,index) in student.friends" :key="index">
				{{f.name}}--{{f.age}}
			</li>
		</ul>
	</div>
</body>

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

	const vm = new Vue({
		el: '#root',
		data: {
			student: {
				name: 'tom',
				age: 18,
				hobby: ['抽烟', '喝酒', '烫头'],
				friends: [{
						name: 'jerry',
						age: 35
					},
					{
						name: 'tony',
						age: 36
					}
				]
			}
		},
		methods: {
			addSex() {
				Vue.set(this.student, 'sex', '男')
				//this.$set(this.student, 'sex', '男')//另一种写法
			},
			addFriends() {
				this.student.friends.unshift({
					name: '小希',
					age: 18
				})
			},
			updateFriendsFirstName() {
				this.student.friends[0].name = '张三'
			},
			addHobby() {
				this.student.hobby.push('唱歌')
			},
			updateHobby() {
				//Vue.set(this.student.hobby, 0, '开车')
				vm.$set(this.student.hobby, 0, '开车')
				//this.student.hobby.splice(0, 1, '开车')

			},
			smoke() {
				this.student.hobby = this.student.hobby.filter((p) => {
					return p !== '抽烟'

				})
			}

		},


	})
</script>

十二、收集表单数据

主要是通过v-model将表单数据收集,然后转为json文件

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

(2)<input type="radio"/>,单选 则v-model收集的是value值,且要给标签配置value值。

(3)<input type="checkbox"/>多选

若没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

若 配置input的value属性:

①v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

②v-model的初始值是数组,那么收集的的就是value组成的数组

注:v-model的三个修饰符:

lazy:失去焦点再收集数据

number:输入字符串转为有效的数字

trim:输入首尾空格过滤

十三、过滤器

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

语法:

(1)注册过滤器:Vue.filter(name,callback) 全局 或 new Vue{filters:{}}局部

(2)使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"

备注:

(1)过滤器也可以接收额外参数、多个过滤器也可以串联

(2)并没有改变原本的数据, 是产生新的对应的数据

<div id="app">
        <!-- 计算属性computed实现 -->
        <h3>现在的时间是:{{fmtTime}}</h3>
        <!--methods属性实现  -->
        <h3>现在的时间是:{{getFmtTime()}}</h3>
        <!-- 过滤器实现 -->
        <h3>现在的时间是:{{time | timeFormater}}</h3>
        <!-- 过滤器传参 -->
        <h3>现在的时间是:{{time | timeFormater('YYYY_MM_DD')}}</h3>
        <!-- 过滤器实现多个参数 -->
        <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
        <h3 :x="msg | mySlice">你好呀</h3>
    </div>
    <script>
        Vue.config.productionTip = false
        /* 全局过滤器 */
        Vue.filter('mySlice', function (value) {
            return value.slice(0, 4) //截取前4位
        })
        new Vue({
            el: '#app',
            data: {
                time: 1649388425377,
                msg: '好的好的非常好'
            },
            computed: {
                fmtTime() {
                    return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss') //引用了bootCDN开源库中的dayjs
                }
            },
            methods: {
                getFmtTime() {
                    return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
                }
            },
            /* 局部过滤器 */
            filters: {
                timeFormater(value, str =
                    'YYYY年MM月DD日 HH:mm:ss') { //如果str有值,就用传过来的。没有就用YYYY年MM月DD日 HH:mm:ss
                    return dayjs(value).format(str)
                }
            }
        })
    </script>

注:引用了BootCDN中的dayjs格式化时间。

十四、内置指令

1、其他指令

v-bind : 单向绑定解析表达式, 可简写为 :xxx

v-model : 双向数据绑定

v-for : 遍历数组/对象/字符串

v-on : 绑定事件监听, 可简写为@

v-if : 条件渲染(动态控制节点是否存存在)

v-else : 条件渲染(动态控制节点是否存存在)

v-show : 条件渲染 (动态控制节点是否展示)

1、V-text

作用:向其所在的节点中渲染文本内容。

与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

2、v-html

作用:向指定节点中渲染包含html结构的内容。

与插值语法的区别:

(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。

(2).v-html可以识别html结构。

严重注意:v-html有安全性问题!!!!

(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

<!-- 准备好一个容器-->
<div id="root">
  <div>你好,{{name}}</div>
  <div v-html="str"></div>
  <div v-html="str2"></div>
</div>

<script type="text/javascript">
  Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
  
  new Vue({
    el: '#root',
    data: {
      name: '小希',
      str: '<h3>你好啊!</h3>',
      str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
    }
  })
    </script>
3、v-cloak(没有值)

本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

4、v-once(没有值)

v-once所在节点在初次动态渲染后,就视为静态内容了。

以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

    <div id="app">
        <h2 v-once>初始化的n:{{n}}</h2>
        <h2>当前的n为:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
<script>
    Vue.config.productionTip = false
    new Vue({
        el: '#app',
        data: {
            n: 1
        }
    })
</script>
5、v-pro

跳过其所在节点的编译过程。

可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

十五、自定义指令

1、对象自定义指令

(1)定义语法:

①.局部指令:

new Vue({

directives:{指令名:配置对象}

})

②.全局指令:

Vue.directive(指令名,配置对象)

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

(1).bind:指令与元素成功绑定时调用。

(2).inserted:指令所在元素被插入页面时调用。

(3).update:指令所在模板结构被重新解析时调用。

2、函数自定义指令

new Vue({

directives{指令名:回调函数}

})

Vue.directive(指令名,回调函数)

备注:

指令定义时不加v-,但使用时要加v-;

指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

十六、生命周期

1、概念

Vue在关键时刻帮我们调用的一些特殊名称的函数

注:(1)又名:生命周期回调函数、生命周期函数、生命周期钩子。

(2)生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。

(3)生命周期函数中的this指向是vm 或 组件实例对象。

2、常用生命周期的函数

(1)mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。

(2)beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

3、关于销毁Vue实例

(1)销毁后借助Vue开发者工具看不到任何信息。

(2)销毁后自定义事件会失效,但原生DOM事件依然有效。

(3)一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

<body>

	<div id="root">
		<h2 :style="{opacity}">欢迎学习Vue</h2>
		<button @click="opacity = 1">透明度设置为1</button>
		<button @click="stop">点我停止变换</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	new Vue({
		el: '#root',
		data: {
			opacity: 1
		},
		methods: {
			stop() {
				this.$destroy()
			}
		},
		//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
		mounted() {
			console.log('mounted', this)
			this.timer = setInterval(() => {
				console.log('setInterval')
				this.opacity -= 0.01
				if (this.opacity <= 0) this.opacity = 1
			}, 16)
		},
		beforeDestroy() {
			clearInterval(this.timer)
			console.log('vm即将驾鹤西游了')
		},
	})
</script>

十七、非单文件组件

1、Vue组件的三大步骤

(1)定义组件(创建组件)

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

区别如下:

①el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

②data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

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

(2)注册组件

①局部注册:靠new Vue的时候传入components选项

②全局注册:靠Vue.component('组件名',组件)

(3)使用组件(写组件标签)

<school></school>

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

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

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

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

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

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

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

十八、单文件组件

十九、ref属性

二十、props配置

二十一、mixin混入

二十二、scoped样式

二十三、TodoList案例

二十四、本地存储

二十五、组件自定义事件

二十六、全局事件总线

任意组件间通信

二十七、TodoList_pubsub

二十八、TodoList_nextTick

过度与动画

二十九、vuex

理念:共享

二十九、路由

1、理解

(1) 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。

(2)点击页面中的导航链接,不会刷新页面。只做页面的局部更新。单页面

(2)数据通过ajax请求从服务器获得.

(3)前端路由:key是路径,value是组件。

Vue.js 路由允许我们通过不同的 URL 访问不同的内容,通过Vue.js可以实现多视图的单页Web应用(single page web application,SPA)。

<router-link> 是一个组件,该组件用于设置一个导航链接,切换不同 HTML 内容。 to 属性为目标地址, 即要显示的内容。

如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)。

2、基本使用

案例:实现About和Home路由组件的切换

(1)安装vue-router,命令:npm i vue-router@3 (router3版本)

(2)在main.js,导入并应用插件vue-router

import Vue from 'vue'
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
import router from './router'

Vue.config.productionTip = false

//应用插件
Vue.use(VueRouter)
new Vue({
  el: '#app',
  render: h => h(App),
  router: router
})

(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)在App.vue实现切换两个路由组件About和Home的切换

(active-class可配置点击的时候高亮样式)

<router-link active-class="active" to="/about">About</router-link> 

(5)指定展示位置

<router-view></router-view>

App.vue全:

<template>
<div>
  <div class="row">
    <div class="col-xs-offset-2 col-xs-8">
      <div class="page-header"><h2>Vue Router Demo</h2></div>
  </div>
  </div>
  <div class="row">
    <div class="col-xs-2 col-xs-offset-2">
      <div class="list-group">
        <!-- 原始html中我们使用a标签实现页面的跳转 -->
        <!-- <a class="list-group-item active" href="./about.html">About</a> -->
        <!-- <a class="list-group-item" href="./home.html">Home</a> -->
        
        <!-- Vue中借助router-link标签实现路由的切换   active-class点击激活 -->
        <router-link class="list-group-item" active-class="active" to="/about"
                     >About</router-link
          >
        <router-link class="list-group-item" active-class="active" to="/home"
                     >Home</router-link
          >
  </div>
  </div>
    <div class="col-xs-6">
      <div class="panel">
        <div class="panel-body">
          <!-- 指定组件的呈现位置 -->
          <router-view></router-view>
  </div>
  </div>
  </div>
  </div>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>

About组件:

<template>
<h2>我是About的内容</h2>
</template>

<script>
  export default {
    name: 'AboutS'
  }
</script>

Home组件:

<template>
<h2>我是Home的内容</h2>
</template>

<script>
  export default {
    name: 'HomeS'
  }
</script>
3、注

(1)路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。

(2)通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

(3)每个组件都有自己的$route属性,里面存储着自己的路由信息。

(4)整个应用只有一个router,可以通过组件的$router属性获取到。

4、多级路由

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

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import HomeMessage from '../pages/HomeMessage'
import HomeNews from '../pages/HomeNews'
//创建并暴露一个路由器
export default new VueRouter({
  routes: [
    {
      path: '/about',/* 路径 */
      component: About/* 组件 */
    },
    {
      path: '/home',//一级路由
      component: Home,
      children: [//通过children配置子级路由
        {
          path: 'HomeMessage',//此处一定不要写:/HomeMessage
          component: HomeMessage
        },
        {
          path: 'HomeNews',
          component: HomeNews
        }
      ]
    }
    
  ]
})

(2)跳转(要写完整路径)

home组件路由下的HomeMessage实现跳转

<router-link to="/home/HomeMessage">HomeMessage</router-link>
5、路由的query参数

三级路由的参数传递:

(1)给HomeMessage传递参数query是Detail组件中的内容

    <li v-for="m in messageList" :key="m.id">
        <!-- 跳转路由并携带query参数,to的字符串写法 -->
        <!--     <router-link
          :to="`/home/HomeMessage/Detail?id={{m.id}}&title={{m.title}}`"
          >{{ m.title }}</router-link
        >
 -->
        <!-- 跳转路由并携带query参数,to的对象写法 -->
        <router-link
          :to="{
            path: '/home/HomeMessage/Detail',
            query: {
              id: m.id,
              title: m.title
            }
          }"
        >
          {{ m.title }}
        </router-link>
      </li>

(2)detail路由接收参数query

<ul>
    <li>消息编号:{{ $route.query.id }}</li>
    <li>消息内容:{{ $route.query.title }}</li>
</ul>
6、路由命名name

方便多级路由路径太长,可以使用name对象

//创建并暴露一个路由器
export default new VueRouter({
  routes: [
    {
      name: 'guanyu',/* 路由命名*/
      path: '/about',/* 路径 */
      component: About/* 组件 */
    },
    {
      path: '/home',//一级路由
      component: Home,
      children: [//二级路由
        {
          path: 'HomeMessage',
          component: HomeMessage,
          children: [
            {
              name: 'xiangqing',
              path: 'Detail',
              component: Detail,
            }
          ]
        },
        {
          path: 'HomeNews',
          component: HomeNews
        }
      ]
    }
    
  ]
})

使用name前:

<!-- 跳转路由并携带query参数,to的对象写法 -->
        <router-link
          :to="{
            path: '/home/HomeMessage/Detail',
            query: {
              id: m.id,
              title: m.title
            }
          }"
        >
          {{ m.title }}
        </router-link>

使用name后:

<router-link
             :to="{
                  name: 'xiangqing',
                  query: {
                  id: m.id,
                  title: m.title
                  }
                  }"
             >
  {{ m.title }}
        </router-link>
7、路由的params参数

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

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import HomeMessage from '../pages/HomeMessage'
import Detail from '../pages/Detail'
import HomeNews from '../pages/HomeNews'

//创建并暴露一个路由器
export default new VueRouter({
  routes: [
    {
      name: 'guanyu',/* 路由命名*/
      path: '/about',/* 路径 */
      component: About/* 组件 */
    },
    {
      path: '/home',//一级路由
      component: Home,
      children: [//二级路由
        {
          path: 'HomeMessage',
          component: HomeMessage,
          children: [
            {
              name: 'xiangqing',
              path: 'Detail:/:id/:title',//使用占位符声明接收params参数
              component: Detail,
            }
          ]
        },
        {
          path: 'HomeNews',
          component: HomeNews
        }
      ]
    }
    
  ]
})

(2)传递参数

   <!-- 跳转路由并携带params参数,to的字符串写法 -->
        <!-- <router-link :to="`/home/HomeMessage/Detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->

        <!-- 跳转路由并携带params参数,to的对象写法 -->
        <!-- 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置! -->
        <router-link
          :to="{
            name: 'xiangqing',
            params: {
              id: m.id,
              title: m.title
            }
          }"
        >
          {{ m.title }}
        </router-link>

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

(3)接收参数

<ul>
    <li>消息编号:{{ $route.params.id }}</li>
    <li>消息内容:{{ $route.params.title }}</li>
 </ul>
8、路由的props参数的三种写法

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

(1)props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件

 children: [
            {
              name: 'xiangqing',
              path: 'Detail:/:id/:title',//使用占位符声明接收params参数
              component: Detail,
              //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
              props: { a: 1, b: '你好呀!' }
            }
          ]

接收:

<template>
  <ul>
    <li>a:{{ a }}</li>
    <li>b:{{ b }}</li> 
  </ul>
</template>

<script>
export default {
    name: 'DetailS',
    /* 第一种props参数传递 */
   props: ['a', 'b'], 
  }

(2)props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件

children: [
  {
    name: 'xiangqing',
    path: 'Detail:/:id/:title',//使用占位符声明接收params参数
    component: Detail,
    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    props:true
  }
          ]

接收:params参数

<template>
<ul>
  <li>消息编号:{{ id }}</li>
  <li>消息标题:{{ title }}</li>
  </ul>
</template>

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

(3)props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件

children: [
  {
    name: 'xiangqing',
    path: 'Detail:/:id/:title',//使用占位符声明接收params参数
    component: Detail,  
    //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props($route) {
      return {
        id: $route.query.id,
        title: $route.query.title
      }
    }
  }
          ]

接收:

<template>
  <ul>
    <li>消息编号:{{ id }}</li>
    <li>消息标题:{{ title }}</li>
  </ul>
</template>

<script>
  export default {
    name: 'DetailS',
    /* 第三种props参数传递 */
    props: ['id', 'title']
  }
</script>
9、路由守卫

对路由进行权限控制。

(1)全局路由守卫

//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
  console.log('前置路由守卫', to, from)
  if (to.meta.isAuth) { //判断是否需要鉴权
    if (localStorage.getItem('school') === 'hukeda') {
      next()
    } else {
      alert('学校名不对,无权限查看!')
    }
  } else {
    next()/* 放行 */
  }
})

//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
  console.log('后置路由守卫', to, from)
  document.title = to.meta.title || '测试系统'
})

(2)独享路由守卫

/* HomeMessage组件单独使用的守卫。 */
          beforeEnter: (to, from, next) => {
            console.log('独享路由守卫', to, from)
            if (to.meta.isAuth) { //判断是否需要鉴权
              if (localStorage.getItem('school') === 'hukeda') {
                next()
              } else {
                alert('学校名不对,无权限查看!')
              }
            } else {
              next()
            }
          },

(2)组件内路由守卫

//通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {
  console.log('About--beforeRouteEnter', to, from)
  if (to.meta.isAuth) {
    //判断是否需要鉴权
    if (localStorage.getItem('school') === 'hukeda') {
      next()
    } else {
      alert('学校名不对,无权限查看!')
    }
  } else {
    next()
  }
},
  
  //通过路由规则,离开该组件时被调用
  beforeRouteLeave(to, from, next) {
    console.log('About--beforeRouteLeave', to, from)
    next()
    }

三十、插槽

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

1、默认插槽

在子组件中用slot声明插槽位置

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

父组件中定义了数据。将数据直接放到组件名中间

<组件名>

需要插入到子组件中的html

</组件名>

<template>
  <div class="container">
    <Category title="食物">
      <img
        src="https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF"
        alt=""
      />
    </Category>
    <Category title="游戏">
      <ul>
        <li v-for="(g, index) in games" :key="index">{{ g }}</li>
      </ul>
    </Category>
    <Category title="电影">
      <video
        controls
        src="//video-qn.51miz.com/preview/video/00/00/14/73/V-147399-A6478B1E.mp4"
      ></video>
    </Category>
  </div>
</template>


<script>
  import Category from './components/Category'
  export default {
    name: 'App',
    data() {
      return {
        foods: ['火锅', '烧烤', '小龙虾', '牛排'],
        games: ['LPL', '穿越火线', '消消乐', '超级玛丽'],
        films: ['《盗梦空间》', '《流浪地球》', '《战狼》', '《你好,李焕英》']
      }
    },
    components: { Category }
  }
</script>
2、具名插槽

子组件中需要多个插槽,通过name来区分插入的位置

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!-- 具名插槽,插入多个标签时 -->

    <slot name="center"
      >我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
    >
    <slot name="footer"
      >我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
    >
  </div>
</template>

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

父组件往子组件中插入多个标签时,通过slot="子组件中定义的name值",来区分放的位置

<template>
  <div class="container">
    <Category title="食物">
      <!-- 具名插槽,插入多个标签时 -->
      <img
        slot="center"
        src="https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF"
        alt=""
      />
      <a slot="footer" href="http://www.atguigu.com">更多美食</a>
    </Category>
    <Category title="游戏">
      <div slot="center">
        <ul>
          <li v-for="(g, index) in games" :key="index">{{ g }}</li>
        </ul>
      </div>
      <div class="foot" slot="footer">
        <a href="">点击</a>
        <a href="">点击</a>
        <a href="">点击</a>
      </div>
    </Category>
    <Category title="电影">
      <video
        slot="center"
        controls
        src="//video-qn.51miz.com/preview/video/00/00/14/73/V-147399-A6478B1E.mp4"
      ></video>
      <a href="" slot="footer">加载</a>
    </Category>
  </div>
</template>


<script>
  import Category from './components/Category'
  export default {
    name: 'App',
    components: { Category },
    data() {
      return {
        foods: ['火锅', '烧烤', '小龙虾', '牛排'],
        games: ['LPL', '穿越火线', '消消乐', '超级玛丽'],
        films: ['《盗梦空间》', '《流浪地球》', '《战狼》', '《你好,李焕英》']
      }
    }
  }
</script>
3、作用域插槽

理解:数据在(子)组件的自身,但根据数据生成的结构需要组件的(父组件)使用者来决定。(foods数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

需要给子组件中的数据命名:foodss="foods"好在父组件中调用这些数据

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
     <slot :foodss="foods" msg="hello"
      >我是一些默认值,当使用者没有传递具体结构时,我会出现</slot
    >
  </div>
</template>

<script>
  export default {
    name: 'CategoryS',
    props: ['list', 'title'],
    data() {
      return {
        foods: ['火锅', '烧烤', '小龙虾', '牛排']
      }
    }
  }
</script>

父组件必须用放在template,并通过scope="hahha"声明,然后通过hahha.foodss调用数据

<template>
<div class="container">
  
  
  <Category title="食物">
    <!-- 作用域插槽,只能包在template中使用,要用scope-->
       <template scope="hahha">
        <ul>
           <li v-for="(g, index) in hahha.foodss" :key="index">{{ g }}</li>
        </ul>
       </template>
   </Category>


<Category title="食物">
  <template scope="{foodss}">
   <ol>
     <li style="color: red" v-for="(g, index) in foodss" :key="index">
       {{ g }}
      </li>
      </ol>
   </template>
</Category>



<Category title="食物">
  <template slot-scope="{ foodss }">
     <h4 v-for="(g, index) in foodss" :key="index">{{ g }}</h4>
  </template>
</Category>



</div>
</template>

快速入门vue

1、常用指令

(1)v-bind:

v-bind:属性名="表达式"

设置元素的属性。(比如:src,title,clas)

简写:省略v-bind,只保留 :属性名

<div id="app">
    <img v-bind:src="imgSrc" :title="imgTitle+'!!!'">
    <br>
    <img :src="imgSrc" alt=""><!-- 简写 -->
    <br>
    <!-- v-bind控制class类 三目表达式" a?a,c" -->
    <img v-bind:src="imgSrc" :title="imgTitle+'!!!'" :class="isAction?'active':''" @click="toAtion">
    <br>
    <!-- 对象形式 -->
    <img v-bind:src="imgSrc" :title="imgTitle+'!!!'" :class="{active:isAction}}" @click="toAtion">

  </div>
  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#app',
      data: {
        imgSrc: 'D:/web/vue/img/favicon.ico',
        imgTitle: '你好呀',
        isAction: true
      },
      methods: {
        toAtion: function () { //改变isAction的值,true或者false
          this.isAction = !this.isAction
        }
      },
    })
  </script>
(2)v-text:

设置标签的内容(textContent)

默认写法会替换全部内容,使用差值表达式{{}}可以替换指定内容

v-text指令无论内容是什么,只会解析为文本。

 <div id="demo">
    <h1>
      小希,{{message+"!!"}}
    </h1>
    <h1 v-text="message+'!!'">
      小希
      <!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
    </h1>
  </div>
  <script>
    Vue.config.productionTip = false
    new Vue({
      el: '#demo',
      data: {
        message: '你好!'
      }
    })
  </script>
(3)v-html:

设置元素的innerhtml属性

内容之中有标签属性会被解析出来

解析文本使用v-text,需要解析html结构使用v-html

<div id="demo">
    <h1 v-text="message">
      小希
      <!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
    </h1>
    <!-- v-text与v-html区别:v-text全部显示;v-html解析出来 -->
    <h1 v-text="content">
      小希
      <!-- 这里没有显示出来,修改部分不能用这种方式,要用插值语句 -->
    </h1>
    <!-- v-html -->
    <h1 v-html="content"></h1>
  </div>
  <script>
    Vue.config.productionTip = false
    new Vue({
      el: '#demo',
      data: {
        message: '你好!',
        content: "<a href='https://www.baidu.com/'>百度</a>"
      }
    })
  </script>
(4)v-on:

为元素绑定事件

绑定的方法定义在methods属性中;

方法内部通过this关键字可以访问定义在data中数据;

简写@事件名 ;

事件绑定的方法写成函数调用的形式,可以传入自定义参数;

事件的后面跟上 .修饰符 可以对事件进行限制;

.enter 可以限制触发的按键为回车;

  <div id="demo">
    <input type="button" value="点击事件" v-on:click="doIt">
    <input type="button" value="v-on简写" @click="doIt">
    <input type="button" value="双击事件" @dblclick="doIt">
    <!-- 不用更改dome元素,而是更改vue实例中的数据,就可以更改页面的内容 -->
    <h1 @click="changeFood">{{food}}</h1>
  </div>
  <script>
    Vue.config.productionTip = false
    new Vue({
      el: '#demo',
      data: {
        food: '马铃薯'
      },
      methods: {
        doIt: function () {
          alert('你好')
        },
        changeFood: function () {
          this.food += '好吃!'
        }
      },
    })
  </script>
<div id="app">
    <!-- v-on传参 -->
    <input type="button" value="点击" @click="doIt(666,'老铁')">
    <!-- v-on:keyup.enter回车事件 -->
    <input type="text" @keyup.enter="sayHi">
  </div>

  <script>
    Vue.config.productionTip = false
    var app = new Vue({
      el: "#app",
      methods: {
        doIt: function (p1, p2) {
          console.log("做it");
          console.log(p1);
          console.log(p2);
        },
        sayHi: function () {
          alert("吃了没");
        }
      },
    })
  </script>
(5)v-show:

根据真假切换元素的显示状态;

原理是修改元素的display,实现显示隐藏;

值为true元素显示,值为false元素隐藏;

数据改变之后,对应元素的显示状态会同步更新。

<div id="root">
    <img src="../img/favicon.ico" v-show="isShow">
    <button @click="changeIsShow">点击切换显示</button>
    <br><br>
    <button @click="addAge">点击增加年龄</button>
    <img src="../img/favicon.ico" v-show="age>18"><!-- 年龄大于18时才显示图片 -->


  </div>

  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#root',
      data: {
        isShow: true,
        age: 17,
      },
      methods: {
        /* 切换图片显示状态 */
        changeIsShow: function () {
          this.isShow = !this.isShow
        },
        /* 年龄增加方法 */
        addAge: function () {
          this.age++;

        }
      },
    })
  </script>
(6)v-if:

根据表达式的真假切换元素的显示状态;

本质是通过操纵dom元素来切换显示状态;

表达式的值为true,元素存在于dom树中,为false,从dom树中移除;

频繁的切换v-show,反之使用v-if,前者的切换消耗小。

<div id="root">
    <!-- 
      v-show:根据表达式的真假切换元素的显示状态,本质是修改元素额display,实现显示与隐藏;
      v-if:根据表达式的真假切换元素的显示状态,本质是操作dom元素切换显示状态
     -->
    <img src="../img/favicon.ico" v-if="isShow">
    <button @click="changeIsShow">点击切换显示</button>
    <img src="../img/favicon.ico" v-show="isShow">


  </div>

  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#root',
      data: {
        isShow: true,
      },
      methods: {
        /* 切换图片显示状态 */
        changeIsShow: function () {
          this.isShow = !this.isShow
        }
      },
    })
  </script>
(7)v-for:

v-for:循环指令,基于一个数组或者对象渲染一个列表;

根据数据生成列表结构;

数组经常和v-for结合使用;

语法是( item,index ) in 数组名;

数组长度的更新会同步到页面上,是响应式的

<div id="app">
    <ul>
      <!-- item占位,数组里有几个值,就显示几个,,自定义名称
        inde表示数组的序号,自定义名称 -->
      <li v-for="(item,inde) in arr">{{inde+1}}你好呀:{{item}}</li>
    </ul>
    <br>
    <input type="button" value="增加菜" @click="add">
    <input type="button" value="减少菜" @click="remeove">


    <h2 v-for="intem in vegetables" v-bind:title="intem.name">吃:{{intem.name}}</h2>


  </div>
  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#app',
      data: {
        arr: ['北京', '上海', '苏州'],
        vegetables: [{
            name: '土豆'
          },
          {
            name: '洋芋'
          },
          {
            name: '马铃薯'
          },

        ]
      },
      methods: {
        add: function () {
          this.vegetables.push({
            name: '还有啥'
          });
        },
        remeove: function () {
          this.vegetables.pop();
        }
      },
    })
  </script>
(8)v-model:

实现表单输入和应用状态之间的双向绑定(绑定的数据会和表单元素值相关联)

便捷的设置和获取表单元素的值;

<div id="app">
    <input type="button" value="修改message" @click="setM">
    <!-- v-model同步进行 -->
    <input type="text" v-model="message" @keyup.enter="getM">
    <h2>{{ message }}</h2>
  </div>

  <script>
    Vue.config.productionTip = false
    var app = new Vue({
      el: "#app",
      data: {
        message: "小希"
      },
      methods: {
        getM: function () {
          alert(this.message);
        },
        setM: function () {
          this.message = "你好";
        }
      },
    })
  </script>

2、案例

(1)记事本案例

主要功能:

最终成果:

<section id="todoapp">

  <header>

    <h1>小希记事本</h1>
    <!--     <a
      href="https://weathernew.pae.baidu.com/weathernew/pc?query=%E6%B1%9F%E8%8B%8F%E8%8B%8F%E5%B7%9E%E5%A4%A9%E6%B0%94&srcid=4982"><input
        class="weather" type="button" value="查询天气"></input></a> -->
    <input v-model="inputValue" @keyup.enter="add" autofocus="autofocus" placeholder="请输入任务" class="new-todo" />
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo" v-for="(item,index) in list">
        <!-- 序号 -->
        <span class="index">{{ index+1 }}.</span>
        <label>{{ item }}</label>
        <button class="destroy" @click="remove(index)"></button>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer">
    <span class="todo-count" v-show="list.length!=0">
      <strong>{{ list.length }}</strong> 个计划
    </span>
    <button class="clear-completed" v-show="list.length!=0" @click="clear">
      Clear
    </button>
  </footer>


</section>
<!-- 底部天气 -->
<footer class="info">
  <p>
    你好

  </p>
</footer>

<body>
  <script>
    Vue.config.productionTip = false
    new Vue({
      el: '#todoapp',
      data: {
        list: [
          'html', 'css', 'js'
        ],
        inputValue: "好好学习,天天向上"
      },
      methods: {
        add: function () {
          this.list.push(this.inputValue);
        },
        remove: function (index) {
          console.log("删除");
          console.log(index);
          this.list.splice(index, 1);
        },
        clear: function () {
          this.list = [];
        }
      },

    })
  </script>

css:

body {
  font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
  line-height: 1.4em;
  background: #fff;
  color: #4d4d4d;
  min-width: 230px;
  max-width: 550px;
  margin: 0 auto;
  font-weight: 300;
  
}
#todoapp {
  background: #fff;
  margin: 180px 0;
  position: relative;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1), 0 25px 50px 0 rgba(0, 0, 0, 0.2);
}
#todoapp h1 {
  position: absolute;
  top: -160px;
  width: 100%;
  font-size: 60px;
  font-weight: 100;
  text-align: center;
  color: rgba(175, 47, 47, .8);

}
.weather {
  position: absolute;
  top: -160px;
  right: -260px;
  font-size: 30px;
  font-weight: 100;
  text-align: center;
  color: rgba(0, 47, 47, .8);

}
.todo-list {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 500px;
  overflow: auto;
}
.todo-list li {
  position: relative;
  font-size: 20px;
  margin: 0 0 0 20px;
  border-bottom: 1px solid #ededed;
  height: 60px;
  line-height: 60px;
  box-sizing: border-box;
 
}

/* 输入框初始显示样式 */
.new-todo {
  position: relative;
  margin: 0;
  padding: 16px;
  width: 100%;
  font-size: 20px;
  line-height: 1.4em;
  border: 0;
  color: rgba(20, 20, 20, 0.2);
  box-shadow: inset 0 -2px 1px 0 rgba(0, 0, 0, 0.2);
  box-sizing: border-box;
  word-wrap:break-word;
}
.index{
  margin: 0px 10px;
}

/* 去掉button样式 */
button {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: 100%;
  vertical-align: baseline;
  font-family: inherit;
  font-weight: inherit;
  color: inherit;
  -webkit-appearance: none;
  appearance: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.todo-list li .destroy {
  display: none;
  position: absolute;
  top: 0;
  right: 10px;
  bottom: 0;
  width: 40px;
  height: 40px;
  margin: auto 0;
  font-size: 30px;
  color: #cc9a9a;
  margin-bottom: 11px;
  transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
  color: #af5b5e;
}
/* 删除样式 */
.todo-list li .destroy:after {
  content: "×";
}
/*  鼠标悬浮*/
.todo-list li:hover .destroy {
  display: block;
}

/* 清空列表 */
.clear-completed,
html .clear-completed:active {
  float: right;
  position: relative;
  line-height: 20px;
  text-decoration: none;
}

.clear-completed:hover {
  color: #b84c50;

}
/* 统计标签栏 */
.todo-count {
  float: left;
  text-align: left;
}

.todo-count strong {
  font-weight: 300;
}
/* 最后一栏样式 */
.footer {
  color: #777;
  padding: 10px 15px;
  height: 20px;
  text-align: center;
  border-top: 1px solid #e6e6e6;
}

(2)图片切换

<div id="mask">
    <div class="center">
      <!-- 通过数组编号调用数组中的图片 -->
      <img :src="imgArr[index]" alt="">
      <!-- 左箭头 -->
      <!-- <a href="javascript:void(0)" v-show="index!=0" @click="prev" class="left">
        <img src="../img/prev.png" alt="" />
      </a> -->
      <a href="javascript:void(0)" v-if="index!=0" @click="prev" class="left">
        <img src="../img/prev.png" alt="" />
      </a>
      <!-- 右箭头 -->

      <a href="javascript:void(0)" v-show="index<imgArr.length-1" @click="next" class="right">
        <img src="../img/next.png" alt="" />
      </a>

    </div>
  </div>
  <script>
    Vue.config.productionTip = false
    const vm = new Vue({
      el: '#mask',
      data: {
        imgArr: [
          '../img/01.jfif',
          '../img/02.jpg',
          '../img/03.jpg',
          '../img/04.jpg',
          '../img/05.jpg',
          '../img/06.jpg',
          '../img/07.jpg',
          '../img/08.jpg',
          '../img/09.jpg',
        ],
        index: 0
      },
      methods: {
        /* 前一张 */
        prev: function () {
          this.index--;
        },
        /* 后一张 */
        next: function () {
          this.index++;
        }
      },

    })
  </script>
(3)计数器

<div id="app">
    <button @click="sub">-</button>
    <span>{{num}}</span>
    <button @click="add">+</button>
  </div>
  <script>
    Vue.config.productionTip = false

    const vm = new Vue({
      el: '#app',
      data: {
        num: 1
      },
      methods: {
        /* 加1方法 */
        add: function () {
          if (this.num < 10) {
            this.num++
          } else {
            alert('别点击了,这是最大了')
          }
        },
        /* 减1方法 */
        sub: function () {
          if (this.num > 0) {
            this.num -= 1;
          } else {
            alert('别点击了,这是最小了')
          }
        }
      },
    })
  </script>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值