Vue全家桶系~1.Vue2基础(兼容)

Vue基础学习

HTML+CSS+JS基础文档:https://developer.mozilla.org/zh-CN/docs/Web

Vue3官网文档:https://cn.vuejs.org/ | Vue2文档:https://v2.cn.vuejs.org/v2/guide

个人建议:对于小白和新手,以及只会HTML+CSS+JS基础的人来说,别上来就搞那一套高度封装的开发方式,开发是很方便,调bug也是非常麻烦的。先以这种脚本引入+HTML的混合开发入手,熟悉之后再用组件化开发方式

这样做两个好处:1.原来HTML+JQ的后端开发程序员,用这种开发方式反而更容易入手、2.市面上很多Vue2的项目,不至于直接懵圈

PS:我下面案例只是使用Vue2的脚本引入的方式便捷开发,语法和知识点都是参考最新Vue3最新文档(贴的地址也都是Vue3的文档)

如果有了前端开发基础,比如开发的时候早就抛弃HTML这种老一辈开发方式已经有TS、Vue之类的基本经验 ==> 【跳过这篇文章,看下篇

1.环境配置

1.1.IDE配置(必配)

VSCode官方插件Vue Language Features (Volar)

TypeScript支持:TypeScript Vue Plugin (Volar)

Vue快速开发Vue VSCode Snippets(输入缩写快速生成代码段)

错误高亮提示Error Lens


1.2.第一个demo

练手先使用传统的开发方式:把vue当做一个脚本js文件引入,然后再开发(官方也推荐这样入门)

开发的时候可以引用开发版vue.js(更多友好提示),发布的时候替换为vue.mini.js(更轻量级)

首先说下大致流程:

1首先要在HTML文件里面创建一个存放宿主文件的标签(Vue所有DOM操作都是这个里面进行)可以是id,也可以是class

<div id="app"> </div>

2然后引入vuejs(Vue2基础语法和Vue3基本上一样)

 <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.js"></script>

3接着就要在script标签里面实例化vue对象了:

<script>
    // 3.创建一个vue示例
    const app = new Vue({
        el: '#app' // 【必须】指定宿主id或者class
        }
    });
</script>

下面进行案例实战,需求:在HMTL页面里面显示小明的自我介绍(变量赋值),1s后补充下介绍(变量修改

<!-- 1.创建一个宿主文件 -->
<div id="app"> 自我介绍:{{name}} </div>
<!-- 2.引入vue.js -->
<script src="../assets/vue2.js"></script>
<script>
    // 3.创建一个vue示例
    const app = new Vue({
        el: '#app', // 【必须】指定宿主id或者class
        data() {// 输入vdata可以快速生成
            return {
                name: '大家好,我叫小明'// 变量初始化赋值
            }
        }
    });
    // 4.一秒后修改下文本内容
    setTimeout(() => {
        // 直接变量修改,不用DOM操作了
        app.name = '谢谢大家'; // 变量修改
    }, 1000);
</script>

输出:

自我介绍:大家好,我叫小明(刚开始)
自我介绍:谢谢大家(1s后)

Vue不只是一个模板引擎,它其实是响应式的,数据和DOM已经关联了。要验证也很简单,控制台改变下变量,页面就实时改变了:

2.模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

2.1.文本渲染(文本插值)

数据绑定最常见的形式就是使用“Mustache”语法{{ 变量 }}的文本插值,这种方式分两种:

  1. 当数据改变时,插值文本跟着改变(常用):
    1. <span>Message: {{ msg }}</span>
  2. 能执行一次性的插值,,当数据改变时,内容不会更新
    1. <span v-once>这个将不会改变: {{ msg }}</span>
    2. 注意:这会影响到该节点上的其它数据绑定

贴一下第一个demo的例子:

<!-- 1.创建一个宿主文件 -->
<div id="app"> <span>{{name}}</span> </div>
<!-- 2.引入vue.js -->
<script src="../assets/vue2.js"></script>
<script>
    // 3.创建一个vue示例
    const app = new Vue({
        el: '#app', // 【必须】指定宿主id或者class
        data() {// 输入vdata可以快速生成
            return {
                name: '大家好,我叫小明'
            }
        }
    });
    // 4.一秒后修改下文本内容
    setTimeout(() => {
        // 直接变量修改,不用DOM操作了
        app.name = '谢谢大家';
    }, 1000);
</script>

如果其他不变,就是把<span>{{name}}</span>改成:<span v-once>{{name}}</span>,你会发现只显示第一次赋值的内容

app.name的变量其实已经修改了,但是页面文本内容因为设置了v-once,不会修改

补充:HTML渲染(v-html)

双大括号会将数据解释为普通文本,为了输出HTML,可以使用v-html 指令:<span v-html="变量"></span></p>

示例:

<div id="app">
    <!-- 默认是文本 -->
    <p>{{htmlone}}</p>
    <!-- 想渲染html就要这样写 -->
    <p v-html="htmltwo"></p>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                htmlone: '<h2>我是HTML</h2>',
                htmltwo: '<h2>我是HTML</h2>'
            }
        },
    })
</script>

效果:

这个 p 的内容将会被替换成为 property 值 htmltw,直接作为 HTML——会忽略解析 property 值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。

你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。


2.2.属性绑定(v-bind)

1.常用绑定案例

HTML特性不能用Mustache语法(双大括号),要使用v-bind指令:v-bind:属性值="变量名"

举个title属性值的例子:<p v-bind:title="title1">鼠标移上去,停顿几秒</p>

Vue还提供了一种简写方式:<p :title="title2">鼠标移上去,停顿几秒</p>

完整案例:

<div id="app">
    <p v-bind:title="title1">
        鼠标移上去,停顿几秒看看
    </p>
    <!-- 可以缩写 -->
    <p :title="title2">
        鼠标移上去,停顿几秒看看
    </p>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                title1: '我是一个标题1',
                title2: '我是一个标题2'
            }
        },
    })
</script>

2.布尔类型属性

布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上,如果 btnDisabled 的值是 nullundefinedfalsedisabled attribute直接不会出现在 <button>

<button :disabled="btnDisabled">按钮</button>

btnDisabled为真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。

3.动态绑定多个值

如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

通过不带参数的 v-bind,你可以将它们绑定到单个元素上:

<div v-bind="objectOfAttrs"></div>

案例:

<div id="app">
    <div v-bind="objectOfAttrs">F12查看我的源码</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                objectOfAttrs: {
                    id: 'container',
                    class: 'wrapper'
                }
            }
        },
    })
</script>

效果:


扩展:JavaScript 表达式

这边贴一下官方的文档:https://cn.vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions

对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。

在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:

  • 在文本插值中 (双大括号)
  • 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中

案例:

<div id="app">{{num + 1}}</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                num: 3
            }
        },
    })
</script>

结果是4


每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return 后面。

下面的例子都是无效的:

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

更多参考:https://cn.vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions


2.3.条件渲染(v-if)

  1. v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。

  2. v-else 相当于为 v-if 添加一个“else 区块”

    1. 使用 v-else 元素必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别
  3. v-else-if 提供的是相应于 v-if 的“else if 区块”

    1. 使用 v-else-if 的元素必须紧跟在一个 v-if 或一个 v-else-if 元素后面

举个例子:

<div id="app">
    <div v-if="str === 'A'"> A </div>
    <div v-else-if="str === 'B'"> B </div>
    <div v-else-if="str === 'C'"> C </div>
    <div v-else> other </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                str: 'D'
            }
        },

    });
</script>

输出:other


Vue3新增了v-showhttps://cn.vuejs.org/guide/essentials/conditional.html#v-show

v-if惰性渲染的:如果在初次渲染时条件值为 false,元素是不存在的。

v-show 是通过css属性display控制元素显示,无论初始条件如何,始终会被渲染。

总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

2.4.列表渲染(v-for)

同时使用 v-ifv-for不推荐的,因为这样二者的优先级不明显(Vue2和Vue3两者优先级恰恰相反)

1.遍历数组

v-for 指令基于一个数组来渲染一个列表。 v-for 指令需要使用item in items形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名(用item of items迭代遍历也行)【items是一个数组,而不能是一个方法可以是计算属性)】

eg:<p v-for="item in items"> {{ item }} </p>

示例:

<!-- div#app -->
<div id="app">
    <!--快速生成: ul>li*2 -->
    <ul>
        <!-- vfor -->
        <li v-for="item in items">
            {{ item }}
        </li>
    </ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                items: ['狮子头', '韭菜', '鸡腿']
            }
        },
    })
</script>

效果:

2.第二参数

还可以(value,index)的方式遍历:

<!-- div#app -->
<div id="app">
    <ul>
        <li v-for="(value,index) in items">
            {{index}}.{{value}}
        </li>
    </ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                items: ['狮子头', '韭菜', '鸡腿'],
            }
        },
    })
</script>

效果:

3.遍历对象

举个遍历小明个人信息的案例:

<div id="app">
    <ul>
        <li v-for="(value,key,index) in items">
            {{index}}.{{key}}:{{value}}
        </li>
    </ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                items:{
                    'name':'小明',
                    'age':'32',
                    'gender':'男'
                }
            }
        },
    })
</script>

效果:


如果你直接遍历,则只会输出value值。我们只修改上面代码的html部分:

<div id="app">
    <ul>
        <li v-for="value in items">
            {{value}}
        </li>
    </ul>
</div>

效果:


如果想要获取key和value,可以写2个参数:v-for="(value,key) in items

<div id="app">
    <ul>
        <li v-for="(value,key) in items">
            {{key}}:{{value}}
        </li>
    </ul>
</div>
<!-- script:src -->
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                items: {
                    'name': '小明',
                    'age': '32',
                    'gender': '男'
                }
            }
        },
    })
</script>

效果:


4.对象列表★

来个实际的案例,v-if + v-for遍历班上的同学,并显示他们的个人信息

<div id="app">
    <p v-if="models.length==0">该班级没有学生</p>
    <ul v-else>
        <li v-for="model in models" :key="model.id">
            {{ model.id }}.{{model.name}}.{{model.age}}.{{model.gender}}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app', data() {
            return {
                models: [
                    { 'id': 1, 'name': '小明', 'age': '32', 'gender': '男' },
                    { 'id': 2, 'name': '小华', 'age': '22', 'gender': '男' },
                    { 'id': 3, 'name': '小花', 'age': '28', 'gender': '女' }
                ]
            }
        },
    })
</script>

效果:

PS:如果models的数值是空数组,就会显示:该班级没有学生

4.总结扩展

如果遍历的是列表,就两个参数,第1个是value值,第2个是index。

如果遍历的是对象,第1个参数是value值,第2个是key,第3个是index。

为了v-for有更高的渲染性能,需要给 Vue 一个提示,需要为每项提供一个唯一 key 属性(DOM中没有key属性,是给vue用的)eg:

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

<div v-for="item in items" v-bind:key="item.id">
  <!-- 内容 -->
</div>

<!-- 当你使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上: -->
<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template>

key 并不仅与 v-for 特别关联,它还具有其它用途

key 绑定的值期望是一个基础类型的值,例如字符串number类型。不要用对象作为 v-for 的 key

更多用法:https://cn.vuejs.org/guide/essentials/list.html#displaying-filtered-sorted-results

key文档:https://cn.vuejs.org/guide/essentials/list.html#maintaining-state-with-key


2.5.事件处理(v-on)

官方文档:https://cn.vuejs.org/guide/essentials/event-handling.htm

1.简单调用

先来个简单案例:单击后调用show方法:v-on:click="show"

完整写法:<div id="app"><button v-on:click="show">点我弹框</button></div>

简写用法:<div id="app"><button @click="show">点我弹框</button></div>
const app = new Vue({
    el: '#app',
    methods: {
        show() {
            alert('v-on test');
        }
    },
});

效果:

2.按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键,Vue 为一些常用的按键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“Delete”和“Backspace”两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统按键修饰符

系统按键修饰符和常规按键不同。与 keyup 事件一起使用时,该按键必须在事件发出时处于按下状态。换句话说,keyup.ctrl 只会在你仍然按住 ctrl 但松开了另一个键时被触发。若你单独松开 ctrl 键将不会触发。

  • .ctrl
  • .alt
  • .shift
  • .meta:微软的Windows 键、苹果的Command 键

来个案例:

Alt+Enter键触发事件@keyup.alt.enter="xxx"Ctrl+Click按键触发@click.ctrl="xxx"

<div id="app">
    <input type="text" @keyup.alt.enter="show('Alt + Enter')" value="Alt + Enter 试试?">
    <button @click.ctrl="show('点击 + Ctrl')">Ctrl+单击试试?</button>
    <!-- @keyup.ctrl.click这样写是没用的 -->
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        methods: {
            show(msg) { alert(msg); }
        },
    });
</script>

.exact 修饰符,允许控制触发一个事件所需的确定组合的系统按键修饰符。 ==> 就是只按下了什么键

<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

鼠标按键修饰符

  • .left
  • .right
  • .middle

这些修饰符将处理程序限定为由特定鼠标按键触发的事件


3.事件修饰符(了解)

专注于数据逻辑而不用去处理 DOM 事件的细节,Vue 为 v-on 提供了事件修饰符

事件修饰符官方文档:https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers

修饰符是用 . 表示的指令后缀,包含以下这些:

使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。eg:使用 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为,而 @click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>

<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>

<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

.capture.once.passive 修饰符与原生 addEventListener 事件相对应:

.passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能

<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>

<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>

2.6.表单绑定(v-model)

可以用 v-model 指令在表单 <input> <textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素

v-model 会忽略任何表单元素上初始的 valuecheckedselected attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中使用响应式系统的 API来声明该初始值。

看个简单v-model的案例:(v-model后面的item要先定义下

<!-- div#app>input -->
<div id="app">
    <input v-model="item" type="text" @keyup.enter="addItem" />
    <button @click="addItem">按钮添加</button>
    <div v-if="items.length==0">
        食堂里暂时没有菜了
    </div>
    <!-- v-if尽量不和v-for嵌套使用 -->
    <div v-else>
        <p v-for="name in items">
            {{ name }}
        </p>
    </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                item: '', // 变量要定义一下
                items: ["狮子头", "韭菜", "鸡腿"]
            }
        },
        methods: {
            addItem() {
                if (this.item == '') { return; }
                this.items.push(this.item); // 数组中加一个值
                this.item = ''; // 清空文本框内容
            }
        },
    })
</script>

效果:在文本框输入内容直接回车,或者按后面的按钮,就可以添加内容

更多可以查看文档:https://cn.vuejs.org/guide/essentials/forms.html

2.7.样式绑定(:class)

主要是ClassStyle,平时class用的比较多,Style如果要用可以直接绑定到一个样式对象(比较直观)

代码::class="{类名:bool}

1.简单案例

先来个简单案例:首先列出学生列表,然后点到哪个学生,哪个学生名字变红

  • :class="{active:selectedStudent==name}:设置一个类active,当变量selectedStudent和当前name相同的时候生效

  • @click="selectedStudent=name:当li被单击的时候,把name赋值给selectedStudent

<div id="app">
    <div v-if="students.length==0">这个班级还没有学生</div>
    <div v-else>
        <ul>
            <li v-for="name in students" :key="name" :class="{active:selectedStudent==name}"
                @click="selectedStudent=name">
                {{ name }}
            </li>
        </ul>
    </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                student: '', // v-model的变量
                selectedStudent: '',// 初始化
                students: ['张三', '李四', '王二', '赵五', '老六']
            }
        },
    })
</script>

点了老六一下的效果:

2.多个class值

你可以在对象中写多个字段来操作多个 class。此外,:class 指令也可以和一般的 class attribute 共存。eg:

<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>

如果isActive是true,hasError是false,最后显示出来就是这样的:

<div class="static active"></div>

3.Sytle案例

列出学生列表,然后点到哪个学生,哪个学生背景变黄

background-color这种有-的在js中容易有问题,所以需要'xx'引号包裹,或者写成backgroundColor驼峰命名的样子,然后Vue会帮我们转换

<div id="app">
    <div v-if="students.length==0">这个班级还没有学生</div>
    <div v-else>
        <ul>
            <!-- background-color这种有-的在js中容易有问题,所以需要引号包裹,或者写成backgroundColor驼峰命名的样子,然后Vue会帮我们转换 -->
            <!-- @click="selectedStudent=name:当li被单击的时候,把name赋值给selectedStudent -->
            <li v-for="name in students" :key="name" 
                :style="{'background-color':selectedStudent==name?'yellow':'transparent'}" 
                @click="selectedStudent=name">
                {{ name }}
            </li>
        </ul>
    </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                student: '', // v-model的变量
                selectedStudent: '',// 一定要先初始化
                students: ['张三', '李四', '王二', '赵五', '老六']
            }
        },
    })
</script>

更多参考官方文档:https://cn.vuejs.org/guide/essentials/class-and-style.html

2.8.计算属性(computed)

对于任何复杂逻辑,你都应当使用计算属性

1.表达式实现

先看一个简单案例引入一下:现在有个数组,里面数据有点错乱,需要处理后显示

这个案例没有使用计算属性,是表达式的方法实现

  • split(','):以,进行字符串分割,结果返回一个数组
  • reverse():把分割后的数组翻转下
  • join('-'):把翻转后的数组重新以-连接为一个字符串
<div id="app">
    <ul>
        <li v-for="str in students" :key="str">
            {{ str.split(',').reverse().join('-') }}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
            }
        },
    });
</script>

效果:

2.计算属性改写★

这种明显就违背了Vue的初衷,表达式明显过于复杂,而且这样效率也低(数据量大的时候还会有性能瓶颈)下面改写一下:

<div id="app">
    <ul>
        <li v-for="str in updateStudents" :key="str">
            {{ str }}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
            }
        },
        computed: {
            updateStudents() {
                return this.students.map(s => s.split(',').reverse().join('-'));
            }
        },
    });
</script>

在Vue.js中,计算属性的使用方式是像数据属性一样使用,而不是作为函数调用

PS:不能通过{{ newStr(str) }}的方式来调用计算属性


3.计算属性 vs 方法

计算属性在数据不变的情况下,是有缓存的,能够提升效率。上面方法也可以用函数改写,但是更推荐计算属性的方式:

两者逻辑类似,就是少了缓存(计算属性值会基于其响应式依赖被缓存,它仅会在其响应式依赖更新时才重新计算)

<div id="app">
    <ul>
        <li v-for="str in students" :key="str">
            {{ newStr(str) }}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ['男,23,张三,1', '男,22,李四,2', '女,30,王二,3']
            }
        },
        methods: {
            newStr(str) {
                return str.split(',').reverse().join('-');
            }
        },
    });
</script>

更多计算属性可以参考官方文档:https://cn.vuejs.org/guide/essentials/computed.html

2.9.侦听器(watch)

我们用一个案例来看下watch的一些特性:

PS:真实使用的时候{{studentCount}}不会通过这种方式实现,直接写{{students.length}}即可实时更新

1.watch不立刻执行的特性

<div id="app">
    <input v-model="student" type="text" @keyup.enter="addStudent" />
    <p>{{studentCount}}</p>
    <ul>
        <li v-for="(item,index) in students" :key="index">
            {{index+1}}.{{ item }}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                student: '', // v-model初始化
                studentCount: 0, // 初始化
                students: ['张三', '李四', '露西', '王五']
            }
        },
        methods: {
            addStudent() {
                this.students.push(this.student);
                this.student = '';
            }
        },
        watch: {
            // 不加特殊声明,变化之后才会执行
            students(newValue, oldValue) {
                this.studentCount = newValue.length;
            }
        },
    });
</script>

你会发现刚运行的时候并没有统计学生数量

这是因为watch不特殊处理的话只会在数据改变的时候触发回调函数,现在我们新增一个学生看下效果:

数据一改变,立马就更新了

2.让watch立刻执行

如果你想立刻执行,就用watch-option的方式来指定一下immediate:true,代码和上面一样,就watch变下:

watch: {
    students: {
        immediate: true,
            // deep: true, // 一般不启用,影响性能(特殊情况可以使用)
            handler(newValue, oldValue) {
            this.studentCount = newValue.length;
        }
    }
},

这样一打开就自动计算好了:

3.深层侦听器

深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

用watch-option的方式来指定一下deep: true(数据嵌套太深默认的watch是检测不到的,这时候你如果要侦听就开下,要考虑性能)

更多参考官方文档:https://cn.vuejs.org/guide/essentials/watchers.html


★ 计算属性 VS 侦听器 ★

  1. 计算属性场景:多个值的改变影响一个值

    1. 能用computed实现的,全部使用computed,不去使用watch
  2. 侦听器的场景:一个值的改变影响多个值

    1. 侦听器提供了通用方法,适合执行异步操作、较大开销的操作

举一个官方的侦听器案例:

<div id="watch-example">
    <p>
        提出一个问题,回答 yes or no
        <input v-model="question">
    </p>
    <!-- 提示语 -->
    <p>{{ answer }}</p>
</div>
<script src="../assets/vue2.js"></script>
<script src="../assets/axios.1.3.6.js"></script>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js"></script> -->
<script>
    var watchExampleVM = new Vue({
        el: '#watch-example',
        data: {
            question: '',
            answer: '提出一个问题,以英文的问号结尾'
        },
        watch: {
            // 如果 `question` 发生改变,这个函数就会运行
            question: function (newQuestion, oldQuestion) {
                this.answer = '稍等,我去问下,不要打字了哈~';
                this.getAnswer();
            }
        },
        methods: {
            getAnswer: function () {
                if (this.question.indexOf('?') == -1) {
                    this.answer = '请在最后加一个英文的?';
                    return
                }
                this.answer = '思考ing...'
                console.log(this);
                var vm = this;
                // axios里面的this就不是vm了
                axios.get('https://yesno.wtf/api')
                    .then(function (response) {
                    console.log(response.data)
                    console.log(this);
                    vm.answer = response.data.answer;
                })
                    .catch(function (error) {
                    vm.answer = '没法访问API,错误信息:' + error;
                })
            }
        }
    })
</script>

2.10.生命周期

Vue2和Vue3生命周期的钩子其实是有变化的,但是我们经常使用的没什么变换,这边贴一下Vu3的生命周期

Vue2和Vue3这块是大体一样的:beforeCreate > created > beforeMount > mounted > beforeUpdate > updated(销毁有些变化)

跑个测试案例看下:

<div id="app">
    {{testData}}
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                testData: '初始化数据', // 初始化
            }
        },
        beforeCreate() { console.log('beforeCreate'); },
        // 【常用】组件实例已创建,尚未挂载,dom不存在
        created() { console.log('created ' + this.$el); },
        beforeMount() { console.log('beforeMount'); },
        // 【常用】挂载结束,dom已经存在
        mounted() {
            console.log('mounted ' + this.$el);
            setTimeout(() => {
                this.testData = '我被修改了';
            }, 2000);
        },
        beforeUpdate() { console.log('beforeUpdate'); },
        updated() { console.log('updated'); }
    });
</script>

执行效果:created的时候没有dom,mounted的时候已经有dom了

如果不进行数据更新,下面两个方法是不会执行的:这个模拟了一个2s延迟的更新,于是就触发了

应用场景

  • beforecreate // 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
  • created // 组件初始化完毕,各种数据可以使用,常用于异步数据获取
  • beforeMounted // 未执行染、更新,dom未创建
  • mounted // 初始化(挂载)结束,dom已创建,可用于获取访问数据和dom元素
  • beforeupdate // 更新前,可用于获取更新前各种状态
  • updated // 更新后,所有状态已是最新
  • BeforeUnmount // 销毁前,可用于一些定时器或订阅的取消(组件还在
  • Unmounted // 组件已销毁,作用同上(组件不在了

更多参考官方文档:https://cn.vuejs.org/guide/essentials/lifecycle.html

生命周期钩子:https://cn.vuejs.org/api/composition-api-lifecycle.html

2.11.组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:

全局注册组件:Vue.component(name, options)

Vue.component('my-component-name', {
// ... options ...
})


1.子组件中的局部变量案例

我们先写个普通的功能:点击按钮数字+1

<div id="app">
    <div>
        {{count}}
        <button @click="count++">点我+1</button>
    </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                count: 0
            }
        },
    })
</script>

效果:单击一下数字就会+1


下面把这个案例改写成组件的方式(count这个变量主要是在组件中使用,那就可以单独封装在组件里面)

<div id="app">
    <my-button></my-button>
</div>
<script src="../assets/vue2.js"></script>
<script>
    // 组件的名称最好是这种中划线的方式Vue(开头大写)
    Vue.component('my-button', {
        data() {
            return {
                count: 0 // 这个变量和该组件强关联,定义在组件里面比较合适
            }
        },
        // template里面的模板最外面用个单独的div比较好
        template: `
<div>
{{count}}
<button @click="count++">点我+1</button>
    </div>
`
    }
                 );
    // 组件定义后再实例化,如果在前面实例化,页面会不正常
    const app = new Vue({ el: '#app' });
</script>

几个注意事项:

  1. 组件的名称最好是中划线的方式
    1. Vue.component(V开头大写,c开头小写)
  2. template里面的模板字符串,最外层要加个父标签/根标签,比如用个单独的div

效果:单击一下数字就会+1

如果你页面放好几个组件,也是可以的,而且相互不干扰:

<div id="app">
    <my-button></my-button>
    <my-button></my-button>
    <my-button></my-button>
</div>

任意点击后效果:


2.父组件传递数据给子组件

一个展示学生列表的案例,这个是改成组件之前的代码:

<div id="app">
    <li v-for="name in students" :key="name">
        {{ name }}
    </li>
</div>
<script src="../assets/vue2.js"></script>
<script>
    // 组件定义后再实例化,如果在前面实例化,页面会不正常
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ["张三", "李四", "王二"]
            }
        }
    });
</script>

输出:


改成组件的方式(把students的数据传递到组件里面)

<div id="app">
    <!-- 两个变量名不一定要完全一样,这边只是方便理解 -->
    <my-list :students="students"></my-list>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('my-list', {
        props: { // 对传过来的students进行约束和默认值设置(也可以直接props:['students'])
            students: {
                type: Array, // 设置students的数据类型是数组
                default: [] // 默认值是空列表
            },
        },
        template:
        `<ul>
            <li v-for="name in students" :key="name">
            {{ name }}
            </li>
    	</ul>`
    })
    // 组件定义后再实例化,如果在前面实例化,页面会不正常
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ["张三", "李四", "王二"]
            }
        },
    })
</script>

输出效果和上面一样:

3.子组件传递数据给父组件

比如现在要输入一个学生名称(子组件中的变量)然后数据(students)是在父组件中的,那就需要每次新增的student传递给父组件

案例改组件前的实现:两块内容,一个是文本框用来新增学生名、另一个是列表来列举学生

<div id="app">
    <input v-model="student" type="text" @keyup.enter="addStudent" />
    <ul>
        <li v-for="name in students" :key="name">
            {{ name }}
        </li>
    </ul>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                student: '',
                students: ['张三', '李四', '王二']
            }
        },
        methods: {
            addStudent() {
                this.students.push(this.student);
                this.student = '';
            }
        },
    });
</script>


改成组件的方式:

$emit:触发当前实例上的事件(附加参数都会传给监听器回调)【记不得可以想提交是submit,事件提交就是emit】

list部分和上面一样,重点说下input的事件监听:如果不传参可以不自定义事件,传参就可以这样写:

<!-- `add-student`是自定义事件,当触发就会调用父容器中的`addStudent`方法 -->
<enter-input @add-student="addStudent"></enter-input>

自定义组件:

Vue.component('enter-input', {
    data() {
        return {
            student: ''
        }
    },
    methods: {
        addStudent() {
            // 注意:事件名称定义时不要有大写字母出现
            // add-student是自定义事件,this.student是传给父容器addStudent的值
            this.$emit('add-student', this.student);
            this.student = ''; // 清空当前姓名
        }
    },
    template:
        `<input v-model="student" type="text" @keyup.enter="addStudent" />`
})

完整代码:

<div id="app">
    <!-- 监听组件事件 -->
    <enter-input @add-student="addStudent"></enter-input>
    <my-list :students="students"></my-list>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('enter-input', {
        data() {
            return {
                student: ''
            }
        },
        methods: {
            addStudent() {
                // 注意:事件名称定义时不要有大写字母出现
                // add-student是自定义事件,this.student是传给addStudent的值
                this.$emit('add-student', this.student);
                this.student = ''; // 清空当前姓名
            }
        },
        template:
        `<input v-model="student" type="text" @keyup.enter="addStudent" />`
    })
    Vue.component('my-list', {
        props: {
            students: {
                type: Array,
                default: []
            },
        },
        template:
        `<ul>
<li v-for="name in students" :key="name">
{{ name }}
    </li>
    </ul>`
    })
    const app = new Vue({
        el: '#app',
        data() {
            return {
                students: ['张三', '李四', '王二']
            }
        },
        methods: {
            addStudent(student) {
                this.students.push(student);
            }
        },
    });
</script>

4.自定义组件使用v-model

先回顾下vue的v-model怎么使用的:

<div id="app">
    <div><input v-model="name" type="text" /></div>
    <div>{{name}}</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                name: '小明'
            }
        },
    })
</script>

当改变文本框内容时,文本内容立刻改变:

双向绑定的这个v-model其实只是一个语法糖,背后相当于这样的:

PS:为什么event要加个$ ==> 你不加他到底是字符串,还是vdata中的变量呢?vue就不知道了

<input type="text" value="name" @input="name=$event.target.value">

如果我们自定义的组件,在绑定v-model的时候是不会双向绑定的,需要自己处理下(valueinput

  1. 自定义组件可以直接写v-model="name"

  2. 在自定义组件时设置下props: ['name']

  3. 方法里面添加一个变量修改的返回方法:this.$emit('input', e.target.value);

  4. template里面代码:<input :value="name" @input="onInput" type="text" />

完整代码示意

<div id="app">
    <my-input v-model="name"></my-input>
    <div>{{name}}</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('my-input',
    {
        props: ['name'],
        methods: {
            onInput(e) {
                this.$emit('input', e.target.value);
            }
        },
        template:
        `<div>
			<input :value="name" @input="onInput" type="text" />
    	 </div>`
    })
    const app = new Vue({
        el: '#app',
        data() {
            return {
                name: '小明'
            }
        },
    })
</script>

当改变文本框内容时,文本内容立刻改变:


5.插槽分发内容(含多个)

对于插槽分发,vue提供了 <slot> 元素来给组件便捷的传递内容

这个类比弹框提示,弹框这个组件是子组件封装好的,内容是由父组件传过来的(父组件内容显示在子组件中)

5.1.封装前的弹框

先写一个弹框显示和关闭的案例:

.message-box {
    width: 300px;
    padding: 10px 20px;
    color: white;
    background: rgb(2, 214, 133);
}
.message-box-close {
    float: right;
    color: white;
}

代码部分:

<div id="app">
    <div class="message-box" v-show="isShow">
        {{msg}}
        <span class="message-box-close" @click="isShow=false">X</span>
    </div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                isShow: true, // 是否显示弹框
                msg: '操作成功!' // 弹框提示内容
            }
        },
    })
</script>

打开后弹框默认是显示的,点击X后就关闭:

5.2.子组件版弹框

下面我们进行改写下:

PS:不能直接在子组件里面这样写:<span class="message-box-close" @click="show=false">X</span>这样容易导致父子组件show变量不一致,控制台也会警告提示:只要父组件重新渲染,该值就会被覆盖,所以还是要让父组件修改

子组件中的$emit('close',false)是触发自定义事件close,并把false传递给事件调用的函数。之前我们是一个单独的函数然后用参数接收的,这边父组件可以通过$event来接收传递的值

<div id="app">
    <message-box :show="isShow" @close="isShow=$event">{{msg}}</message-box>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('message-box', {
        props: ['show'],
        template: `
    <div class="message-box" v-show="show">
        <!-- 相当于占位符 -->
        <slot></slot>
        <span class="message-box-close" @click="$emit('close',false)">X</span>
    </div>`
    })
    const app = new Vue({
        el: '#app',
        data() {
            return {
                isShow: true, // 是否显示弹框
                msg: '操作成功!' // 弹框提示内容
            }
        },
    })
</script>

结果和上面一样,默认是显示的,点击X后就关闭:

5.3.精简具名插槽

Vue提供了一种简化的方式:xxx.sync来实现(相当于帮你写了自定义事件,事件的名称是固定的 @update:xxx

子组件只需要微调:<message-box :show.sync="isShow">{{msg}}</message-box>

Template也微调下:@click="$emit('update:show',false)"

完整代码如下:

<div id="app">
    <message-box :show.sync="isShow">{{msg}}</message-box>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('message-box', {
        props: ['show'],
        template: `
<div class="message-box" v-show="show">
<!-- 相当于占位符 -->
<slot></slot>
<span class="message-box-close" @click="$emit('update:show',false)">X</span>
    </div>`
    })
    const app = new Vue({
        el: '#app',
        data() {
            return {
                isShow: true, // 是否显示弹框
                msg: '操作成功!' // 弹框提示内容
            }
        },
    })
</script>
5.4.多位插槽案例

如果一个组件中需要占位多个坑位,可以在一个 <template> 元素上使用 v-slot 指令

PS:一个不带 name<slot> 默认的名字是default

还是上面的例子,如果我们现在除了提示语外,还要有个提示的标题,封装前写法如下:

<div id="app">
    <div class="message-box" v-show="isShow">
        {{msgTitle}}
        <strong>{{msgContent}}</strong>
        <span class="message-box-close" @click="isShow=false">X</span>
    </div>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                isShow: true,
                msgTitle: '温馨提示:',
                msgContent: '操作成功'
            }
        },
    })
</script>

样式微调如下:

.message-box {
    width: 300px;
    padding: 10px 20px;
    color: white;
    background: rgb(2, 214, 133);
}
.message-box-close {
    float: right;
    color: white;
}

打开页面进行提示,单击x则被关闭

封装成子组件则和上面有些不同,子组件里面要这样写:template v-slot:名字

PS:要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令

<message-box :show.sync="isShow">
    <template v-slot:title>{{msgTitle}}</template>
    <template v-slot:content>{{msgContent}}</template>
</message-box>

也可以简写:v-slot 有对应的简写 #,因此 <template v-slot:title>可以简写为 <template #title>

<message-box :show.sync="isShow">
    <template #title>{{msgTitle}}</template>
    <template #content>{{msgContent}}</template>
</message-box>

然后Template里面是这样的:

<div class="message-box" v-show="show">
    <slot name="title"></slot>
    <strong>
        <slot name="content"></slot>
    </strong>
    <span class="message-box-close" @click="$emit('update:show',false)">X</span>
</div>

代码如下:

<div id="app">
    <message-box :show.sync="isShow">
        <template v-slot:title>{{msgTitle}}</template>
        <template v-slot:content>{{msgContent}}</template>
    </message-box>
</div>
</div>
<script src="../assets/vue2.js"></script>
<script>
    Vue.component('message-box', {
        props: ['show'],
        template:
        `<div class="message-box" v-show="show">
<slot name="title"></slot>
<strong>
<slot name="content"></slot>
    </strong>
<span class="message-box-close" @click="$emit('update:show',false)">X</span>
    </div>`
    })
    const app = new Vue({
        el: '#app',
        data() {
            return {
                isShow: true,
                msgTitle: '温馨提示:',
                msgContent: '操作成功'
            }
        },
    })
</script>

6.组件知识概述

Q:谈一下你对Vue组件化的理解

A:组件化是Vue的精髓,Vue应用就是由一个个组件构成的。Vue的组件化涉及到的内容非常多,可以从以下几点进行阐述:

  1. 组件定义:组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue

  2. 组件优点:组件化可以增加代码的复用性可维护性可测试性

  3. 使用场景

    1. 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等
    2. 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件
    3. 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件
  4. 组件应用

    1. 定义Vue.component()components选项,sfc
    2. 分类:有状态组件,functionalabstract
    3. 通信props$emit()/$on()provide/inject
      1. 不考虑耦合性可以使用:$children/$parent/$root/$attrs/$listeners
    4. 内容分发<slot><template>,v-slot
    5. 使用及优化iskeep-alive,异步组件
  5. 组件本质:组件的本质是产生虚拟DOM

    1. Vue中的组件经历如下过程:组件配置 => VueComponent实例 => render()转换为方法 => Virtual DOM=> DOM

3.一个vue3的案例

如果还是这种混合开发的风格,Vu3和Vue2差不多,就是引用的JS变成了Vue3的,然后创建语句变成了下面这个:

const app = Vue.createApp({}) // 创建app

app.mount('#app') // 挂载

来个案例:通过两个按钮来控制count数值的加和减

<div id="app">
    <h3>计数:{{count}}</h3>
    <button @click="addCount">++</button>&nbsp;
    <button @click="subCount">--</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- <script src="../assets/vue3.js"></script> -->
<script>
    const app = Vue.createApp({ // 创建app对象
        data() {
            return {
                count: 0
            }
        },
        methods: {
            addCount() {
                this.count++;
            },
            subCount() {
                this.count--;
            }
        },
    })
    app.mount('#app') // 挂载
</script>

Vue基础部分先到这,后面就不是这种传统的混合开发了,以上内容只是为了之前混合开发的朋友做过渡所写,Vue3内容请看下篇(完)

posted @ 2023-08-11 17:01  鲲逸鹏  阅读(132)  评论(0编辑  收藏  举报