Vue3学习笔记(三)——模板语法、Class 与 Style 、ES6新增数组方法、UI库介绍

一、模板语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。

在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。

如果你对虚拟 DOM 的概念比较熟悉,并且偏好直接使用 JavaScript,你也可以结合可选的 JSX 支持直接手写渲染函数而不采用模板。但请注意,这将不会享受到和模板同等级别的编译时优化。

1.1、文本插值

最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):

<span>Message: {{ msg }}</span>

双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。

1.2、原始 HTML

双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Using text interpolation: <span style="color: red">This should be red.</span>

Using v-html directive: This should be red.

<template>
  <h2>
    {{ msg }}
  </h2>
  <h2 v-text="msg"></h2>
  <h2 v-html="msg"></h2>
</template>

<script lang="ts">
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = "<p style='color:blue'>Hello, world!</p>";
    return { msg };
  },
};
</script>

这里我们遇到了一个新的概念。这里看到的 v-html attribute 被称为一个指令。指令由 v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,你可能已经猜到了,它们将为渲染的 DOM 应用特殊的响应式行为。这里我们做的事情简单来说就是:在当前组件实例上,将此元素的 innerHTML 与 rawHtml 属性保持同步。

span 的内容将会被替换为 rawHtml 属性的值,插值为纯 HTML——数据绑定将会被忽略。注意,你不能使用 v-html 来拼接组合模板,因为 Vue 不是一个基于字符串的模板引擎。在使用 Vue 时,应当使用组件作为 UI 重用和组合的基本单元。

安全警告

在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容。

跨站脚本攻击(也称为XSS)指利用网站漏洞从用户那里恶意盗取信息。

<template>
  <h2>
    {{ msg }}
  </h2>
  <h2 v-text="msg"></h2>
  <div v-html="msg"></div>
  <textarea v-model="msg" style="width: 100%; height: 300px"></textarea>
</template>

<script lang="ts">
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = ref("<p style='color:blue'>Hello, world!</p>");
    return { msg };
  },
};
</script>

1.3、属性绑定

双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令

<div v-bind:id="dynamicId"></div>

v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

1.3.1、简写

因为 v-bind 非常常用,我们提供了特定的简写语法:

<div :id="dynamicId"></div>

开头为 : 的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中。简写语法是可选的,但相信在你了解了它更多的用处后,你应该会更喜欢它。

接下来的指引中,我们都将在示例中使用简写语法,因为这是在实际开发中更常见的用法。

1.3.2、布尔型属性

布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled 就是最常见的例子之一。

v-bind 在这种场景下的行为略有不同:

<button :disabled="isButtonDisabled">Button</button>

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

1.3.3、动态绑定多个值

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

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

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

<div v-bind="objectOfAttrs"></div>
<template>
  <button :title="msg + '!!!'">冒号绑定</button>
  <button v-bind:title="msg + '!!!'">v-bind绑定</button>
  <button :disabled="isDisalbed">被禁用的按钮</button>
  <button v-bind="attrs">登录按钮</button>
</template>

<script lang="ts">
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = "这是一个按钮";
    let isDisalbed = ref(true);
    let attrs = {
      class: "cls1",
      style: "color:red",
      id: "btnLogin",
    };
    return { msg, isDisalbed, attrs };
  },
};
</script>

1.4、使用 JavaScript 表达式

至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:

{{ number + 1 }}

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

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

		<div :id="`list-${id}`"></div>
		

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

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

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

1.4.1、仅支持表达式

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

因此,下面的例子都是无效的:

<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}

<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}

1.4.2、调用函数

可以在绑定的表达式中使用一个组件暴露的方法:

<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

TIP

绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。

<template>
  {{ show() }}
  <hr />
  <input v-model="msg" />
</template>
<script lang="ts" setup>
import { ref } from "vue";
function show() {
  console.log("show被调用了");
}
let msg = ref("Hello");
</script>

<template>
  <button @click="msg = msg ? 'ok' : 'no'">
    {{ (msg + "!!!").split("").reverse().join("") }}
  </button>

  <h2>
    {{ `消息内容是:${msg}` }}
  </h2>

  <input v-model="msg" />

  <h2>
    {{ showInfo() }}
  </h2>

  <h2>
    {{ showInfoComputed }}
  </h2>
</template>

<script lang="ts">
import { computed } from "@vue/reactivity";
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = ref("这是一个按钮");
    function showInfo() {
      console.log("showInfo被调用了");
      return "showinfo的返回值";
    }
    let showInfoComputed = computed(() => {
      console.log("showInfoComputed被调用了");
      return "showInfoComputed的返回值";
    });
    return { msg, showInfo, showInfoComputed };
  },
};
</script>

1.4.3、受限的全局访问

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math 和 Date

没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

1.4.4、vue3.0之全局变量app.config.globalProperties的使用

globalProperties

类型:[key: string]: any
默认:undefined
用法
添加一个可以在应用的任何组件实例中访问的全局 property。组件的 property 在命名冲突具有优先权。

这可以代替 Vue 2.x Vue.prototype 扩展:

// 之前(Vue 2.x)
Vue.prototype.$http = () => {}

Vue3挂载前添加全局对象,main.ts:

import { createApp } from 'vue'
import App from './App.vue'
import HelloWorld from './components/HelloWorld.vue'
import router from './router'

let app=createApp(App);

app.config.globalProperties.msg="abc";

app.use(router).mount('#app');

取值:

<template>
  <input v-model="info" />
  <button @click="info += '1'">加1</button>
</template>
<script lang="ts">
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = getCurrentInstance()?.appContext.config.globalProperties.msg;
    let info = ref(msg);

    return { info };
  },
  mounted(this: any) {
    console.log(this.msg);
  },
};
</script>

注意:vue3推荐使用依赖注入:provide和inject。原因:vue/rfcs.

1.5、指令 Directives

指令是带有 v- 前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bind 和 v-html

指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-forv-on 和 v-slot)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if 为例:

<p v-if="seen">Now you see me</p>

这里,v-if 指令会基于表达式 seen 的值的真假来移除/插入该 <p> 元素。

1.5.1、参数 Arguments

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute:

<a v-bind:href="url"> ... </a>

<!-- 简写 -->
<a :href="url"> ... </a>

这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。

1. 动态 v-bind:src

图片资源一般就近放置在组件中 或者 src/assets/img 文件中

// 组件 template 中
<img :src="getAssetsFile(icon)" alt="">

// 组件 js 中
import { getAssetsFile } from '@/utils/index.js'

// 公共 util.js 中
const getAssetsFile = (url) => {
return new URL(`../assets/images/${url}`, import.meta.url).href
}

注意:new URL(url,import.meta.url) 中的参数 url ,不能如下面定义变量拼接

GPT解释:

你提供的是一个Javascript函数,它用来生成指向资产文件(例如图片、样式表或脚本)的URL。这个函数假设这些文件都位于名为"icon"的目录中,该目录与当前的Javascript模块(即调用import.meta.url的模块)同目录。这用于支持 import.meta.url 的模块,如ES6 或更新的版本。

以下是关于该函数工作原理的简单解释:

import.meta.url 给出的是当前模块的URL。在浏览器中,这将是提取模块脚本的URL。在本地,它将是从中导入或执行模块的文件的URL。

new URL(...) 用于创建新的URL对象。传给new URL(...)的第一个参数是相对URL —— 在这种情况下,./icon/${url} 是一个将 url 参数添加到 ./icon/ 后得到的相对URL的模板字符串。

new URL(...)的第二个参数是在第一个参数为相对URL时使用的基础URL — 在这种情况下,作为基础URL的 import.meta.url 被使用。如前所述,import.meta.url解析为当前模块的URL。

.href 是URL对象的一个属性,返回整个URL作为一个字符串。

因此,如果你从具有URL "http://example.com/path/to/module.js" 的模块调用 getAssetsFile("image.jpg"),它将返回 "http://example.com/path/to/icon/image.jpg".

const getAssetsFile = (url) => {
let path = 'src/assets'
return new URL(`${path}${url}`, import.meta.url).href
}
上述开发没问题,但是打包会报错,不支持import.meta.url

2. 静态 src
公共 assets 文件夹中存放的图片

<!-- 绝对路径 -->
<img src="/src/assets/img/1.png">
<img src="@/assets/img/1.png" class="link-icon">

组件就近原则目录存放的图片

<!-- 相对路径 -->
<img src="./icon/1.png">
css中,背景图片
background: url(@/assets/img/bg.jpg) no-repeat center;
或者
background: url(/src/assets/img/bg.jpg) no-repeat center;

另一个例子是 v-on 指令,它将监听 DOM 事件:

<a v-on:click="doSomething"> ... </a>

<!-- 简写 -->
<a @click="doSomething"> ... </a>

这里的参数是要监听的事件名称:clickv-on 有一个相应的缩写,即 @ 字符。我们之后也会讨论关于事件处理的更多细节。

 

1.5.2、动态参数

同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

相似地,你还可以将一个函数绑定到动态的事件名称上:

<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething">

在此示例中,当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus

第一步,vite 配置
vite.config.ts 文件增加 resolve.alias 配置

import { resolve } from 'path'
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: [{ find: '@', replacement: resolve(__dirname, 'src') }]
  }
})
如果“path”有错误提示, 是因为 TS 找不到类型定义,按照提示安装 “@types/node”后即可
运行命令

npm install --dev @types/node
这时候已经可以使用@别名了

但还不能通过鼠标点击跳转到定义位置,鼠标点击跳转到定义位置需要在 tsconfig.json 中增加配置

    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
这时候就可以通过鼠标点击跳转到方法或变量定义位

动态参数值的限制

动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。

动态参数语法的限制

动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:

<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>

如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。

当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:

<a :[someAttr]="value"> ... </a>

上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板不受此限制。

<template>
  <button :[attrName]="'button'">这是一个按钮</button>
  要绑定的属性名称:<input v-model="attrName" />

  <hr />

  <button @[eventName]="show">这是一个按钮</button>
  要绑定的事件名称:<input v-model="eventName" />
</template>

<script lang="ts">
import { ref } from "vue";
export default {
  setup() {
    let attrName = ref("title");
    let eventName = ref("click");
    let show = (e) => {
      console.log(eventName.value);
    };
    return { attrName, eventName, show };
  },
};
</script>
<style scoped></style>

1.5.3、修饰符 Modifiers

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

<form @submit.prevent="onSubmit">...</form>

之后在讲到 v-on 和 v-model 的功能时,你将会看到其他修饰符的例子。

最后,在这里你可以直观地看到完整的指令语法:

指令语法图

1.5.4、v-text

更新元素的文本内容。

  • 期望的绑定值类型:string

  • 详细信息

    v-text 通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容。如果你需要更新 textContent 的部分,应该使用 mustache interpolations 代替。

  • 示例

  • <span v-text="msg"></span>
    <!-- 等同于 -->
    <span>{{msg}}</span>
  • 参考:模板语法 - 文本插值

1.5.5、v-html

更新元素的 innerHTML

  • 期望的绑定值类型:string

  • 详细信息

v-html 的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。如果你发现自己正打算用 v-html 来编写模板,不如重新想想怎么使用组件来代替。

安全说明

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

单文件组件scoped 样式将不会作用于 v-html 里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。如果你想让 v-html 的内容也支持 scoped CSS,你可以使用 CSS modules 或使用一个额外的全局 <style> 元素,手动设置类似 BEM 的作用域策略。

<template>
  <h2 v-html="msg"></h2>
  <p>这是一个P标签</p>
</template>

<script lang="ts">
import { computed } from "@vue/reactivity";
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = ref("<p>这是一个按钮</p>");
    return { msg };
  },
};
</script>
<style scoped>
p {
  color: red;
}
</style>

1.5.6、v-show

基于表达式值的真假性,来改变元素的可见性。

  • 期望的绑定值类型:any

  • 详细信息

    v-show 通过设置内联样式的 display CSS 属性来工作,当元素可见时将使用初始 display 值。当条件改变时,也会触发过渡效果。

  • 参考:条件渲染 - v-show

1.5.7、v-if

基于表达式值的真假性,来条件性地渲染元素或者模板片段。

  • 期望的绑定值类型:any

  • 详细信息

    当 v-if 元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。

    可用于 <template> 表示仅包含文本或多个元素的条件块。

    当条件改变时会触发过渡效果。

    当同时使用时,v-if 比 v-for 优先级更高。我们并不推荐在一元素上同时使用这两个指令 — 查看列表渲染指南详情。

  • 参考:条件渲染 - v-if

  • 因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template> 元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。

    <template v-if="ok">
      <h1>Title</h1>
      <p>Paragraph 1</p>
      <p>Paragraph 2</p>
    </template>

    v-else 和 v-else-if 也可以在 <template> 上使用。

  • <template>
      <button @click="isShow = !isShow">{{ isShow ? "隐藏" : "显示" }}</button>
    
      <div v-if="isShow">
        <h2>这段文字的显示与隐藏完全是由isShow控制的,DOM控制的</h2>
        <textarea>textarea</textarea>
        <textarea>textarea</textarea>
      </div>
    
      <template v-if="isShow">
        <h2>这段文字的显示与隐藏完全是由isShow控制的,DOM控制的</h2>
        <textarea>textarea</textarea>
        <textarea>textarea</textarea>
      </template>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      setup() {
        let isShow = ref(true);
        return { isShow };
      },
    };
    </script>
    <style scoped></style>

1.5.8、v-if vs v-show

  • v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
  • v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
  • 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
  • 总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

1.5.9、v-else

表示 v-if 或 v-if / v-else-if 链式调用的“else 块”。

  • 无需传入表达式

  • 详细信息

    • 限定:上一个兄弟元素必须有 v-if 或 v-else-if

    • 可用于 <template> 表示仅包含文本或多个元素的条件块。

  • 示例

  • <div v-if="Math.random() > 0.5">
      Now you see me
    </div>
    <div v-else>
      Now you don't
    </div>
  • 参考:条件渲染 - v-else

1.5.10、v-else-if

表示 v-if 的“else if 块”。可以进行链式调用。

  • 期望的绑定值类型:any

  • 详细信息

    • 限定:上一个兄弟元素必须有 v-if 或 v-else-if

    • 可用于 <template> 表示仅包含文本或多个元素的条件块。

  • 示例

  • <div v-if="type === 'A'">
      A
    </div>
    <div v-else-if="type === 'B'">
      B
    </div>
    <div v-else-if="type === 'C'">
      C
    </div>
    <div v-else>
      Not A/B/C
    </div>
  • 参考:条件渲染 - v-else-if

示例:

<template>
  <h2 v-html="msg"></h2>
  <template v-if="isShow">
    <p>这是一个P标签</p>
    <button>abc</button>
  </template>
  <button @click="isShow = !isShow">{{ `isShow=${isShow}` }}</button>

  <div v-if="Math.random() > 0.5">Math.random()&gt;0.5</div>
  <div v-else>Math.random()&lt;=0.5</div>

  <hr />
  <input v-model="type" />
  <p v-if="type === 'A'">优秀</p>
  <p v-else-if="type === 'B'">良好</p>
  <p v-else-if="type === 'C'">达标</p>
  <p v-else>不达标</p>
</template>

<script lang="ts">
import { computed } from "@vue/reactivity";
import { ref, getCurrentInstance } from "vue";
export default {
  setup() {
    let msg = ref("<p>这是一个按钮</p>");
    let isShow = ref(true);
    let type = ref("A");
    return { msg, isShow, type };
  },
};
</script>
<style scoped>
p {
  color: red;
}
</style>

结果:

修改:

1.5.11、v-for

基于原始数据多次渲染元素或模板块。

  • 期望的绑定值类型:Array | Object | number | string | Iterable

  • 详细信息

    指令值必须使用特殊语法 alias in expression 为正在迭代的元素提供一个别名:

  • <div v-for="item in items">
      {{ item.text }}
    </div>
  • 或者,你也可以为索引指定别名 (如果用在对象,则是键值):

  • <div v-for="(item, index) in items"></div>
    <div v-for="(value, key) in object"></div>
    <div v-for="(value, name, index) in object"></div>
  •  

    v-for 的默认方式是尝试就地更新元素而不移动它们。要强制其重新排序元素,你需要用特殊 attribute key 来提供一个排序提示:

  • <div v-for="item in items" :key="item.id">
      {{ item.text }}
    </div>
  •  

    v-for 也可以用于 Iterable Protocol 的实现,包括原生 Map 和 Set

  • <template>
      <ul>
        <li v-for="(item, index) in users" :key="item.id">
          {{ index }} - {{ item.id }} - {{ item.name }}
          <hr />
          <ul>
            <li v-for="(value, key, index) in item">
              {{ index }} - {{ key }}:{{ value }}
            </li>
          </ul>
        </li>
      </ul>
      <hr />
      <ul>
        <li v-for="(value, key, index) in user">
          {{ index }} - {{ key }}:{{ value }}
        </li>
      </ul>
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let users = reactive([
          { id: 201, name: "mark" },
          { id: 202, name: "rose" },
          { id: 203, name: "jack" },
        ]);
    
        let user = { name: "张三", age: 19 };
    
        return { users, user };
      },
    };
    </script>
    <style scoped>
    .div1 {
      height: 200px;
      overflow: auto;
      width: 300px;
      background-color: lightgoldenrodyellow;
    }
    .div1 ul li {
      height: 300px;
      line-height: 300px;
    }
    </style>

  • 参考:

1.5.12、v-on

给元素绑定事件监听器。

  • 缩写:@

  • 期望的绑定值类型:Function | Inline Statement | Object (不带参数)

  • 参数:event (使用对象语法则为可选项)

  • 修饰符:

    • .stop ——调用 event.stopPropagation()
    • <template>
        <div id="div1" @click="showInfo">
          <button @click.stop="showInfo">stop示例</button>
        </div>
      </template>
      
      <script lang="ts">
      import { reactive, ref } from "vue";
      export default {
        setup() {
          function showInfo() {
            console.log("事件被触发了");
          }
          return { showInfo };
        },
      };
      </script>
      <style scoped>
      #div1 {
        height: 100px;
        line-height: 100px;
        text-align: center;
        width: 200px;
        background: yellow;
      }
      </style>

    • .prevent ——调用 event.preventDefault()
    • <template>
        <div id="div1" @click="showInfo">
          <button @click.stop="showInfo">stop示例</button>
          <a href="http://best.cnblogs.com" @click.stop.prevent="showInfo">博客2</a>
        </div>
      
        <hr />
      
        <a href="http://best.cnblogs.com" @click.prevent="showInfo">博客1</a>
      </template>
      
      <script lang="ts">
      import { reactive, ref } from "vue";
      export default {
        setup() {
          function showInfo() {
            console.log("事件被触发了");
          }
          return { showInfo };
        },
      };
      </script>
      <style scoped>
      #div1 {
        height: 100px;
        line-height: 100px;
        text-align: center;
        width: 200px;
        background: yellow;
      }
      </style>

    • .capture ——在捕获模式添加事件监听器。
    • <template>
        <div id="div1" @click.capture="showInfo('2、父元素', $event)">
          <button @click="showInfo('1、子元素', $event)">capture示例</button>
        </div>
      </template>
      
      <script lang="ts">
      import { reactive, ref } from "vue";
      export default {
        setup() {
          function showInfo(msg, event) {
            console.log(msg, event);
          }
          return { showInfo };
        },
      };
      </script>
      <style scoped>
      #div1 {
        height: 100px;
        line-height: 100px;
        text-align: center;
        width: 200px;
        background: yellow;
      }
      </style>

      •   <div id="div1" @click="showInfo('2、父元素', $event)">
            <button @click="showInfo('1、子元素', $event)">确定</button>
          </div>

          <div id="div1" @click.capture="showInfo('2、父元素', $event)">
            <button @click="showInfo('1、子元素', $event)">确定</button>
          </div>

         

    • .self ——只有事件从元素本身发出才触发处理函数。
    • <template>
        <div id="div1" @click.self="showInfo('2、父元素', $event)">
          <button @click="showInfo('1、子元素', $event)">self示例</button>
        </div>
      </template>
      
      <script lang="ts">
      import { reactive, ref } from "vue";
      export default {
        setup() {
          function showInfo(msg, event) {
            console.log(msg, event.target);
          }
          return { showInfo };
        },
      };
      </script>
      <style scoped>
      #div1 {
        height: 100px;
        line-height: 100px;
        text-align: center;
        width: 200px;
        background: yellow;
      }
      </style>

    • stop与self的区别:

        self只能限制自己的触发机制,不管会不会进行冒泡或者捕获事件,只要不点击我,我就不触发,(你给谁添加.self只能让限制自己的点击事件,如果这个元素外面还有一个元素,这外面的元素照样能进行冒泡或捕获行为)

        stop 就能阻止事件冒泡,不会影响其他的事件

    • .{keyAlias} ——只在某些按键下触发处理函数。
      • 常用别名如下:  

        Vue中常用的按键别名:

      • 回车 => enter

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

        退出 => esc

        空格 => space

        换行 => tab (不适合用keyup事件 适合用于keydown事件)

        上 => up

        下 => down

        左 => left

        右 => right

        <template>
          <input @keyup.enter="keyEvent" />
        </template>
        
        <script lang="ts">
        import { reactive, ref } from "vue";
        export default {
          setup() {
            function keyEvent(e) {
              console.log("key:" + e.key, "code:" + e.code, "keyCode:" + e.keyCode);
            }
        
            return { keyEvent };
          },
        };
        </script>
        <style scoped>
        .div1 {
          height: 200px;
          overflow: auto;
          width: 300px;
          background-color: lightgoldenrodyellow;
        }
        .div1 ul li {
          height: 300px;
          line-height: 300px;
        }
        </style>

    • .once ——最多触发一次处理函数。
    • .left ——只在鼠标左键事件触发处理函数。
    • .right ——只在鼠标右键事件触发处理函数。
    • .middle ——只在鼠标中键事件触发处理函数。
    • <template>
        <input @keyup.ctrl.alt.s="keyEvent" />
        <button @click.right.prevent="rightClickEvent">只有按右键才触发事件</button>
        <button @mousedown.middle="rightClickEvent">只有按中键才触发事件</button>
        <button @mouseup.left="rightClickEvent">只有按左键才触发事件</button>
      </template>
      
      <script lang="ts">
      import { reactive, ref } from "vue";
      export default {
        setup() {
          function keyEvent(e) {
            console.log("key:" + e.key, "code:" + e.code, "keyCode:" + e.keyCode);
            return false;
          }
      
          function rightClickEvent(e) {
            console.log("您点击了", e);
            console.log(["", "", ""][e.button] + "键被点击");
          }
      
          return { keyEvent, rightClickEvent };
        },
      };
      </script>
      <style scoped></style>

    • 模拟同步延时
    • <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Document</title>
        </head>
        <body>
          <button id="btnLogin">登录</button>
          <script>
            //异步的,异步IO
            // setTimeout(() => {
            //   console.log("A、2000毫秒后你可以看到控制台输出该消息");
            // }, 2000);
      
            // console.log("B、2000毫秒后你可以看到控制台输出该消息");
      
            function sleep(millSeconds) {
              let time = new Date().getTime();
              while (new Date().getTime() <= time + millSeconds);
            }
      
            for (var i = 0; i < 10; i++) {
              console.log(i);
              sleep(2000);
            }
          </script>
        </body>
      </html>

    • .passive ——通过 { passive: true } 附加一个 DOM 事件。 positive正面的
      •   
        <!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
        <!-- 以防其中包含 `event.preventDefault()` -->
        <div @scroll.passive="onScroll">...</div>
  • <template>
      <!--wheel是中间的滚轮-->
      <!--scroll是滚动事件-->
      <div id="div1" @wheel.passive="eventMethod">
        <ul>
          <li>rose</li>
          <li>mark</li>
          <li>jack</li>
          <li>lucy</li>
          <li>lili</li>
        </ul>
      </div>
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        //延时
        let sleep = (millSeconds) => {
          let time = new Date().getTime();
          while (new Date().getTime() <= time + millSeconds);
        };
    
        let eventMethod = (e) => {
          sleep(2000);
          console.log(e);
        };
        return { eventMethod };
      },
    };
    </script>
    <style scoped>
    #div1 {
      width: 300px;
      height: 100px;
      background: lightgoldenrodyellow;
      overflow: auto;
    }
    
    #div1 li {
      margin: 20px;
      background-color: aquamarine;
      height: 100px;
      width: 200px;
    }
    </style>

  • 事件的默认行为立即执行,不用等到函数执行完毕

    在鼠标滚动事件添加.passive,会先执行滚动事件,再执行该事件后面调用的方法(@scroll.passive=“函数名”)

  • 注意:如果上面的wheel事件不加passive则下一次滚动必须等事件执行完成后再滚动,滚动条会卡住,加上后事件的执行会后置
  • 详细信息

    事件类型由参数来指定。表达式可以是一个方法名,一个内联声明,如果有修饰符则可省略。

    当用于普通元素,只监听原生 DOM 事件。当用于自定义元素组件,则监听子组件触发的自定义事件。

    当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,声明可以访问一个特殊的 $event 变量:v-on:click="handle('ok', $event)"

    v-on 还支持绑定不带参数的事件/监听器对的对象。请注意,当使用对象语法时,不支持任何修饰符。

  • 示例:

  • <!-- 方法处理函数 -->
    <button v-on:click="doThis"></button>
    
    <!-- 动态事件 -->
    <button v-on:[event]="doThis"></button>
    
    <!-- 内联声明 -->
    <button v-on:click="doThat('hello', $event)"></button>
    
    <!-- 缩写 -->
    <button @click="doThis"></button>
    
    <!-- 使用缩写的动态事件 -->
    <button @[event]="doThis"></button>
    
    <!-- 停止传播 -->
    <button @click.stop="doThis"></button>
    
    <!-- 阻止默认事件 -->
    <button @click.prevent="doThis"></button>
    
    <!-- 不带表达式地阻止默认事件 -->
    <form @submit.prevent></form>
    
    <!-- 链式调用修饰符 -->
    <button @click.stop.prevent="doThis"></button>
    
    <!-- 按键用于 keyAlias 修饰符-->
    <input @keyup.enter="onEnter" />
    
    <!-- 点击事件将最多触发一次 -->
    <button v-on:click.once="doThis"></button>
    
    <!-- 对象语法 -->
    <button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
  •  

    监听子组件的自定义事件 (当子组件的“my-event”事件被触发,处理函数将被调用):

  • <MyComponent @my-event="handleThis" />
    
    <!-- 内联声明 -->
    <MyComponent @my-event="handleThis(123, $event)" />
  • 参考:

1.5.13、v-bind

动态的绑定一个或多个 attribute,也可以是组件的 prop。

  • 缩写:: 或者 . (当使用 .prop 修饰符)

  • 期望:any (带参数) | Object (不带参数)

  • 参数:attrOrProp (可选的)

  • 修饰符:

    • .camel ——将短横线命名的 attribute 转变为驼峰式命名。
    • .prop ——强制绑定为 DOM property。3.2+
    • .attr ——强制绑定为 DOM attribute。3.2+
  • 用途:

    当用于绑定 class 或 style attribute,v-bind 支持额外的值类型如数组或对象。详见下方的指南链接。

    在处理绑定时,Vue 默认会利用 in 操作符来检查该元素上是否定义了和绑定的 key 同名的 DOM property。如果存在同名的 property,则 Vue 会把作为 DOM property 赋值,而不是作为 attribute 设置。这个行为在大多数情况都符合期望的绑定值类型,但是你也可以显式用 .prop 和 .attr 修饰符来强制绑定方式。有时这是必要的,特别是在和自定义元素打交道时。

    当用于组件 props 绑定时,所绑定的 props 必须在子组件中已被正确声明。

    当不带参数使用时,可以用于绑定一个包含了多个 attribute 名称-绑定值对的对象。

  • 示例:

  • <!-- 绑定 attribute -->
    <img v-bind:src="imageSrc" />
    
    <!-- 动态 attribute 名 -->
    <button v-bind:[key]="value"></button>
    
    <!-- 缩写 -->
    <img :src="imageSrc" />
    
    <!-- 缩写形式的动态 attribute 名 -->
    <button :[key]="value"></button>
    
    <!-- 内联字符串拼接 -->
    <img :src="'/path/to/images/' + fileName" />
    
    <!-- class 绑定 -->
    <div :class="{ red: isRed }"></div>
    <div :class="[classA, classB]"></div>
    <div :class="[classA, { classB: isB, classC: isC }]"></div>
    
    <!-- style 绑定 -->
    <div :style="{ fontSize: size + 'px' }"></div>
    <div :style="[styleObjectA, styleObjectB]"></div>
    
    <!-- 绑定对象形式的 attribute -->
    <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
    
    <!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
    <MyComponent :prop="someThing" />
    
    <!-- 传递子父组件共有的 prop -->
    <MyComponent v-bind="$props" />
    
    <!-- XLink -->
    <svg><a :xlink:special="foo"></a></svg>
  •  

    .prop 修饰符也有专门的缩写,.

  • <div :someProperty.prop="someObject"></div>
    
    <!-- 等同于 -->
    <div .someProperty="someObject"></div>
  •  

    当在 DOM 内模板使用 .camel 修饰符,可以驼峰化 v-bind attribute 的名称,例如 SVG viewBox attribute:

  • <svg :view-box.camel="viewBox"></svg>
  •  

    如果使用字符串模板或使用构建步骤预编译模板,则不需要 .camel

  • 参考:

1.5.14、v-model

在表单输入元素或组件上创建双向绑定。

  • 期望的绑定值类型:根据表单输入元素或组件输出的值而变化

  • 仅限:

    • <input>
    • <select>
    • <textarea>
    • components
  • 修饰符:

    • .lazy ——监听 change 事件而不是 input
    • .number ——将输入的合法符串转为数字
    • .trim ——移除输入内容两端空格
  • <template>
      <p>
        <input v-model.lazy="msg" />
      </p>
      <p>
        <select v-model="msg">
          <option value="apple">苹果</option>
          <option value="orange">橙子</option>
        </select>
      </p>
      <p>
        <textarea v-model="msg" />
      </p>
      <h2>
        {{ msg }}
      </h2>
      <p>
        <input v-model.number.trim="cardno" @keyup="show" />
      </p>
      <h2>卡号:{{ cardno }}</h2>
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let msg = ref("");
        let cardno = ref("");
        function show() {
          console.log(typeof cardno.value, cardno);
        }
        return { msg, cardno, show };
      },
    };
    </script>
    <style scoped>
    #div1 {
      width: 300px;
      height: 100px;
      background: lightgoldenrodyellow;
      overflow: auto;
    }
    
    #div1 li {
      margin: 20px;
      background-color: aquamarine;
      height: 100px;
      width: 200px;
    }
    </style>

  • 参考:

1.5.15、v-slot

用于声明具名插槽或是期望接收 props 的作用域插槽。

  • 缩写:#

  • 期望的绑定值类型:能够合法在函数参数位置使用的 JavaScript 表达式。支持解构语法。绑定值是可选的——只有在给作用域插槽传递 props 才需要。

  • 参数:插槽名 (可选,默认是 default)

  • 仅限:

    • <template>
    • components (用于带有 prop 的单个默认插槽)
  • 示例:

  • <!-- 具名插槽 -->
    <BaseLayout>
      <template v-slot:header>
        Header content
      </template>
    
      <template v-slot:default>
        Default slot content
      </template>
    
      <template v-slot:footer>
        Footer content
      </template>
    </BaseLayout>
    
    <!-- 接收 prop 的具名插槽 -->
    <InfiniteScroll>
      <template v-slot:item="slotProps">
        <div class="item">
          {{ slotProps.item.text }}
        </div>
      </template>
    </InfiniteScroll>
    
    <!-- 接收 prop 的默认插槽,并解构 -->
    <Mouse v-slot="{ x, y }">
      Mouse position: {{ x }}, {{ y }}
    </Mouse>
  • 参考:

1.5.16、v-pre

跳过该元素及其所有子元素的编译。

  • 无需传入

  • 详细信息

    元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

  • <template>
      <div v-pre>
        <h2>
          {{ msg }}
        </h2>
      </div>
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let msg = ref("apple");
        return { msg };
      },
    };
    </script>
    <style scoped>
    </style>

     

     

     

  • 示例:

  • <span v-pre>{{ this will not be compiled }}</span>

1.5.17、v-once

仅渲染元素和组件一次,并跳过之后的更新。

  • 无需传入

  • 详细信息

    在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。

  • <!-- 单个元素 -->
    <span v-once>This will never change: {{msg}}</span>
    <!-- 带有子元素的元素 -->
    <div v-once>
      <h1>comment</h1>
      <p>{{msg}}</p>
    </div>
    <!-- 组件 -->
    <MyComponent v-once :comment="msg" />
    <!-- `v-for` 指令 -->
    <ul>
      <li v-for="i in list" v-once>{{i}}</li>
    </ul>
  •  

    从 3.2 起,你也可以搭配 v-memo 的无效条件来缓存部分模板。

  • 参考:

1.5.18、v-memo

  • 期望的绑定值类型:any[]

  • 详细信息

    缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。举例来说:

  • <div v-memo="[valueA, valueB]">
      ...
    </div>
  •  

    <template>
      valueA:<input v-model="valueA" /> msg:<input v-model="msg" />
      <div v-memo="[valueA]">
        <h2>msg:{{ msg }} {{ show() }}</h2>
      </div>
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let msg = ref("apple");
        let valueA = ref("ok");
        function show() {
          console.log("msg被渲染了");
        }
        return { msg, valueA, show };
      },
    };
    </script>
    <style scoped></style>

    当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。

    正确指定缓存数组很重要,否则应该生效的更新可能被跳过。v-memo 传入空依赖数组 (v-memo="[]") 将与 v-once 效果相同。

  • <template>
      <div v-memo="msg">
        <h2>{{ msg }} {{ info }}</h2>
      </div>
      msg<input v-model="msg" />
      <hr />
      info<input v-model="info" />
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let msg = ref("apple");
        let info = ref("pear");
        return { msg, info };
      },
    };
    </script>
    <style scoped></style>

    上面的示例中只有当msg更新时才重新渲染界面

  •  

    与 v-for 一起使用

    v-memo 仅用于性能至上场景中的微小优化,应该很少需要。最常见的情况可能是有助于渲染海量 v-for 列表 (长度超过 1000 的情况):

  • <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
      <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
      <p>...more child nodes</p>
    </div>
  •  

    当组件的 selected 状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。v-memo 用在这里本质上是在说“只有当该项的被选中状态改变时才需要更新”。这使得每个选中状态没有变的项能完全重用之前的 vnode 并跳过差异比较。注意这里 memo 依赖数组中并不需要包含 item.id,因为 Vue 也会根据 item 的 :key 进行判断。

    警告

    当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。

    v-memo 也能被用于在一些默认优化失败的边际情况下,手动避免子组件出现不需要的更新。但是一样的,开发者需要负责指定正确的依赖数组以免跳过必要的更新。

  • <template>
      <ul
        v-for="(item, index) in users"
        :key="item.id"
        v-memo="[selected == item.id]"
      >
        <input
          name="user"
          type="radio"
          :value="item.id"
          v-on:change="selected = item.id"
          :checked="selected === item.id"
        />
        {{
          item.id
        }}
        -
        {{
          item.name
        }}
        -
        {{
          showMsg(item)
        }}
        - selected:
        {{
          item.id === selected
        }}
      </ul>
      选择编号:{{ selected }}
      <input v-model="selected" />
    
      <input v-model="key" />
    </template>
    
    <script lang="ts">
    import { reactive, ref } from "vue";
    export default {
      setup() {
        let users = reactive([
          { id: 201, name: "mark" },
          { id: 202, name: "rose" },
          { id: 203, name: "jack" },
          { id: 204, name: "lucy" },
        ]);
        let selected = ref(0);
        function showMsg(item) {
          console.log("渲染了", item);
        }
        let key = ref("100");
        return { users, selected, showMsg, key };
      },
    };
    </script>
    <style scoped>
    .div1 {
      height: 200px;
      overflow: auto;
      width: 300px;
      background-color: lightgoldenrodyellow;
    }
    .div1 ul li {
      height: 300px;
      line-height: 300px;
    }
    </style>

  • 参考:

1.5.19、v-cloak

用于隐藏尚未完成编译的 DOM 模板。

  • 无需传入

  • 详细信息

    该指令只在没有构建步骤的环境下需要使用。

    当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。

    v-cloak 会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像 [v-cloak] { display: none } 这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。

  • 示例:

    [v-cloak] {
      display: none;
    }
    <div v-cloak>
      {{ message }}
    </div>

    直到编译完成前,<div> 将不可见。

  • <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          /* 使用属性选择器 */
          [v-cloak] {
            display: none;
          }
        </style>
      </head>
    
      <body>
        <!-- 在这个标签中中编写Vue的视图 -->
        <div id="app">
          <!-- 1直接进行使用 -->
          <p v-cloak>{{str}}</p>
          <hr />
          <!-- 2字符拼接进行输出 -->
          <p>{{str + '拼接文本'}}</p>
          <!-- 3进行数学运算 -->
          <p>{{num +10}}</p>
          <!-- 4三元表达式 -->
          <p>{{age > 18 ? '成年人' : '未成年' }}</p>
          <!-- 5 使用方法,将字符串进行剪切 -->
          <p>{{str.substr(1,5)}}</p>
        </div>
      </body>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      <script>
        // !创建Vue实例
        new Vue({
          // 通过el绑定元素
          el: "#app",
          // 所有的数据都放在data中
          data: {
            // 定义一个数据
            str: "Vue里面data里面的数据",
            num: 10,
            age: 13,
            str: "0123456789",
          },
        });
      </script>
    </html>

 

1.6、重要的v-for指令

1.6.1、v-for

我们可以使用 v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名:

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
  {{ item.message }}
</li>

在 v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。

const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
  • Parent - 0 - Foo
  • Parent - 1 - Bar

v-for 变量的作用域和下面的 JavaScript 代码很类似:

const parentMessage = 'Parent'
const items = [
  /* ... */
]

items.forEach((item, index) => {
  // 可以访问外层的 `parentMessage`
  // 而 `item` 和 `index` 只在这个作用域可用
  console.log(parentMessage, item.message, index)
})

注意 v-for 是如何对应 forEach 回调的函数签名的。实际上,你也可以在定义 v-for 的变量别名时使用解构,和解构函数参数类似:

<li v-for="{ message } in items">
  {{ message }}
</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
  {{ message }} {{ index }}
</li>

对于多层嵌套的 v-for,作用域的工作方式和函数的作用域很类似。每个 v-for 作用域都可以访问到父级作用域:

<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>

你也可以使用 of 作为分隔符来替代 in,这更接近 JavaScript 的迭代器语法:

<div v-for="item of items"></div>

1.6.2、v-for 与对象

你也可以使用 v-for 来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys() 的返回值来决定。

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

可以通过提供第二个参数表示属性名 (例如 key):

<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

第三个参数表示位置索引:

<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

1.6.3、在 v-for 里使用范围值

v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n 的取值范围重复多次。

<span v-for="n in 10">{{ n }}</span>

注意此处 n 的初值是从 1 开始而非 0

1.6.4、template 上的v-for

与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染一个包含多个元素的块。例如:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

1.6.3、v-for 与 v-if

注意

同时使用 v-if 和 v-for 是不推荐的,因为这样二者的优先级不明显。请转阅风格指南查看更多细节。

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

1.6.4、通过 key 管理状态

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况。

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute:

<div v-for="item in items" :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-bind 绑定的特殊 attribute。请不要和在 v-for 中使用对象里所提到的对象属性名相混淆。

推荐在任何可行的时候为 v-for 提供一个 key attribute,除非所迭代的 DOM 内容非常简单 (例如:不包含组件或有状态的 DOM 元素),或者你想有意采用默认行为来提高性能。

key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key。关于 key attribute 的更多用途细节,请参阅 key API 文档

示例:

<template>
  id:<input type="text" v-model="id" />,name:<input
    type="text"
    v-model="name"
  /><button @click="add1">在末尾添加新人物</button>
  <button @click="add2">在前部添加新人物</button>
  <div v-for="item in list">
    <input type="checkbox" />
    {{ item.id }}.{{ item.name }}
  </div>
</template>

<script lang="ts">
import { reactive, ref } from "vue";
export default {
  setup() {
    let list = reactive([
      {
        id: 1,
        name: "刘备",
      },
      {
        id: 2,
        name: "孙权",
      },
      {
        id: 3,
        name: "曹操",
      },
      {
        id: 4,
        name: "诸葛亮",
      },
    ]);
    let id = ref(0);
    let name = ref("");

    function add1() {
      list.push({
        id: id.value,
        name: name.value,
      });
    }
    function add2() {
      list.unshift({
        id: id.value,
        name: name.value,
      });
    }

    return { list, id, name, add1, add2 };
  },
};
</script>
<style scoped></style>

在这里插入图片描述
事先勾选3.曹操,使用push方法王数组中添加对象并没有什么问题
在这里插入图片描述
但是,如果将push修改unshift方式添加对象,

v就会发现执行同样的操作时,原先勾选曹操变成了勾选孙权
在这里插入图片描述
这是因为勾选是和索引绑定的,当使用unshift方法时,原先的数组对象索引都加1,所以勾选的位置就往回退了一个对象,但是索引仍然不变。因此,就需要将checkbox的勾选和对象本身绑定,而非其索引,这里就需要使用到key。

前面说到需要将checkbox和对象数组中的对象绑定,但是当我们输入如下代码却发现页面报错

<div v-for="item in list" :key="item">
<input type="checkbox">
{{item.id}}.{{item.name}}
</div>

所以可以得出v-for循环时,key属性只能使用number或者string,而不是整个对象,修改代码

key的作用主要是为了高效的更新虚拟DOM

1、在组件中使用v-for循环的时候,如果v-for出现了问题,就必须同时指定唯一的字符串/数字类型的:key值。

2、v-for循环时,key属性只能用number或者string类型。

3、key使用时,必须使用bind属性绑定的形式,指定key的值。

6.5、组件上使用 v-for

这一小节假设你已了解组件的相关知识,或者你也可以先跳过这里,之后再回来看。

我们可以直接在组件上使用 v-for,和在一般的元素上使用没有区别 (别忘记提供一个 key):

<MyComponent v-for="item in items" :key="item.id" />

但是,这不会自动将任何数据传递给组件,因为组件有自己独立的作用域。为了将迭代后的数据传递到组件中,我们还需要传递 props:

<MyComponent
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
/>

不自动将 item 注入组件的原因是,这会使组件与 v-for 的工作方式紧密耦合。明确其数据的来源可以使组件在其他情况下重用。

这里是一个简单的 Todo List 的例子,展示了如何通过 v-for 来渲染一个组件列表,并向每个实例中传入不同的数据。

1.6.6、组件 和 v-for

了解组件相关知识,查看  组件 。Feel free to skip it and come back later.

在自定义组件里,你可以像任何普通元素一样用 v-for 。

<my-component v-for="item in items"></my-component>

然而他不能自动传递数据到组件里,因为组件有自己独立的作用域。为了传递迭代数据到组件里,我们要用 props :

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index">
</my-component>

不自动注入 item 到组件里的原因是,因为这使得组件会紧密耦合到 v-for 如何运作。在一些情况下,明确数据的来源可以使组件可重用。

下面是一个简单的 todo list 完整的例子:

<div id="todo-list-example">
  <input
    v-model="newTodoText"
    v-on:keyup.enter="addNewTodo"
    placeholder="Add a todo"
  >
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:title="todo"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>
Vue.component('todo-item', {
  template: '
    <li>
      {{ title }}
      <button v-on:click="$emit(\'remove\')">X</button>
    </li>',
  props: ['title']
})
new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [
      'Do the dishes',
      'Take out the trash',
      'Mow the lawn'
    ]
  },
  methods: {
    addNewTodo: function () {
      this.todos.push(this.newTodoText)
      this.newTodoText = ''
    }
  }
})

示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>列表渲染</title>
    </head>
    <body>
        <div id="app1">
            任务:<input v-model="newTask" @keyup.enter="addNew" placeholder="请输入您要完成的任务" />
            <ul>
                <li is="todoitem" v-for="(task,index) in tasks" :title="task" @remove="removeItem(index)"></li>
            </ul>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            Vue.component("todoitem", {
                template: "<li>{{title}} <button @click='$emit(\"remove\")'>X</button></li>",
                props: ['title']
            });

            var app1 = new Vue({
                el: "#app1",
                data: {
                    newTask: '',
                    tasks: ["买一本书", "给爸妈打电话", "整理自己的硬盘"]
                },
                methods: {
                    addNew: function() {
                        this.tasks.unshift(this.newTask);
                        this.newTask = '';
                    },
                    removeItem: function(index) {
                        if(confirm('确定要移除吗?')) {
                            this.tasks.splice(index, 1);
                        }
                    }
                }
            });
        </script>
    </body>
</html>

结果:

1.7、数组变化侦测

1.7.1、变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
1. push () 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

2. pop () 方法数组最后一位元素删除并返回数组的最后一个元素。

3. shift () 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。

4. unshift () 方法可向数组的开头添加一个或更多元素,并返回新的长度。

5.splice (index,howmany,item1, …, itemX) 方法向 / 从数组中添加 / 删除项目,然后返回被删除的项目
第一个参数:表示从哪个索引位置(index)添加 / 删除元素
第二个参数:要删除的项目数量。如果设置为 0,则不会删除项目。
第三个参数:可选。向数组添加的新项目。

6. sort () 方法对原列表进行排序,如果指定参数,则使用比较函数指定的比较函数。
arr.sort (sortby) 可选。规定排序顺序。必须是函数。

7. 例:大小排序

function sortNumber (a, b) {
  return a - b
}
let arr = [10,5,40,25,1000,1]
arr.sort(sortNumber)
console.log(arr) // [1, 5, 10, 25, 40, 1000]
8. reverse () 方法颠倒数组中元素的顺序。

9. 替换数组

它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

10.filter () 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

11.concat () 方法用于连接两个或多个数组。

12.slice () 方法可从已有的数组中返回选定的元素。

1.7.2、替换一个数组

变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter()concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:

// `items` 是一个数组的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))

你可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,情况并非如此。Vue 实现了一些巧妙的方法来最大化对 DOM 元素的重用,因此用另一个包含部分重叠对象的数组来做替换,仍会是一种非常高效的操作。

<template>
  {{ arr1 }}

  <button @click="changeArray">修改数组</button>
</template>

<script lang="ts">
import { reactive, ref } from "vue";
export default {
  setup() {
    let arr1 = ref([1, 2, 3, 4, 5, 6]);

    function changeArray() {
      arr1.value = arr1.value.filter((a) => a % 2 == 0);
    }
    return { arr1, changeArray };
  },
};
</script>
<style scoped></style>

1.7.3、展示过滤或排序后的结果

有时,我们希望显示数组经过过滤或排序后的内容,而不实际变更或重置原始数据。在这种情况下,你可以创建返回已过滤或已排序数组的计算属性。

举例来说:

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})
<li v-for="n in evenNumbers">{{ n }}</li>

在计算属性不可行的情况下 (例如在多层嵌套的 v-for 循环中),你可以使用以下方法:

const sets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

function even(numbers) {
  return numbers.filter((number) => number % 2 === 0)
}
<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

在计算属性中使用 reverse() 和 sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:

- return numbers.reverse()
+ return [...numbers].reverse()

二、Class 与 Style 绑定

数据绑定一个常见需求是操作元素的 class 列表和它的内联样式。因为它们都是属性 ,我们可以用v-bind 处理它们:只需要计算出表达式最终的字符串。不过,字符串拼接麻烦又易错。因此,在 v-bind 用于 class 和 style 时, Vue.js 专门增强了它。表达式的结果类型除了字符串之外,还可以是对象或数组。

2.1、绑定 HTML Class

2.1.1、对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class 。

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 classactive 的更新将取决于数据属性 isActive 是否为 真值 。

<template>
  <h2 :class="{ active: isActive }">{{ isActive ? "激活" : "未激活" }}</h2>
  <button @click="isActive = !isActive">isActive:{{ isActive }}</button>
</template>

<script lang="ts" setup>
import { computed } from "@vue/reactivity";
import { reactive, ref, toRefs } from "vue";

let isActive = ref(false);
</script>
<style scoped>
.active {
  background: #9f9;
}
</style>

我们也可以在对象中传入更多属性用来动态切换多个 class 。此外, v-bind:class 指令可以与普通的 class 属性共存。如下模板:

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

如下 data:

data: {
  isActive: true,
  hasError: false
}

渲染为:

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

当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError的值为 true , class列表将变为 "static active text-danger"

你也可以直接绑定数据里的一个对象:

<div v-bind:class="classObject"></div>
data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}

渲染的结果和上面一样。我们也可以在这里绑定返回对象的 计算属性。这是一个常用且强大的模式:

<div v-bind:class="classObject"></div>
data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal',
    }
  }
}

示例:

<template>
  <h2 :class="{ active: isActive, 'text-danger': true }">
    {{ isActive ? "激活" : "未激活" }}
  </h2>
  <button @click="isActive = !isActive">isActive:{{ isActive }}</button>

  <p>
    <label>价格:</label>
    <input v-model="price" />
    <span class="valid" :class="{ requiredPrice }">请输入价格</span>
    <span class="valid" :class="{ rangePrice }">价格只能是0-10000之间</span>
  </p>
</template>

<script lang="ts" setup>
import { computed } from "@vue/reactivity";
import { reactive, ref, toRefs } from "vue";

let isActive = ref(false);
let price = ref();
let requiredPrice = computed(() => {
  return price + "" === "" || isNaN(price.value);
});
let rangePrice = computed(() => {
  return price.value < 0 || price.value > 10000;
});
</script>
<style scoped>
.active {
  background: #9f9;
}
.text-danger {
  color: orangered;
}
.valid {
  color: orangered;
  display: none;
}
.requiredPrice,
.rangePrice {
  display: inline;
}
</style>

结果:

 

 

 

2.1.2、数组语法

我们可以把一个数组传给 v-bind:class ,以应用一个 class 列表:

<div v-bind:class="[activeClass, errorClass]">
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

渲染为:

<div class="active text-danger"></div>

如果你也想根据条件切换列表中的 class ,可以用三元表达式:

<div v-bind:class="[isActive ? activeClass : '', errorClass]">

此例始终添加 errorClass ,但是只有在 isActive 是 true 时添加 activeClass 。

不过,当有多个条件 class 时这样写有些繁琐。可以在数组语法中使用对象语法:

<div v-bind:class="[{ active: isActive }, errorClass]">

示例:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>Class 与 Style 绑定</title>
    </head>

    <body>
        <div id="app1">
            <span class="bgGreen" v-bind:class="[isHide,isRight]">span3</span>
            <span class="bgGreen" v-bind:class="[(isShow?'bg3':''),isRight,bg4]">span4</span>
            <span class="bgGreen" v-bind:class="[{bg3:isShow},isRight,bg4]">span5</span>
        </div>

        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            var app1 = new Vue({
                el: "#app1",
                data: {
                    isShow: true,
                    isHide: 'bg1',
                    isRight: 'bg2',
                    price: 0
                }

            });
        </script>
    </body>

</html>

结果:

2.1.3、With Components

This section assumes knowledge of  Vue Components. Feel free to skip it and come back later.

When you use the class attribute on a custom component, those classes will be added to the component’s root element. Existing classes on this element will not be overwritten.

For example, if you declare this component:

Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

Then add some classes when using it:

<my-component class="baz boo"></my-component>

The rendered HTML will be:

<p class="foo bar baz boo">Hi</p>

The same is true for class bindings:

<my-component v-bind:class="{ active: isActive }"></my-component>

When isActive is truthy, the rendered HTML will be:

<div class="foo bar active"></div>

2.2、绑定内联样式

2.2.1、对象语法

v-bind:style 的对象语法十分直观——看着非常像 CSS ,其实它是一个 JavaScript 对象。 CSS 属性名可以用驼峰式(camelCase)或短横分隔命名(kebab-case):

<template>
  <h2 :style="{ background: background, fontSize: fontSize + 'px' }">样式表</h2>
</template>

<script lang="ts" setup>
import { computed } from "@vue/reactivity";
import { reactive, ref, toRefs } from "vue";
let background = ref("red");
let fontSize = ref(30);
</script>
<style scoped>
</style>

直接绑定到一个样式对象通常更好,让模板更清晰:

<template>
  <h2 :style="objStyle">样式表</h2>
</template>

<script lang="ts" setup>
let objStyle = {
  color: "blue",
  textDecoration: "underline",
};
</script>
<style scoped></style>

同样的,对象语法常常结合返回对象的计算属性使用。

2.2.2、数组语法

v-bind:style 的数组语法可以将多个样式对象应用到一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]">

 

 

 

<template>
  <h2 :style="[objStyle, borderStyle]">样式表</h2>
</template>

<script lang="ts" setup>
let borderStyle = {
  border: "1px solid #666",
};
let objStyle = {
  color: "blue",
  textDecoration: "underline",
};
</script>
<style scoped></style>

2.2.3、自动添加前缀

当 v-bind:style 使用需要特定前缀的 CSS 属性时,如 transform ,Vue.js 会自动侦测并添加相应的前缀。

官方帮助: http://vuejs.org/guide/class-and-style.html

2.3、引入外部样式

1.main.ts 引入(推荐)

一般src目录下新建styles文件夹, 该方式能解析less的语法
import "@/styles/index.less";

2.App.vue的style标签内引入
<style>
@import "./styles/index.less"
</style>
3.index.html 引入
<link href="index.css" type="text/css" rel="stylesheet" /> #文件引入
#或者
<style>
.test{
color: red;
}
</style>

三、ES6新增数组方法

ECMAScript2015中新增了9个方法,分别是:

  1. Array.prototype.indexOf
  2. Array.prototype.lastIndexOf
  3. Array.prototype.every
  4. Array.prototype.some
  5. Array.prototype.forEach
  6. Array.prototype.map
  7. Array.prototype.filter
  8. Array.prototype.reduce
  9. Array.prototype.reduceRight

3.1、indexOf()找到的元素位置

indexOf()方法返回在该数组中第一个找到的元素位置,如果它不存在则返回-1。
不使用indexOf时:

var arr = ['apple','orange','pear'],
found = false;
for(var i= 0, l = arr.length; i< l; i++){
if(arr[i] === 'orange'){
found = true;
}
}
console.log("found:",found);

使用后:

var arr = ['apple','orange','pear'];
console.log("found:", arr.indexOf("orange") != -1);

3.2、filter()过滤

该filter()方法创建一个新的匹配过滤条件的数组。
不用 filter() 时

var arr = [
{"name":"apple", "count": 2},
{"name":"orange", "count": 5},
{"name":"pear", "count": 3},
{"name":"orange", "count": 16},
];
var newArr = [];
for(var i= 0, l = arr.length; i< l; i++){
if(arr[i].name === "orange" ){
newArr.push(arr[i]);
}
}
console.log("Filter results:",newArr);

用了 filter():

var arr = [
{"name":"apple", "count": 2},
{"name":"orange", "count": 5},
{"name":"pear", "count": 3},
{"name":"orange", "count": 16},
];

var newArr = arr.filter(function(item){
return item.name === "orange";
});
console.log("Filter results:",newArr);

3.3、forEach()迭代

forEach为每个元素执行对应的方法

var arr = [1,2,3,4,5,6,7,8];

// Uses the usual "for" loop to iterate
for(var i= 0, l = arr.length; i< l; i++){
console.log(arr[i]);
}

console.log("========================");

//Uses forEach to iterate
arr.forEach(function(item,index){
console.log(item);
});

forEach是用来替换for循环的

3.4、map()映射

map()对数组的每个元素进行一定操作(映射)后,会返回一个新的数组

不使用map:

var oldArr = [{first_name:"Colin",last_name:"Toh"},{first_name:"Addy",last_name:"Osmani"},{first_name:"Yehuda",last_name:"Katz"}];

function getNewArr(){

var newArr = [];

for(var i= 0, l = oldArr.length; i< l; i++){
var item = oldArr[i];
item.full_name = [item.first_name,item.last_name].join(" ");
newArr[i] = item;
}

return newArr;
}

console.log(getNewArr());

使用map后:

var oldArr = [{first_name:"Colin",last_name:"Toh"},{first_name:"Addy",last_name:"Osmani"},{first_name:"Yehuda",last_name:"Katz"}];

function getNewArr(){

return oldArr.map(function(item,index){
item.full_name = [item.first_name,item.last_name].join(" ");
return item;
});

}

console.log(getNewArr());

map()是处理服务器返回数据时是一个非常实用的函数。

3.5、reduce()累加器

reduce()可以实现一个累加器的功能,将数组的每个值(从左到右)将其降低到一个值。
说实话刚开始理解这句话有点难度,它太抽象了。
场景: 统计一个数组中有多少个不重复的单词
不使用reduce时:

var arr = ["apple","orange","apple","orange","pear","orange"];

function getWordCnt(){
var obj = {};

for(var i= 0, l = arr.length; i< l; i++){
var item = arr[i];
obj[item] = (obj[item] +1 ) || 1;
}

return obj;
}

console.log(getWordCnt());

使用reduce()后

var arr = ["apple","orange","apple","orange","pear","orange"];

function getWordCnt(){
return arr.reduce(function(prev,next){
prev[next] = (prev[next] + 1) || 1;
return prev;
},{});
}

console.log(getWordCnt());

让我先解释一下我自己对reduce的理解。reduce(callback, initialValue)会传入两个变量。回调函数(callback)和初始值(initialValue)。假设函数它有个传入参数,prev和next,index和array。prev和next你是必须要了解的。
一般来讲prev是从数组中第一个元素开始的,next是第二个元素。但是当你传入初始值(initialValue)后,第一个prev将是initivalValue,next将是数组中的第一个元素。
比如:

/*
* 二者的区别,在console中运行一下即可知晓
*/

var arr = ["apple","orange"];

function noPassValue(){
return arr.reduce(function(prev,next){
console.log("prev:",prev);
console.log("next:",next);

return prev + " " +next;
});
}
function passValue(){
return arr.reduce(function(prev,next){
console.log("prev:",prev);
console.log("next:",next);

prev[next] = 1;
return prev;
},{});
}

console.log("No Additional parameter:",noPassValue());
console.log("----------------");
console.log("With {} as an additional parameter:",passValue());

示例:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>列表渲染</title>
    </head>

    <body>
        <div id="app1">
            <span v-for="n in items">
                    {{n}} 
                </span>
            <button @click="indexOfMethod">indexOf()找到的元素位置</button>
            <button @click="filterMethod">filter()过滤</button>
            <button @click="forEachMethod">forEach()迭代</button>
            <button @click="mapMethod">map()映射</button>
            <button @click="reduceMethod">reduce()累加器</button>
        </div>
        <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            var app1 = new Vue({
                el: "#app1",
                data: {
                    items: [1, 3, 7, 9, 2, 4, 6, 8, 3],
                    fruits: [{
                            "name": "apple",
                            "count": 2
                        },
                        {
                            "name": "orange",
                            "count": 5
                        },
                        {
                            "name": "pear",
                            "count": 3
                        },
                        {
                            "name": "orange",
                            "count": 16
                        }
                    ],
                    words: ["apple", "orange", "apple", "orange", "pear", "orange"]
                },
                methods: {
                    indexOfMethod: function() {
                        console.log("数字3第一次出现的位置是:" + this.items.indexOf(3));
                        console.log("数字5第一次出现的位置是:" + this.items.indexOf(5));
                    },
                    filterMethod: function() {
                        //获得数量不小于5的水果
                        var arr1 = this.fruits.filter(function(f) {
                            return f.count >= 5;
                        });
                        console.log(JSON.stringify(arr1));
                        //获得名称中含有r的水果
                        var arr2 = this.fruits.filter(function(f) {
                            return f.name.match(/r/igm);
                        });
                        console.log(JSON.stringify(arr2));
                    },
                    forEachMethod: function() {
                        this.fruits.forEach(function(obj, index) {
                            console.log(index + "-" + obj.name + "-" + obj.count);
                        });
                    },
                    mapMethod: function() {
                        var arr3 = this.fruits.map(function(obj, index) {
                            obj.showInfo = index + "->水果:" + obj.name + ",数量:" + obj.count;
                            return obj;
                        });
                        console.log(JSON.stringify(arr3));
                    },
                    reduceMethod: function() {
                        var objs = {};
                        for(var i = 0, l = this.words.length; i < l; i++) {
                            var item = this.words[i];
                            objs[item] = (objs[item] + 1) || 1;
                        }
                        console.log(JSON.stringify(objs));

                        var objs2 = this.words.reduce(function(prev, next) {
                            console.log("prev:", JSON.stringify(prev));
                            console.log("next:", JSON.stringify(next));
                            prev[next] = (prev[next] + 1) || 1;
                            return prev;
                        }, {});
                        console.log(JSON.stringify(objs2));
                    }
                }
            });
        </script>
    </body>

</html>

结果:

结果

四、VUE UI框架

4.1、PC端UI库

4.1.1、 element-plus(PC端) 

饿了么UI库

文档地址:https://element-plus.org/zh-CN/

npm install element-plus --save

4.1.2、完整引入

如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。

// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

4.1.3、Volar 支持

如果您使用 Volar,请在 tsconfig.json 中通过 compilerOptions.type 指定全局组件类型。

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

4.1.4、按需导入

您需要使用额外的插件来导入要使用的组件。

自动导入推荐

首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

npm install -D unplugin-vue-components unplugin-auto-import

然后把下列代码插入到你的 Vite 或 Webpack 的配置文件中

// webpack.config.js
const { defineConfig } = require('@vue/cli-service')

// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack:{
    plugins: [
      AutoImport({
        resolvers: [ElementPlusResolver()],
      }),
      Components({
        resolvers: [ElementPlusResolver()],
      }),
    ],
  }
})

想了解更多打包 (RollupVue CLI) 和配置工具,请参考 unplugin-vue-components 和 unplugin-auto-import

4.1.5、手动导入

Element Plus 提供了基于 ES Module 开箱即用的 Tree Shaking 功能。

但是你需要安装 unplugin-element-plus 来导入样式。 请参考 文档 了解如何配置它。

App.vue

<template>
  <el-button>I am ElButton</el-button>
</template>
<script>
  import { ElButton } from 'element-plus'
  export default {
    components: { ElButton },
  }
</script>
// vite.config.ts
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  // ...
  plugins: [ElementPlus()],
})

WARNING

如果您使用 unplugin-element-plus 并且只使用组件 API,您需要手动导入样式。

示例︰

import 'element-plus/es/components/message/style/css'
import { ElMessage } from 'element-plus'

4.1.6、快捷搭建项目模板

我们提供了 Vite 模板。 对于 Laravel 用户,我们也准备了相应的模板,同样可以直接下载使用。

4.1.7、全局配置

在引入 Element Plus 时,可以传入一个包含 size 和 zIndex 属性的全局配置对象。 size 用于设置表单组件的默认尺寸,zIndex 用于设置弹出组件的层级,zIndex 的默认值为 2000。

完整引入:

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus, { size: 'small', zIndex: 3000 })

按需引入:

<template>
  <el-config-provider :size="size" :z-index="zIndex">
    <app />
  </el-config-provider>
</template>

<script>
import { defineComponent } from 'vue'
import { ElConfigProvider } from 'element-plus'

export default defineComponent({
  components: {
    ElConfigProvider,
  },
  setup() {
    return {
      zIndex: 3000,
      size: 'small',
    }
  },
})
</script>

4.1.18、CDN引入在HTML中直接使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- Import style -->
    <link
      rel="stylesheet"
      href="//cdn.jsdelivr.net/npm/element-plus/dist/index.css"
    />
    <!-- Import Vue 3 -->
    <script src="//cdn.jsdelivr.net/npm/vue@3"></script>
    <!-- Import component library -->
    <script src="//cdn.jsdelivr.net/npm/element-plus"></script>
  </head>
  <body>
    <div id="app">
      <el-button @click="messageHandle">{{message}}</el-button>
    </div>
    <script>
      const { ElMessage } = ElementPlus;
      let app = Vue.createApp({
        data() {
          return {
            message: "Hello Element Plus!",
          };
        },
        setup() {
          function messageHandle() {
            ElMessage({
              message: "操作成功了!",
              type: "success",
              "show-close": true,
            });
          }
          return { messageHandle };
        },
      })
        .use(ElementPlus)
        .mount("#app");
    </script>
  </body>
</html>

2. tdesign-vue-next(PC端)
腾讯优质 UI 组件,配套工具完满,设计工整,文档清晰
文档地址:https://tdesign.tencent.com/vue-next/overview

3. arco-design-vue(PC端)
字节跳动 UI 组件库开源,大厂逻辑,设计文档完美
文档地址:https://arco.design/vue/docs/start

4. ant-design-vue(PC端)
蚂蚁前端 UI 库,面向企业级中后台
文档地址:https://www.antdv.com/components/overview-cn

5. naive-ui(PC端)
宝藏 Vue UI 库,Vue UI 新星,从 Vue 3 起步
文档地址:https://www.naiveui.com/zh-CN/os-theme/docs/introduction

6. Buefy(PC端)
为基于 Bulma 的 Vue.js 提供了轻量级的 UI 组件
文档地址:https://buefy.org/documentation

7. Vue Material(PC端)
简单、轻巧,并且完全按照 Google Material Design 规范构建
文档地址:https://www.creative-tim.com/vuematerial/components/app

8. Vuesax(PC端)
一个用Vuesax创建的UI组件框架,具有独特和愉快的风格
文档地址:https://vuesax.com/docs/guide/

9. Vuestic(PC端)
完全兼容Vue.js 3,包含50多个功能丰富的组件,可用于任何设计解决方案,允许通过配置和CSS变量全局配置组件,完全响应和支持现代浏览器(除IE11),与i18n无缝集成
文档地址:https://vuestic.dev/zh/introduction/overview

10. View UI Plus(PC端)
View UI Plus 是 View Design 设计体系中基于 Vue.js 3 的一套 UI 组件库,主要用于企业级中后台系统。
文档地址:https://www.iviewui.com/view-ui-plus/guide/introduce

https://github.com/iview/iview
https://iviewui.com/

4.2、移动端UI库

4.2.1. vant-ui(移动端)

有赞团队开源移动 UI 组件库,全面支持 Vue 3,针对移动端
文档地址:https://vant-contrib.gitee.io/vant/#/zh-CN

4.2.2、在HTML中直接使用vant,CDN依赖

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入样式文件 -->
    <link
      rel="stylesheet"
      href="https://fastly.jsdelivr.net/npm/vant@3/lib/index.css"
    />
  </head>
  <body>
    <div id="app">
      <van-button @click="showInfo">按钮</van-button>
    </div>
    <!-- 引入 Vue 和 Vant 的 JS 文件 -->
    <script src="https://fastly.jsdelivr.net/npm/vue@3"></script>
    <script src="https://fastly.jsdelivr.net/npm/vant@3/lib/vant.min.js"></script>

    <script>
      // 在 #app 标签下渲染一个按钮组件
      const app = Vue.createApp({
        setup() {
          function showInfo() {
            vant.Toast("提示");
          }
          return { showInfo };
        },
      });
      app.use(vant);

      // 通过 CDN 引入时不会自动注册 Lazyload 组件
      // 可以通过下面的方式手动注册
      app.use(vant.Lazyload);

      app.mount("#app");
    </script>
  </body>
</html>

2. nutui(移动端)
京东出品,移动端友好,面向电商业务场景
文档地址:https://nutui.jd.com/#/guide/intro

3. vuetify(移动端)
老牌 Vue UI ,基于谷歌的 Material Design 样式开发
文档地址:https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides

4. varlet(移动端)
Varlet 是一个基于 Vue3 开发的 Material 风格移动端组件库,全面拥抱 Vue3 生态,由社区建立起来的组件库团队进行维护。
文档地址:https://varlet.gitee.io/varlet-ui/#/zh-CN/home

5. tdesign-mobile-vue(移动端)
tdesign的移动端版本UI库,面向vue3
文档地址:https://tdesign.tencent.com/mobile-vue/getting-started

4.3、PC端后台管理

4.3.1、打不开github的方法

记住三个网站:

github网址查询:https://ipaddress.com/website/github.com

github域名查询:https://ipaddress.com/website/github.global.ssl.fastly.net

github静态资源ip:https://ipaddress.com/website/assets-cdn.github.com

1、打开hosts文件(C:\Windows\System32\drivers\etc)

2、然末尾放入一下两个 IP 地址:

GitHub Start
140.82.114.4 github.com
199.232.69.194 github.global.ssl.fastly.net
# GitHub End

保存退出

3、在 CMD 命令行中执行下面语句 来刷新 DNS,重启浏览器之后就能进入Github 网址。

vue-admin-better

vue-pure-admin

Geeker-Admin

 

 

 

 

 

 

 

五、数制转换

JS中,通过利用js方法,可以很方便的实现2,8,10,16进制之间的相互转换

1、2,8,16进制格式的数据转换到10进制数据

var num=parseInt(arg1,arg2);

第一个参数就是需要转换为10进制的数,arg2就是被转换数据的进制值,可以是2,8,16等。

如:将十六进制的数‘d9’转为十进制数:

var num=parseInt(d9,16);//num=217

2、将10进制格式的数据转为2,8,16进制格式数据

var num=parseInt(“217”);//如果这个数是字符串格式的,需要执行这一步
var oxNum=num.toString(16);//参数可以是2,8,16.设置转换10进制数据到对应进制格式,本例是将num转成16进制数据 oxNum=d9

3、2,8,10,16任意进制之间转化

通过10进制作为媒介,便可以实现任意进制之间相互转化了

六、示例下载

https://gitee.com/zhangguo5/vue3_-chapter1.git

小红书项目要求:

http://www.cnblogs.com/xsblog/p/8144290.html

七、作业

7.1、完成一个简单的南方打游戏,页面随机出现字符(1级出现5个,2级10个,一直增加;打完50个没有错则升级到下一级;第n关打错n个就结束),字符可以下落,可以有动画效果;

7.2、可以滚动的“回顶端”

 不允许使用fixed布局,要求使用鼠标滚动事件,要有动画效果,延迟下移

7.3、请完成一个只能输入数字与-的文本框

7.4、完成一个不能输入空格的文本框

7.5、完成上课的每一个示例

7.6、请完成一个商品管理模块,要求如下:

  • 使用Element+vue技术实现
  • 添加与修改功能使用模式窗口
  • 支持全选与反选,隔行换色与光棒效果
  • 详细是点击时在弹出层中显示当前商品的所有信息
  • 尝试分页(选做)

7.7、增强商品管理

  • 数据从后台加载,请注意跨域(axios)
  • 实现搜索功能(Lodash)
  • 有验证、有错误提示
  • 增加删除与添加时的动画效果(选作)
  • 了解UIKit(选作)

7.8、请完成一个智能机器人,重点练习使用vue-cli+app界面(任意框架)

接口:http://www.itpk.cn/robot.php、http://www.haoservice.com/docs/119

打包后在手机端根据提问给出答案,app界面

7.9、请随机生成100个字母,放入数组中,统计每个字母出现的次数,使用两种方法使用:a不使用reduce,b使用reduce

八、视频

【Vue3 + Vuex + Pinia + TypeScript + Router】 https://www.bilibili.com/video/BV1at4y1F75D?share_source=copy_web&vd_source=475a31f3c5d6353a782007cd4c638a8a

posted @ 2022-10-26 14:57  张果  阅读(2362)  评论(0编辑  收藏  举报
AmazingCounters.com