Vue 全套教程(三),入门 Vue 必知必会

本文详细讲解Vue的数据监听机制,包括对象和数组的监视,v-model指令的使用和特性,以及过滤器和内置指令的深入剖析。涵盖数据劫持、响应式编程和表单处理技巧。

Vue(三)

链接地址:
Vue 全套教程(一)
Vue 全套教程(二)

一、监视数据原理

Vue 会监视 data 中所有层次的数据,自动的给每个元素添加 getter、setter。也就是说,自定义的 data 数据在变成 Vue 实例的 _data 时会给每个元素自动添加 getter、setter(数据劫持)

基本原理:Vue 会汇总 data 中的所有属性形成一个数组,然后遍历数组中的所有元素,给每一个元素添加 getter 和 setter,setter 方法监视到 _data 中的数据发生变化时,会自动的重新解析模板,更新页面中的数据。

注意:

  1. 这里所说的 getter、setter 指的并不是数据代理,而是 _data 中的每个属性也都会有 getter、setter,如下所示:

    代码演示:

    <body>
        <div id="root">
            <h2>名称:{{name}}</h2>
    	</div>
    </body>
    
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                name:'Jay'
            }
        })
    	console.log(vm)
    </script>
    

    控制台输出vm后的结果如下图:

    image-20220618165727805
  2. 数据代理指的是 Vue 实例的数据如何影响 _data 中的数据。而数据劫持指的是如何将 _data 中的数据改动体现到页面中。

  3. 对于上图,当执行 vm.name = '新名字' 时,首先会通过红框中的 setter 方法修改 _data 中的数据(数据代理),然后自动的通过蓝框中的 setter 修改页面上的值(数据劫持)。

1.1 监视对象中的数据

首先需要明确,Vue 只会对 new Vue 时就存在的 data 数据做数据劫持,后添加的数据默认不会做数据劫持(修改数据的值不会同步到页面上),如下所示:

代码演示:

<body>
    <div id="root">
        <h2>名称:{{student.name}}</h2>
        <!-- 引用的是初始化时不存在的属性 -->
        <h2>性别:{{student.sex}}</h2>
    </div>
</body>

<script type="text/javascript">
    const vm = new Vue({
        el:'#root',
        data:{
            student:{
                name: 'Jay'
            }
        }
    })
</script>

运行结果:

image-20220618174914491

控制台输出 vm._data 后发现,新添加的 sex 属性并没有做数据劫持(没有添加 getter、setter 方法),所以 _data 数据的变化并没有通过 setter 重新解析到页面上,如下图:

image-20220621085621590
1.1.1 Vue.set()方法

该方法用来给后添加的属性做响应式处理(可以理解为做数据劫持,添加 getter、setter 方法)

使用方式1:

Vue.set(要添加属性的对象,要添加的属性名/数组索引值,属性对应的值)

使用方式2:

vm.$set(要添加属性的对象,要添加的属性名/数组索引值,属性对应的值)

对上述运行结果做代码演示:

image-20220618174202106

控制台输出 vm._data 后发现新增的 sex 属性被成功的添加 getter 和 setter 方法,后续通过 vm._data.student.sex = "女" 也可以修改页面的值(通过 setter 方法重新解析模板),如下图所示:

image-20220618174619426

注意:Vue.set() 方法不可以直接用来给 根data 或 根vm实例 添加属性,即第一个参数不能写成 vm._datavm

1.2 监视数组中的数据

如果 data 中的属性是一个数组,那么监视数组的变化首先需要数组本身发生变化,比如调用 sort、reverse 等方法,Vue 会对这些方法进行包装,自动的先将数组本身改变,然后重新解析模板,渲染新数组的数据。

可以使得数组本身发生变化的方法:

  • push:在数组的最后添加一个元素
  • pop:删除数组的最后一个元素
  • shift:删除数组的第一个元素
  • unshift:在数组的最前面添加一个元素
  • splice:在数组的指定位置进行插入、删除、替换等操作
  • sort:对数组进行排序
  • reverse:反转数组

注意:

  1. 无法使数组本身发生变化的方法,比如 filter 方法,更改的数据想要在页面中展示出来,需要使用新数组替换旧数组。
  2. Vue.set() 可以通过索引值给数组元素做响应式处理。

1.3 总结

代码演示:

要求各个按钮完成按钮文字的功能:

<body>
    <div id="root">
        <h3>个人信息</h3>
        <button @click="student.age++">年龄+1岁</button> <br/>
        <button @click="addSex">添加性别属性,默认值:男</button> <br/>
        <button @click="addFriend">在列表首位添加一个朋友</button> <br/>
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
        <button @click="addHobby">末尾添加一个爱好:学习</button> <br/>
        <button @click="updateHobby">修改第二个爱好为:开车</button> <br/>
        <button @click="removeSmoke">过滤掉爱好中的烫头</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">
    const vm = new Vue({
        el:'#root',
        data:{
            student:{
                name:'tom',
                age:18,
                hobby:['抽烟','喝酒','烫头'],
                friends:[
                    {name:'jerry',age:35},
                    {name:'tony',age:36}
                ]
            }
        },
        methods: {
            addSex(){
                this.$set(this.student,'sex','男')
            },
            addFriend(){
                this.student.friends.unshift({name:'jack',age:70})
            },
            updateFirstFriendName(){
                // 数组的元素是对象时,会给对象中的所有属性做数据劫持
                this.student.friends[0].name = '张三'
            },
            addHobby(){
                this.student.hobby.push('学习')
            },
            updateHobby(){
                // this.student.hobby.splice(0,1,'开车')

                // 通过Vue.set方法也可以修改数组的值

                // Vue.set(this.student.hobby,1,'开车')
                this.$set(this.student.hobby,1,'开车')
            },
            removeSmoke(){
                this.student.hobby = this.student.hobby.filter((h)=>{
                    return h !== '烫头'
                })
            }
        }
    })
</script>

运行结果(未点击按钮):

image-20220619105327425

运行结果(点击所有按钮):

image-20220619105436290

运行结果(数据代理):

image-20220619105835426

可以发现,对于自定义的数据,Vue 实例只会对最外层的 student 添加 getter 和 setter 方法(student 任意层次数据发生改变,都会导致 _data 中的数据发生改变),而不会对所有的深层属性都添加 getter 和 setter。

运行结果(数据劫持—hobby):

image-20220619110234760

hobby 是一个数组,所以没有 setter 和 getter,如果想要页面的数据发生变化,则需要修改数组本身。

运行结果(数据劫持—friends):

image-20220619110714004

friends 虽然是一个数组,但是其中的值是对象,Vue 会给对象中的属性做数据劫持,所以直接通过上述的代码 this.student.friends[0].name = '张三' ,也可修改页面的数据。

二、再谈v-model指令

2.1 收集表单数据

收集用户输入的数据可以通过 v-model 双向绑定,会分为以下几种情况:

  • 若为 <input type="text"/> 等文本输入框类型,由于用户的输入默认赋值的就是 value 属性,故 v-model 收集的是用户直接输入的数据。
  • 若为 <input type="radio"/> 单选框,则需要手动给该标签添加 value 属性,v-model 收集的是该标签的 value 属性值。
  • 若为 <input type="checkbox"/> 多选框:
    • 没有配置 value 属性,那么 v-model 收集的是布尔类型的 checked 属性(是否被选中)。
    • 已经配置 value 属性:
      • v-model 绑定的 data 属性的初始类型非数组,那么 v-model 收集的是布尔类型的 checked 属性(是否被选中)。
      • v-model 绑定的 data 属性的初始类型为数组,那么 v-model 收集的是所有多选框的 value 属性值所组成的数组。

代码演示:

<body>
    <div id="root">
        <!-- 给表单绑定一个提交事件,并且阻止默认的跳转页面行为去执行demo函数 -->
        <form @submit.prevent="demo">
            账号:<input type="text" v-model="userInfo.account"> <br/><br/>
            年龄:<input type="number" v-model="userInfo.age"> <br/><br/>

            性别:
            男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
            女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>

            爱好---data初始非数组:
            <!-- data初始非数组 -->
            学习1<input type="checkbox" v-model="userInfo.hobby2" value="study">
            学习2<input type="checkbox" v-model="userInfo.hobby2" value="study">
            <br/><br/>

            爱好---data初始为数组:
            <!-- data初始为数组 -->
            学习<input type="checkbox" v-model="userInfo.hobby1" value="study">
            打游戏<input type="checkbox" v-model="userInfo.hobby1" value="game">
            吃饭<input type="checkbox" v-model="userInfo.hobby1" value="eat">
            <br/><br/>

            所属校区
            <!-- select下拉框默认v-model获取的是value属性值 -->
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">武汉</option>
            </select>
            <br/><br/>

            其他信息:
            <textarea v-model="userInfo.other"></textarea> <br/><br/>

            <!-- 不需要获取value值,只需要判断布尔值即可 -->
            <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>

            <!-- 表单中的按钮会自动触发提交事件 -->
            <button>提交</button>
        </form>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                age:18,  //默认填入18
                sex:'female', //默认取值为女
                hobby1:[], //初始化为数组
                hobby2:'',
                city:'beijing', //默认选中北京
                other:'',
                agree:''
            }
        },
        methods: {
            demo(){
                alert(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

运行结果:

image-20220619160853230

注意:

  1. <input type="number"/> 标签, 可以控制输入框只能输入数字,并且自动添加增减数字选项,如图所示:image-20220619161334697
  2. 上图中数字的取值为字符串,并非数字格式

2.2 三个修饰符

  • lazy:输入框失去焦点后再收集数据,并非实时收集数据
  • number:输入的字符串转为有效的数字
  • trim:输入的数据首尾空格过滤

代码演示:

<body>
    <div id="root">
        <form>
            账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            其他信息:<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
        </form>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                age:'',
                other:''
            }
        }
    })
</script>

运行结果:

image-20220619172542016
  1. 输入字符之前、之后的空格被自动删除,但是字符中的空格不会被删除
  2. 输入的数字转为数字格式而不是字符串
  3. 其他信息会全部输入并且失焦后才会获取到值

三、过滤器

定义:

过滤器(函数)用来对数据进行处理后再显示。

用法:

  • 注册过滤器:
    • 全局:Vue.filter(过滤器名称,过滤器处理数据的函数)
    • 局部:new Vue{filters:{过滤器1名称(参数){处理过程},过滤器2...}}
  • 使用过滤器:
    • 插值语句:{{要处理的数据 | 过滤器名称}}
    • 单向绑定:v-bind:属性 = "要处理的数据 | 过滤器名称"

处理过程:

  1. 要处理的数据自动的作为参数传递给过滤器函数,过滤器函数的返回值替换整个插值表达式或属性值。
  2. 过滤器是不需要传递实参的,自动的将要处理的数据作为参数传递给过滤器函数。
  3. 过滤器函数也可以接收额外的参数,但是第一个形参永远是要处理的数据。

代码演示:

<body>
    <div id="root">
        <!-- 过滤器函数不使用参数 -->
        <h3>现在是:{{msg | Formater1}}</h3>
        <!-- 过滤器函数传参 -->
        <h3>现在是:{{msg | Formater2('一路向北')}}</h3>
    </div>

    <div id="root2">
        <h2>{{msg | mySlice}}</h2>
    </div>
</body>

<script type="text/javascript">

    //全局过滤器,多个容器都可以使用
    Vue.filter('mySlice',function(value){
        return value.slice(0,4)
    })

    new Vue({
        el:'#root',
        data:{
            msg:'Jay111'
        },
        //局部过滤器,仅供div标签为root的容器使用
        filters:{
            // 不传递实参默认value获取的是要处理的数据
            Formater1(value){
                return value.slice(0,3)
            },
            // 第一个参数永远是要处理的数据,第二个参数为传递的参数
            Formater2(value, str){
                return value + str
            }
        }
    })

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

运行结果:

image-20220620222014988

注意:

  1. 过滤器并没有改变原本的数据,而是产生新的数据替换原位置的值。

  2. 多个过滤器可以串联使用,如下列代码所示:

    {{msg | Formater1 | mySlice}},要被修改的数据 msg 作为参数传递给 Formater1 过滤器,Formater1 过滤器的返回值作为参数传递给 mySlice 过滤器。

四、指令

4.1 内置指令

4.1.1 v-text

向其所在的标签渲染指定的文本内容。

注意:

  1. 与插值表达式的区别:v-text 会替换标签中的所有内容,而插值表达式不会替换。
  2. 该指令仅将所有数据渲染为文本,不会解析标签。

代码演示:

<body>
    <!-- 准备好一个容器-->
    <div id="root">
        <div>没有被替换,{{name}}</div>
        <div v-text="name">会被覆盖掉哦</div>
        <div v-text="str"></div>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            name:'Jay',
            str:'<h3>你好啊!</h3>'
        }
    })
</script>

运行结果:

image-20220621202721663
4.1.2 v-html

向其所在的标签渲染指定的文本内容,与 v-text 的不同是,会解析标签。

代码演示:

<body>
    <div id="root">
        <div v-html="str">被替换掉了哦</div>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            str:'<h3>你好啊!</h3>',
        }
    })
</script>

运行结果:

image-20220621203110931

注意:由于 v-html 会解析标签,所以存在一定的安全隐患,容易导致 XSS 攻击。

4.1.3 v-cloak

v-cloak 没有属性值,Vue 实例在创建完毕并接管容器之后,会自动的删除所有 v-cloak 属性。

作用:

有时 JS 文件引入慢时,会出现 JS 阻塞现象,即 Vue 实例并不会立即创建,某些情况下页面上的标签会先展示出来,导致如插值表达式没有被解析就展示在了页面上。

使用 v-cloak 指令配合 css 样式的 display:none 属性,可以避免页面出现未经解析的模板。

代码演示:

思路:给包含 v-cloak 属性的所有标签使用 css 样式的 display:none 属性,Vue 实例没有被加载出来之前,样式被隐藏,不会展示在页面上。当 Vue 实例加载之后,v-cloak 属性消失,css 样式的 display:none 属性失效,成功将解析后的结果展示在页面上。

<head>
    <meta charset="UTF-8" />
    <title>v-cloak指令</title>
    <!-- 隐藏属性的样式 -->
    <style>
        /* 中括号表示所有使用v-cloak属性的标签 */
        [v-cloak]{
            display:none;
        }
    </style>
</head>
<body>
    <div id="root">
        <h2 v-cloak>{{name}}</h2>
    </div>
    <!-- 先展示结果,后引入 JS 标签,假设由于网速该 JS 标签在5秒之后才会被引入,会导致页面加载未解析的插值表达式 -->
    <script type="text/javascript" src="https://xxx.vue.js"></script>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            name:'Jay'
        }
    })
</script>

运行结果:

页面空白5秒之后,显示 Jay,不会出现未解析的模板。

4.1.4 v-once

v-once 没有属性值,v-once 所在的标签在初次动态渲染之后,就视为静态内容,之后的数据不会再改变。

代码演示:

<body>
    <div id="root">
        <h2 v-once>初始化的n值是:{{n}}</h2>
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>

运行结果:

点击了5次按钮:

image-20220621225339906
4.1.5 v-pre

v-pre 没有属性值,v-pre 可以让其所在的标签跳过 Vue 的解析,如果使用在没有指令语法或插值表达式的标签中,可以加快编译过程。

代码演示:

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

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>

运行结果:

点击了5次按钮:

image-20220621230112833

4.2 自定义指令

Vue 除了内置指令之外支持使用者自定义指令,当调用自定义指令时完成某一操作,自定义指令需要使用 directives 关键字。

4.2.1 函数式

定义:

函数需要定义两个参数,参数1表示使用自定义指令所在标签的所有信息,参数2表示指令与绑定数据的一些信息。

<body>
    <div id="root">
        <h2>
            <!-- 在需要使用的位置使用 v-函数名 即可完成big函数内的功能 -->
            <span v-big="n"></span>
        </h2>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            n:1
        },
        // 自定义指令
        directives:{
            big(element,binding){
                console.log(element,binding) 
                element.innerText = binding.value * 10
            }
        }
    })
</script>

运行结果:

页面会显示10,控制台输出:

image-20220623192915839

注意:

  1. 自定义函数被调用的时间:
    • 指令与标签第一次成功绑定时(绑定不代表会显示在页面上,绑定是内存操作)
    • 指令所在的模板(div标签中的内容)被重新解析时
  2. 指令定义时不加 v-,使用时要加 v-
4.2.2 对象式

函数式的缺点是无法在指定的时间进行某一操作。

使用对象式的方式,需要在值中定义三个函数:

  • bind(element,binding){}:指令与标签成功绑定时会自动调用
  • inserted(element,binding){}:指令所在的标签被插入页面时会自动调用
  • update(element,binding){}:指令所在的模板被重新解析时会自动调用

代码演示:

需求:输入框在页面初始展示时自动获取焦点,点击按钮之后,输入框内的值加一并获取焦点。

注意:不能使用函数式,因为函数式在第一次绑定成功(仅在内存中)就会调用函数获取焦点,这个时候页面上并没有输入框,所以无法获取焦点,必须要等到页面中有元素才可以获取焦点。

<body>
    <div id="root">
        <!-- 使用自定义指令v-fbind -->
        <input type="text" v-fbind:value="n">
        <button @click="n++">点我n+1</button>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            n:1
        },
        directives:{
            fbind:{
                //指令与标签成功绑定时会自动调用
                bind(element,binding){
                    element.value = binding.value
                }, 
                //指令所在的标签被插入页面时会自动调用
                inserted(element,binding){
                    //获取焦点
                    element.focus()
                },
                //指令所在的模板被重新解析时会自动调用
                update(element,binding){
                    element.value = binding.value
                    element.focus()
                }
            }
        }
    })
</script>
4.2.3 全局指令

局部方式仅指定的 div 标签(容器)可以使用,全局指令可以让所有容器都可以使用。

代码演示:

// 对象式
Vue.directive('fbind',{
    bind(element,binding){
        element.value = binding.value
    },
    inserted(element,binding){
        element.focus()
    },
    update(element,binding){
        element.value = binding.value
    }
}) 

//函数式
Vue.directive('fbind',function(element,binding) {
    console.log(element,binding) 
}) 

注意:

  1. 自定义指令的命名方式非驼峰式,而是不同的单词使用 ‘-’ 分隔开,比如:

    //使用指令:
    v-big-number
    
    //定义指令:
    'big-number'(element,binding){
        console.log(element, binding)
    },
    

    由于定义函数时,JS 简写形式无法识别 ‘-’,所以函数名必须使用加单引号的非简写形式。

  2. 不论函数式还是对象式,函数中的 this 均表示 window 非 vm(指令不需要Vue管理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nice2cu_Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值