1. Vue常用特性

1.1 表单输入绑定

Vue全家桶之Vue基础(1) 一文中我们已经学习过 v-model 的用法。我们可以用 v-model 指令在表单 <input><textarea><select> 元素上创建 双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。如下图所示:
在这里插入图片描述
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  1. text 和 textarea 元素使用 value property 和 input 事件
  2. checkbox 和 radio 使用 checked property 和 change 事件
  3. select 字段将 value 作为 prop 并将 change 作为事件

对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。
在这里插入图片描述
在后续关于表单输入绑定操作中用到的 js 代码如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            message: "", //文本以及多行文本
            checked: false, //单个复选框绑定到bool值
            checkedNames: [], //多个复选框绑定到同一个数组
            picked: "", //单选按钮
            selected: "", //选择框单选
            selected2: [], //选择框多选
            selected3: "A", //动态渲染选项
            options: [{
                text: 'One',
                value: 'A'
            }, {
                text: 'Two',
                value: 'B'
            }, {
                text: 'Three',
                value: 'C'
            }]
        }
    });
</script>

文本

<!-- placeholder:向用户显示描述性说明或者提示信息 -->
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

演示效果如下:
在这里插入图片描述
多行文本

<span>Multiline message is:</span>
<!-- white-space: pre-line: 合并空白符序列,但是保留换行符。 -->
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

演示效果如下:
在这里插入图片描述
在文本区域插值 <textarea>{{text}}</textarea>) 并不会生效,应用 v-model 来代替。

单个复选框,绑定到布尔值

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

演示效果如下:
在这里插入图片描述
多个复选框,绑定到同一个数组

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>

演示效果如下:
在这里插入图片描述
单选按钮

<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>

演示效果如下:
在这里插入图片描述
补充:

  1. <label> 标记用于在表单元素中定义标签,这些标签可以对其他一些表单控件元素(如单行文本框、密码框等)进行说明。
  2. <label> 标记可以指定 id/style/class 等核心属性,也可以指定 onclick 等事件属性。除此之外,<label> 标记还有一个 for 属性,该属性指定 <label> 标记与哪个表单控件相关联。
  3. 虽然 <label> 标记定义的标签只是输出普通的文本,但 <label> 标记生成的标签还有一个另外的作用,那就是当用户单击 <label> 生成的标签时,和该标签关联的表单控件元素就会 获得焦点。也就是说,当用户选择
  4. 使标签和表单控件相关联主要有两种方式:
    1. 隐式关联
      使用 for 属性,指定 <label> 标记的 for 属性值为所关联的表单控件的 id 属性值。
    2. 显式关联
      将普通文本、表单控件一起放在

选择框单选时

 <select v-model="selected">
   <option disabled value="">请选择</option>
   <option>A</option>
   <option>B</option>
   <option>C</option>
 </select>
 <span>Selected: {{ selected }}</span>

演示效果如下:
在这里插入图片描述
如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为 未选中 状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS不会触发 change 事件。因此,更推荐像上面这样提供一个 值为空的禁用选项

多选时(绑定到一个数组):

<!-- multiple: 表示列表/菜单内容可多选 -->
<select v-model="selected2" multiple style="width: 50px;">
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<br>
<span>Selected: {{ selected2 }}</span>

演示效果如下:
在这里插入图片描述
v-for 渲染的动态选项:

<select v-model="selected3">
	<option v-for="option in options" v-bind:value="option.value">
	  {{ option.text }}
	</option>
</select>
<span>Selected: {{ selected3 }}</span>

演示效果如下:
在这里插入图片描述
修饰符:
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

<!-- 在"change"时而非"input"时更新 -->
<input v-model.lazy="msg">

如以下例子所示:
在这里插入图片描述
演示效果如下:
在这里插入图片描述
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

1.2 自定义指令

为何需要自定义指令?

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是 组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下:
在这里插入图片描述
当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:

//注册一个全局指令focus
Vue.directive("focus", {
    //当被绑定的元素插入到DOM中时
    inserted: function(el) {
        //聚焦元素
        el.focus();
    }
})

自定义指令用法:
在这里插入图片描述
一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  5. unbind:只调用一次,指令与元素解绑时调用。

接下来我们来看一下钩子函数的参数 (即 el、binding、vnode 和 oldVnode)。 指令钩子函数会被传入以下参数:

  1. el:指令所绑定的元素,可以用来直接操作 DOM。
  2. binding:一个对象,包含以下 property:
    1. name:指令名,不包括 v- 前缀。
    2. value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    3. oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    4. expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    5. arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    6. modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  3. vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  4. oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset来进行。 下面通过一个例子来说明:
在这里插入图片描述
演示效果如下:
在这里插入图片描述
如果想注册局部指令,组件中也接受一个 directives 的选项:

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

1.3 计算属性

1.3.1 基本使用

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多包含此处的翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用 计算属性。基础例子如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性</title>
</head>

<body>
    <div id="example">
        <p>Original message: "{{ message }}"</p>
        <p>Computed reversed message: "{{ reversedMessage }}"</p>
    </div>
</body>

</html>
<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#example",
        data: {
            message: "Hello"
        },
        computed: {
            //计算属性的getter
            reversedMessage: function() {
                //this指向vm实例
                return this.message.split("").reverse().join("");
            }
        }

    });
</script>

演示效果如下:
在这里插入图片描述
这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作 property vm.reversedMessage 的 getter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打开浏览器的控制台,自行修改例子中的 vmvm.reversedMessage 的值始终取决于 vm.message 的值。如下图所示:
在这里插入图片描述
你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

1.3.2 计算属性缓存 vs 方法

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>

js 代码如下:

methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

1.4 侦听属性及侦听器

1.4.1 基本使用

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用 计算属性 命令式的 watch 回调。细想一下这个例子:

html 核心代码如下:

<body>
    <div id="app">
        <div>
            <span>姓:</span>
            <span>
                <input type="text" v-model="xing">
            </span>
        </div>
        <div>
            <span>名:</span>
            <span>
                <input type="text" v-model="ming">
            </span>
        </div>
        <div>全名: {{xingming}}</div>
    </div>
</body>

js 核心代码如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            xing: "Amo",
            ming: "Xiang",
            xingming: "Amo Xiang"
        },
        //在这添加一个watch属性
        watch: {
            //watch中的属性 一定是data中已经存在的数据
            //这里的xing对应着data中的xing 当data中xing的值发生改变时 会自动触发watch
            xing: function(val) {
                this.xingming = val + " " + this.ming;
            },
            //这里的ming对应着data中的ming 当data中ming的值发生改变时 会自动触发watch
            ming: function(val) {
                this.xingming = this.xing + " " + val;
            }
        }
    });
</script>

演示效果如下:
在这里插入图片描述
上面代码是命令式且重复的。将它与计算属性的版本进行比较:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            xing: "Amo",
            ming: "Xiang",
        },
        computed: {
            xingming: function() {
                return this.xing + " " + this.ming;
            }
        }
    });
</script>

好得多了,不是吗?虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时 执行异步开销较大 的操作时,这个方式是最有用的。html 结构如下:

<body>
    <div id="app">
        <span>用户名: </span>
        <span>
            <!-- 使用修饰符将input事件改为change事件 -->
            <input type="text" v-model.lazy="uname">
        </span>
        <span>{{tip}}</span>
    </div>
</body>

js 代码如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            uname: "",
            tip: ""
        },
        methods: {
            checkName: function(uname) {
                // 调用接口,但是可以使用定时任务的方式模拟接口调用
                // 因为定时器内部的this是指向window的 所以这里要使用一个变量来存储this指向的vm实例
                let that = this;
                setTimeout(function() {
                    if (uname == "admin") {
                        that.tip = "用户名已经存在,请更改....";
                    } else {
                        that.tip = "用户名可以使用";
                    }
                }, 2000);
            }
        },
        watch: {
            uname: function(val) {
                this.checkName(val); //调用函数校验用户名的合法性
                this.tip = "正在检验用户名是否可用...."
            }
        }
    });
</script>

演示效果如下:
在这里插入图片描述
当需要监听一个对象的改变时,普通的 watch 方法无法监听到对象内部属性的改变,只有 data 中的数据才能够监听到变化,此时就需要 deep 属性对对象进行深度监听 请看下面这个例子:html 结构如下:

<body>
    <div id="app">
        <input type="text" v-model="personInfo.name">
        <h3>{{personInfo.name}}</h3>
    </div>
</body>

js 代码如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            personInfo: {
                name: "Amo",
                age: 18,
                isBoy: true
            }
        },
        watch: {
            personInfo: function(value) {
                console.log("监听成功");
            }
        }
    });
</script>

演示效果如下:
在这里插入图片描述
从上面的结果我们可以看出,是没有监听成功的,我们只需要将上面 js 代码中 watch 部分更改一下即可,如下:
在这里插入图片描述
演示效果如下:
在这里插入图片描述

1.5 过滤器

所谓的 过滤器,大家可以这样理解类似于我们家里的 净水器净水器是按对 的使用要求对水质进行深度过滤、净化处理,Vue 中则是按照对数据的使用要求进行过滤处理,其本质就是对数据的处理,而在 Vue 中数据都是绑定到 Vue实例的 data 属性中,即过滤器处理 data 属性中的数据然后将其返回。

1.5.1 基本使用

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器 应该被添加在 JavaScript 表达式的尾部,由 管道 符号指示:

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

你可以在一个组件的选项中定义本地的过滤器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    //return必须写,charAt(0):获取字符串中第一个字符
    //toUpperCase():将小写字母转换为大写字母
    //slice(1): 表示字符串从索引1开始截取到最后的子串
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

或者在创建 Vue 实例之前全局定义过滤器:

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})

注意:

  1. 当全局过滤器和局部过滤器重名时,会采用局部过滤器。
  2. 全局注册时是 filter,没有 s 的。而局部过滤器是filters,是有 s

下面这个例子用到了 capitalize 过滤器:
在这里插入图片描述
通过上述 gif 演示的效果图,大家可以看出 过滤器 不改变真正的 data,而只是改变渲染的结果,并返回过滤后的版本。核心代码如下:
在这里插入图片描述
过滤器函数总接收表达式的值(之前的操作链的结果)作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。过滤器可以串联:

{{ message | filterA | filterB }}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。过滤器是 JavaScript 函数,因此可以接收参数:

{{ message | filterA('arg1', arg2) }}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。格式化日期例子,核心代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <div>{{date | format('yyyy-MM-dd hh:mm:ss')}}</div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        Vue.filter('format', function(value, arg) {
            function dateFormat(date, format) {
                if (typeof date === "string") {
                    var mts = date.match(/(\/Date\((\d+)\)\/)/);
                    if (mts && mts.length >= 3) {
                        date = parseInt(mts[2]);
                    }
                }
                date = new Date(date);
                if (!date || date.toUTCString() == "Invalid Date") {
                    return "";
                }
                var map = {
                    "M": date.getMonth() + 1, //月份 
                    "d": date.getDate(), //日 
                    "h": date.getHours(), //小时 
                    "m": date.getMinutes(), //分 
                    "s": date.getSeconds(), //秒 
                    "q": Math.floor((date.getMonth() + 3) / 3), //季度 
                    "S": date.getMilliseconds() //毫秒 
                };

                format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
                    var v = map[t];
                    if (v !== undefined) {
                        if (all.length > 1) {
                            v = '0' + v;
                            v = v.substr(v.length - 2);
                        }
                        return v;
                    } else if (t === 'y') {
                        return (date.getFullYear() + '').substr(4 - all.length);
                    }
                    return all;
                });
                return format;
            }
            return dateFormat(value, arg);
        })
        var vm = new Vue({
            el: '#app',
            data: {
                date: new Date()
            }
        });
    </script>
</body>
</html>

这里,format 被定义为接收二个参数的过滤器函数。其中 date 的值作为第一个参数,普通字符串 'yyyy-MM-dd hh:mm:ss' 作为第二个参数。程序结果为:
在这里插入图片描述

1.6 音乐播放器

1.6.1 功能演示

音乐播放器小案例.mp4

1.6.2 前置知识

HTML5 中,新增了两个元素——video 元素与 audio 元素。video 元素专门用来播放网络上的视频或电影,而 audio 元素专门用来播放网络上的音频数据。使用这两个元素,就不再需要使用其他任何插件了,只要使用支持 HTML5 的浏览器就可以了。这两个元素的使用方法都很简单,本文使用到的是audio,所以这里 audio 元素为例,只要把播放音频的 URL给指定元素的 src 属性就可以了,audio 元素使用方法如下所示。

<audio src="./Music/麦小兜-下山.flac">您的浏览器不支持audio元素!</audio>

通过这种方法,可以把指定的音频数据直接嵌入在网页上,其中 您的浏览器不支持audio元素! 为在不支持 audio 元素的浏览器中所显示的替代文字。本案例用到的属性及事件介绍如下:

  1. src 属性用于指定媒体数据的 URL 地址。
  2. autoplay 属性用于指定媒体是否在页面加载后自动播放。使用方法如下:
    <audio src="./Music/汪苏泷-小星星.flac" autoplay></audio>
    
  3. controls 属性指定是否为视频或音频添加浏览器自带的播放用的控制条。控制条中具有播放、暂停等按钮。使用方法如下:
    <audio src="./Music/汪苏泷-小星星.flac" controls></audio>
    
  4. ended 事件:播放由于媒介结束而停止

1.6.3 示例代码

html 结构如下:

<body>
    <div id="app">
        <audio :src="currentSrc" controls autoplay @ended="handleEnded"></audio>
        <ul>
            <li :class="{active:index===currentIndex}" v-for="(item,index) in musicData" :key="item.id" @click="handle(item,index)">
                <h2>{{item.id}}--歌名:{{item.name}}</h2>
                <p>{{item.author}}</p>
            </li>
        </ul>
        <button @click="handlePre">上一首</button>
        <button @click="handleNext">下一首</button>
    </div>
</body>

js 代码如下:

<script>
    const musicData = [{
        id: 1,
        name: '下山',
        author: '麦小兜',
        songSrc: './Music/麦小兜-下山.flac'
    }, {
        id: 2,
        name: 'Please Dont Go',
        author: 'Joel Adams',
        songSrc: './Music/Joel Adams - Please Dont Go.mp3'
    }, {
        id: 3,
        name: '小星星',
        author: '汪苏泷',
        songSrc: './Music/汪苏泷-小星星.flac'
    }, {
        id: 4,
        name: '芒种',
        author: '赵方婧',
        songSrc: './Music/音阙诗听,赵方婧-芒种.flac'
    }];
    let vm = new Vue({
        el: "#app",
        data: {
            musicData,
            currentSrc: "./Music/麦小兜-下山.flac",
            currentIndex: 0
        },
        methods: {
            handle(item, index) {
                this.currentSrc = item.songSrc;
                this.currentIndex = index;
            },
            handleEnded() {
                this.handleNext();
            },
            handlePre() {
                this.currentIndex--;
                if (this.currentIndex < 0) {
                    this.currentIndex = this.musicData.length - 1;
                }
                this.currentSrc = this.musicData[this.currentIndex].songSrc;
            },
            handleNext() {
                this.currentIndex++;
                if (this.currentIndex === this.musicData.length) {
                    this.currentIndex = 0;
                }
                this.currentSrc = this.musicData[this.currentIndex].songSrc;
            }
        }
    });
</script>

2. 综合案例

2.1 Vue之数组更新检测

2.1.1 变更方法

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

  1. push()
  2. pop()
  3. shift()
  4. unshift()
  5. splice()
  6. sort()
  7. reverse()

这里的话,我们通过一个案例来进行演示,案例演示效果如下:
在这里插入图片描述
html 代码结构如下:

<body>
    <div id="app">
        <div>
            <span>
                <input type="text" v-model="person" autofocus>
                <button @click="add">添加</button>
                <button @click="del">删除</button>
                <button @click="modify">修改</button>
            </span>
        </div>
        <ul>
            <li v-for="(item,index) in personList" :key="index">{{item}}</li>
        </ul>
    </div>
</body>

js 代码如下:

<script src="js/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            personList: ["Amo", "Jerry", "Paul"],
            person: ""
        },
        methods: {
            add: function() {
                this.personList.push(this.person);
            },
            del: function() {
                this.personList.pop();
            },
            modify: function() {
                //将新的数组赋值给原有的personList
                this.personList = this.personList.slice(0, 2);
            }
        }
    });
</script>

2.1.2 替换数组

变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()concat()slice()。它们不会变更原始数组,而 总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:

this.personList = this.personList.slice(0, 2);

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

2.1.3 注意事项

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。对于数组: Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

let vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用 splice

vm.items.splice(newLength)

对于对象: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

let vm = new Vue({
  data:{
    a:1
  }
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:

Vue.set(vm.someObject, 'b', 2)

读者还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

this.$set(this.someObject,'b',2)

有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()_.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

2.2 图书管理

2.2.1 图书列表

  1. 实现静态列表效果
  2. 基于数据实现模板效果
  3. 处里每行的操作按钮
    在这里插入图片描述
    示例代码如下:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书管理</title>
    <style>
        .grid {
            margin: auto;
            width: 500px;
            text-align: center;
        }
        
        .grid table {
            width: 100%;
            border-collapse: collapse;
        }
        
        .grid th,
        td {
            padding: 10;
            border: 1px dashed orange;
            height: 35px;
            line-height: 35px;
        }
        
        .grid th {
            background-color: orange;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="grid">
            <table>
                <thead>
                    <tr>
                        <th>编号</th>
                        <th>名称</th>
                        <th>时间</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr :key='item.id' v-for='item in books'>
                        <td>{{item.id}}</td>
                        <td>{{item.name}}</td>
                        <td>{{item.date}}</td>
                        <td>
                            <a href="" @click.prevent>修改</a>
                            <span>|</span>
                            <a href="" @click.prevent>删除</a>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>

</html>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
    let vm = new Vue({
        el: '#app',
        data: {
            books: [{
                id: 1,
                name: '三国演义',
                date: ''
            }, {
                id: 2,
                name: '水浒传',
                date: ''
            }, {
                id: 3,
                name: '红楼梦',
                date: ''
            }, {
                id: 4,
                name: '西游记',
                date: ''
            }]
        }
    });
</script>

这个案例之前的案例差别不大,主要是对前面基础的知识进行复习,也比较简单,笔者就不再一一写下去了。后面如果有时间,在进行补充。

Logo

前往低代码交流专区

更多推荐