官方文档:https://v3.cn.vuejs.org/guide/introduction.html

安装

参考:https://v3.cn.vuejs.org/guide/installation.html

Vue.js 添加到项目中主要有如下四种方式:

  1. 像jquery那种直接在页面中引入托管在CDN上的js文件
  2. 和1类似,不过是下载JS文件到本地,然后直接引用(速度比较快,而且放到本地放心)
  3. 使用npm安装
  4. 使用官方的vue-cli来构建一个项目,它为现代前端工作流程提供了功能齐备的构建设置 (例如,热重载、保存时的提示等等)

从CDN导入

<script src="https://unpkg.com/vue@next"></script>

[!tip]

使用GOOGLE搜索vuejs CDN 加速,可以尝试去找一下其他的加速源

一定要注意这个地址能不能访问,比如我见过很多次的jsdelivr.net,我现在的网络无法访问

image-20220607172653916

下载到本地

可以在如下的CDN地址上下载

  1. unpkg
  2. jsDelivr

通常需要同时下载开发环境构建版本 vue.global.js以及生产环境构建版本 vue.runtime.global.js

NPM

Vue构建大型应用时推荐使用npm安装,可以和webpack配合使用

# 最新稳定版
$ npm install vue@next

如果想使用单文件组件(.vue文件),那么还需要安装

$ npm install -D @vue/compiler-sfc

大多数情况下,更倾向于使用 Vue CLI 来创建一个配置最小化的 webpack 构建版本。

命令行工具

Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了功能齐备的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。

[!note]

等熟悉vue了再回头过来看

对于 Vue 3,你应该使用 npm 上可用的 Vue CLI v4.5 作为 @vue/cli。要升级,你应该需要全局重新安装最新版本的 @vue/cli

yarn global add @vue/cli
# 或
npm install -g @vue/cli

然后在 Vue 项目中运行:

vue upgrade --next

附加:不同情况使用不同的JS

使用 CDN 或没有构建工具

vue(.runtime).global(.prod).js

  • 若要通过浏览器中的 <script src="..."> 直接使用,则暴露 Vue 全局。
  • 浏览器内模板编译:
    • vue.global.js 是包含编译器和运行时的“完整”构建版本,因此它支持动态编译模板。
    • vue.runtime.global.js 只包含运行时,并且需要在构建步骤期间预编译模板。
  • 内联所有 Vue 核心内部包——即:它是一个单独的文件,不依赖于其他文件。这意味着你必须导入此文件和此文件中的所有内容,以确保获得相同的代码实例。
  • 包含硬编码的 prod/dev 分支,并且 prod 构建版本是预先压缩过的。将 *.prod.js 文件用于生产环境。

vue(.runtime).esm-browser(.prod).js

  • 用于通过原生 ES 模块导入使用 (在浏览器中通过 <script type="module"> 来使用)。
  • 与全局构建版本共享相同的运行时编译、依赖内联和硬编码的 prod/dev 行为。

使用构建工具

vue(.runtime).esm-bundler.js

  • 用于 webpackrollupparcel 等构建工具。

  • 留下 prod/dev 分支的 process.env.NODE_ENV 守卫语句 (必须由构建工具替换)。

  • 不提供压缩版本 (打包后与其余代码一起压缩)。

  • import 依赖 (例如:@vue/runtime-core@vue/runtime-compiler)

    • 导入的依赖项也是 esm bundler 构建版本,并将依次导入其依赖项 (例如:@vue/runtime-core imports @vue/reactivity)。
    • 这意味着你可以单独安装/导入这些依赖,而不会导致这些依赖项的不同实例,但你必须确保它们都为同一版本。
  • 浏览器内模板编译:

    • vue.runtime.esm-bundler.js (默认) 仅运行时,并要求所有模板都要预先编译。这是构建工具的默认入口 (通过 package.json 中的 module 字段),因为在使用构建工具时,模板通常是预先编译的 (例如:在 *.vue 文件中)。
    • vue.esm-bundler.js 包含运行时编译器。如果你使用了一个构建工具,但仍然想要运行时的模板编译 (例如,DOM 内 模板或通过内联 JavaScript 字符串的模板),请使用这个文件。你需要配置你的构建工具,将 vue 设置为这个文件。

对于服务端渲染

vue.cjs(.prod).js

  • 通过 require() 在 Node.js 服务器端渲染使用。
  • 如果你将应用程序与带有 target: 'node' 的 webpack 打包在一起,并正确地将 vue 外部化,则将加载此文件。
  • dev/prod 文件是预构建的,但是会根据 process.env.NODE_ENV 自动加载相应的文件。

附加:NPM镜像源

Hello World

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>TEST</title>
<script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="hello-vue" class="demo">
        {{ message }}
    </div>
    <script type="text/javascript">
        const HelloVueApp = {
          data() {
            return {
              message: 'Hello Vue!!'
            }
          }
        }

        Vue.createApp(HelloVueApp).mount('#hello-vue')
    </script>
</body>
</html>

快速上手

声明式渲染

[!tip]

推荐使用VS Code,自动补全真的很舒服

渲染内容

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统;

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="number">
      Count: {{ count }}
    </div>

    <script type="text/javascript">
        const Count = {
          data() {
            return {
              count: 0
            }
          }
        }
        Vue.createApp(Count).mount('#number')
    </script>
</body>
</html>

如上,此时DOM节点已经被建立了关联,可以动态修改,我们尝试让count值递增

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="number">
      Count: {{ count }}
    </div>

    <script type="text/javascript">
        const Count = {
          data() {
            return {
              count: 0
            }
          },
          mounted() {
            setInterval(
              () => {
                this.count++
              }, 1000
            )
          }
        }
        Vue.createApp(Count).mount('#number')
    </script>
</body>
</html>

补充:箭头函数

箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数

基础语法如下:

(param1, param2,, paramN) => { statements }
(param1, param2,, paramN) => expression
//相当于:(param1, param2, …, paramN) =>{ return expression; }

// 当只有一个参数时,圆括号是可选的:
(singleParam) => { statements }
singleParam => { statements }

// 没有参数的函数应该写成一对圆括号。
() => { statements }

渲染属性

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="number">
      <span v-bind:title="title">TEST</span>
    </div>

    <script type="text/javascript">
        const titleBind = {
          data() {
            return {
              title: "TITLE TEST"
            }
          }
        }
        Vue.createApp(titleBind).mount('#number')
    </script>
</body>
</html>

和渲染内容类似,不过在渲染内容用的是{{ var }},在渲染属性的时候用的v-bind:v-bind:attr被称为指令,指令带有前缀v-,表示是vue提供的特殊属性。

处理输入

事件监听

刚才说了带有v-的是指令,检查用户交互,用到的是v-on指令来添加一个事件监听器,类似js中的onxxx事件

[!tip]

这段代码我用vue自动补全写的,感觉效果还不错

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="onxxx">
      <p> {{ message }} </p>
      <button v-on:click="alertData">TEST</button>
    </div>

    <script type="text/javascript">
        const test = Vue.createApp({
          data() {
            return {
              message: "TEST"
            }
          },
          methods: {
            alertData() {
              alert(this.message)
            }
          },
        })
        test.mount("#onxxx")
    </script>
</body>
</html>

数据内容同步

有的时候,多个地方想要实时更新一样的数据,比如1个地方在属性,1个地方在内容中,就可以用v-model指令来实现,如下

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="onxxx">
      <p v-model:title="message"> {{ message }} </p>
      <input v-model="message" />
    </div>

    <script type="text/javascript">
        const test = Vue.createApp({
          data() {
            return {
              message: "TEST"
            }
          },
        })
        test.mount("#onxxx")
    </script>
</body>
</html>

条件循环

IF

通过指令v-if来实现,如果为真则显示,否则不显示标签

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="onxxx">
      <p v-if="judge"> TEST </p>
      <button v-on:click="click" >click</button>
    </div>

    <script type="text/javascript">
        const test = Vue.createApp({
          data() {
            return {
              judge: true
            }
          },
          methods: {
            click() {
              this.judge = !this.judge
            }
          },
        })
        test.mount("#onxxx")
    </script>
</body>
</html>

FOR

v-for指令

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="onxxx">
      <p v-for="data in manyData"> {{ data }} </p>
    </div>

    <script type="text/javascript">
        const test = Vue.createApp({
          data() {
            return {
              manyData: [
                "99999",
                {text: "123"},
                {text: "456"},
                {text: "999"},
              ]
            }
          },
        })
        test.mount("#onxxx")
    </script>
</body>
</html>

组件化应用构建(解耦)

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

Component Tree

在 Vue 中,组件本质上是一个具有预定义选项的实例。在 Vue 中注册组件很简单:如对 app 对象所做的那样创建一个组件对象,并将其定义在父级组件的 components 选项中:

[!note]

模块化,解耦,感觉一般用于定义一个模板来重复使用,或者给for循环来用

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="todo-list">
      <!-- 这里很重要的属性 key 和 自定义的to,key我还不知道干啥,教程说后面会讲 -->
      <todo-item v-for="item in todoList" v-bind:title="item.title" v-bind:key="item.id" v-bind:todo="item">
      </todo-item>
    </div>

    <script type="text/javascript">
      // 组件模板
       const TodoItem = {
         props: ['todo'], // 属性,后面需要用
         template: `<li>{{ todo.title }}</li><br/><p>{{ todo.title }}</p>`
       }

       // Vue应用内容
       const TodoList = {
         data() {
           return {
             todoList : [
               {id: 1, title: "text"},
               {id: 2, title: "test1"},
               {id: 3, title: "text2"},
             ]
           }
         },
         components: {
           TodoItem
         }
       }

       // 创建Vue应用
       const app = Vue.createApp(TodoList)
       // 挂载Vue应用
       app.mount("#todo-list")
    </script>
</body>
</html>

[!warning|style=flat]

注意:

  1. 组件要和标签匹配,驼峰命名法
  2. 绑定的keytodo属性不会显示出来,因为todo属性是拿到组件里面去用了

image-20220608165652292

应用&组件实例

应用实例createApp

其他调用方法说明文档:参考

前面用到很多了,就是VuecreateApp函数,用来创建一个应用实例

    const app = Vue.createApp({
        // 内容
      })

可以在后面跟加一些方法

const app = Vue.createApp({})
app.component('TestComponent', TestComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)

也可以链式调用

const app = Vue.createApp({})
  .component('TestComponent', TestComponent)
  .directive('focus', FocusDirective)
  .use(LocalePlugin)

根组件(mount()返回)

传递给 createApp 的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。

const vm = app.mount('#app')

与大多数应用方法不同的是,mount 不返回应用本身。相反,它返回的是根组件实例。ViewModel,一般缩写为vm

[!note]

简单来说,我感觉就是最上游的组件,和其他组件没什么区别,配置选项都是一样的

组件实例属性(可获取到设定的变量值)

比如前面用到的data(),在 data 中定义的 property 是通过组件实例暴露的,可直接通过跟节点获取值

const app = Vue.createApp({
  data() {
    return {
      message: "test"
    }
  },
})
const vm = app.mount("#test")
console.log(vm.message) // test

其他的一些相关内容也可以获取到,如methodspropscomputedinjectsetup这些

生命周期钩子

组件在创建过程中有一个过程,如初始化、挂载、更新等,整个过程就是生命周期;而这些地方也是我们可以控制的,控制的方法就是通过生命周期钩子

created勾子函数为例,在实例创建后会执行相关的代码

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p>{{ message }}</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test"
          }
        },
        created() {
          console.log(this.message) // this 指向的是根组件vm
        },
      })
      const vm = app.mount("#test")
      console.log(vm.message) // test

    </script>
</body>
</html>

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mountedupdatedunmounted。生命周期钩子的 this 上下文指向调用它的当前活动实例。

[!tip]

不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 thisthis 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

生命周期图示

实例的生命周期

模板基础语法

前面快速上手直接上手在用,也没说为什么要这样,这一节会详细说明。

插值

文本

数据绑定最常见的形式就是使用“Mustache” (双大括号) 语法的文本插值

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

Mustache 标签将会被替代为对应组件实例中 msg property 的值(通过data()进行return返回对应的值)。内容是同步更新的,只要return的值发生了变化那么就会变化。

通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:

<span v-once>这个将不会改变: {{ msg }}</span>

HTML

如果直接插入文本,会自动转义(防止XSS),想要插入html代码的话,需要用到v-html指令

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p> TEXT: {{ message }} </p>
      <p>HTML: <span v-html="message"></span></p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "<h1>test</h1>"
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

效果如下:

image-20220609093046909

属性

前面也提到了,通过v-bind指令来实现

<p v-bind:id="message"> TEXT: {{ message }} </p>

获取值也是通过data()来实现

[!note]

注: 如果绑定的值是 nullundefined,那么该属性不会被包含在渲染的元素上。

对于布尔属性(它们只要存在就意味着值为 true),v-bind 工作起来略有不同,在这个例子中:

<button v-bind:disabled="isButtonDisabled">按钮</button>

如果 isButtonDisabled 的值是 truthy,那么 disabled attribute 将被包含在内。如果该值是一个空字符串,它也会被包括在内,与 <button disabled=""> 保持一致。对于其他 falsy 的值,该 attribute 将被省略。

truthy(真值):指的是在布尔值上下文中,转换后的值为 true 的值。被定义为假值以外的任何值都为真值。(即所有除 false0-00n""nullundefinedNaN 以外的皆为真值)。

js表达式

不直接绑定值,而是通过js表达式来实现对内容的修改

举例如下(3个p标签内):

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p> {{ message + 1 }} </p>
      <p> {{ message === "test" ? "YES" : "NO" }} </p>
      <p> {{ message.split('').reverse().join('') }} </p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test"
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

注意:在表达式中,语句和流程控制也不会生效

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

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

指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是==单个 JavaScript 表达式== (v-forv-on 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。参考我们在介绍中看到的v-if例子

<p v-bind:title='message'> {{ message + 1 }} </p>
<p v-bind:title='message === "test" ? "YES" : "NO"'> {{ message + 1 }} </p>

参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute:

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

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

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

动态参数

大多数情况下,我们的参数是确定的,比如titlehref,但也有可能出现参数动态变换的情况,那可以用方括号[]给这个值括起来,表示这是一个动态参数,vue会自动赋值

<p v-bind:[message]="message"> {{ message }} </p>

通过data()传入数据test,再由vue渲染后的结果

<p test="test">test</p>

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

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

在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

<form v-on:submit.prevent="onSubmit">...</form>

缩写

Vue 为 v-bindv-on 这两个最常用的指令,提供了特定简写

v-bind 缩写v-bind: ==> :

<!-- 完整语法 -->
<a v-bind:href="url"> ... </a>

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

<!-- 动态参数的缩写 -->
<a :[key]="url"> ... </a>

v-on 缩写v-on: ==> @

<!-- 完整语法 -->
<a v-on:click="doSomething"> ... </a>

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

<!-- 动态参数的缩写 -->
<a @[event]="doSomething"> ... </a>

注意事项

对动态参数值约定

动态参数预期会求出一个字符串,null 例外。这个特殊的 null 值可以用于显式地移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式约定

1、最好不要用<p :['test'+ 1]="message">{{ message }}</p>,这样是无效的,建议直接在传变量的时候就设定好值

<p :[message]="message">{{ message }}</p>

2、在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写

JS表达式注意事项

模板表达式都被放在沙盒中,只能访问一个受限的全局变量列表,如 MathDate。不应该在模板表达式中试图访问用户定义的全局变量。

条件IF

if else if else

前面只用了if,这里当然也可以用else还有else if

<p v-if="message">message is OK</p>
<p v-else>no message</p>

完整代码如下:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p v-if="message">message is OK</p>
      <p v-else>no message</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: false
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

v-show

另一个用于条件性展示元素的选项是 v-show 指令。用法大致一样:

<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 display CSS 属性。

v-if vs v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

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

v-ifv-for 一起使用

v-ifv-for 一起使用时,v-if 具有比 v-for 更高的优先级。所以一般不推荐一起使用

循环FOR

v-for遍历列表

v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="item in items">{{ item }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            items: [
              "111",
              "222",
              "333",
            ]
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

v-for 块中,我们可以访问所有父作用域的 property。v-for 还支持一个可选的第二个参数,即当前项的索引。

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="item, index in items">{{ index+1 }} {{ item }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            items: [
              "111",
              "222",
              "333",
            ]
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

除了用item in items,还可以用item of items,效果都是一样的

v-for遍历字典

和上面一样,字典会自动被分解成key、value

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="name, number, index of items">{{ index+1 }} {{ name }} {{ number }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            items: {
              name: "d4m1ts",
              number: "123456"
            }
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

v-for数字循环

比如要从数字1循环到10,要是写一个列表还是有点麻烦,可以用下面的方式来实现

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="item in 10">{{ item }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({})
      const vm = app.mount("#test")

    </script>
</body>
</html>

v-for在模板<template>

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        template: `
          <h1 v-for="i in 10">{{i}}</h1>
          `
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

数组变更更新

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

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

  • 利用计算属性变更后输出

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="item of items">{{ item }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            data: [
              "data1",
              "data5",
              "data2"
            ]
          }
        },
        computed: {
          items() {
            this.data.sort()
            return this.data
          }
        }
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

数组替换更新

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

  • 利用lambda过滤数据
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <li v-for="item of items">{{ item }}</li>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            data: [
                1,2,3,4,5,6,7,8
            ]
          }
        },
        computed: {
          items() {
            return this.data.filter(num => num % 2 === 0)
          }
        }
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

Data属性和方法

Data Property

和上面的 组件实例属性 类似,这里方法更多

组件的 data 选项是一个函数。Vue 会在创建新组件实例的过程中调用此函数。它应该返回一个对象,然后 Vue 会通过响应性系统将其包裹起来,并以 $data 的形式存储在组件实例中。为方便起见,该对象的任何顶级 property 也会直接通过组件实例暴露出来:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p :[message]="message">{{ message }}</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test"
          }
        },
      })
      const vm = app.mount("#test")
      console.log(vm.message) // test
      console.log(vm.$data.message) // test

      vm.message = "666"
      console.log(vm.message) // 666
      console.log(vm.$data.message) // 666

    </script>
</body>
</html>

直接将不包含在 data 中的新 property 添加到组件实例是可行的。但由于该 property 不在背后的响应式 $data 对象内,所以 Vue 的响应性系统不会自动跟踪它。(就是这里变量的值虽然变了,但是显示变量值的地方不会同步变)

Vue 使用 $ 前缀通过组件实例暴露自己的内置 API。它还为内部 property 保留 _ 前缀。你应该避免使用这两个字符开头的顶级 data property 名称。

方法

我们用 methods 选项向组件实例添加方法,它应该是一个包含所需方法的对象

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p @click="messageClick">{{ message }}</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test"
          }
        },
        methods: {
          messageClick() {
            alert(this.message)
          }
        },
      })
      const vm = app.mount("#test")
      vm.messageClick() // 直接调用函数,不需要用户交互

    </script>
</body>
</html>

Vue 自动为 methods 绑定 this,以便于它始终指向组件实例。在定义 methods 时应避免使用箭头函数,因为这会阻止 Vue 绑定恰当的 this 指向。

除此之外,方法也可以用于任何可执行的地方,如插文本值属性

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p :title="messageChange()">{{ messageChange() }}</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        methods: {
          messageChange() {
            return "123123"
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

防抖和节流

Vue 没有内置支持防抖和节流,但可以使用 Lodash 等库来实现。

如果某个组件仅使用一次,可以在 methods 中直接应用防抖,但是更好的方法,是在生命周期中设置,如created

[!tip]

  1. 延时执行
  2. 可以用template来直接确定模板,也可以用组件的形式,也可以直接写到html
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
  <script src="https://unpkg.com/[email protected]/lodash.min.js"></script>
</head>

<body>
    <div id="test"></div>

    <script type="text/javascript">
      const vm = Vue.createApp({
        created() {
          // 使用 Lodash 实现防抖(个人感觉就是延时)
          this.debouncedClick = _.debounce(this.click, 500)
        },
        unmounted() {
          // 移除组件时,取消定时器
          this.debouncedClick.cancel()
        },
        data() {
          return {
            message: "123123"
          }
        },
        methods: {
          click() {
            alert(this.message)
          }
        },
        template: `
          <p :title="message" @click="debouncedClick">{{ message }}</p>
        `
      }).mount("#test")

    </script>
</body>
</html>

计算属性和侦听器

计算属性

js表达式来渲染值很有用了,但是一般用于简单的运算,如果太复杂了,或者写太多了,就不容易维护

基本用法如下(computed属性):

[!tip]

如果data()computed指向的值为同一个,那么data()返回的值会覆盖computed

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
  <script src="https://unpkg.com/[email protected]/lodash.min.js"></script>
</head>

<body>
    <div id="test"></div>

    <script type="text/javascript">
      const vm = Vue.createApp({
        data() {
          return {
            name: "d4m1ts"
          }
        },
        computed: {
          message() {
            return "name is d4m1ts ? " + (this.name === "d4m1ts" ? "YES" : "NO")
          }
        },
        template: `
          <p :title="message">{{ message }}</p>
        `
      }).mount("#test")

    </script>
</body>
</html>

声明了一个计算属性message,且vm.name修改时,对应计算属性的值也会修改

image-20220610094217406

计算属性缓存vs方法

我刚看也在想方法可以达到同样的效果,为啥还要搞个计算属性出来,还好官方文档有写,总的来说我感觉就是节省资源

可以将同样的函数定义为一个方法,而不是一个计算属性。从最终结果来说,这两种实现方式确实是完全相同的。

不同的是计算属性将基于它们的响应依赖关系缓存。计算属性只会在相关响应式依赖发生改变时重新求值。这就意味着只要 name 还没有发生改变,多次访问 message 时计算属性会立即返回之前的计算结果,而不必再次执行函数。

[!tip]

切记要响应依赖关系才会被缓存,比如Date.now()不是响应式依赖,设定了值过后就不会再修改了

计算属性setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test"></div>

    <script type="text/javascript">
      const vm = Vue.createApp({
        data() {
          return {
            name: "d4m1ts"
          }
        },
        computed: {
          message: {
            // getter
            get() {
              return "name is " + this.name
            },
            // setter
            set(value) {
              this.name = "modify name to " + value
            }
          }
        },
        template: `
          <p :title="message">{{ message }}</p>
        `
      }).mount("#test")

    </script>
</body>
</html>

image-20220610095453983

侦听器

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

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p>{{ message1 }}</p>
      <input v-model="message" />
    </div>

    <script type="text/javascript">
      const vm = Vue.createApp({
        data() {
          return {
            message: "d4m1ts",
            message1: "test"
          }
        },
        watch: {
          message(newData, oldData) { // 监听Message,message发生变化的时候,message1也会跟着发生变化
            this.message1 = newData + oldData
          }
        }

      }).mount("#test")

    </script>
</body>
</html>

计算属性 vs 侦听器

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

Class与Style绑定

Class绑定

我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class,用法举例如下

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p class="class1" :class="message">{{ message }}</p> <!-- 手动赋值:<p class="class1 test">test</p> -->
      <p class="class1" :class="{active: isActive}">{{ message }}</p> <!-- 对象语法:<p class="class1 active">test</p> -->
      <p class="class1" :class="[message, message1]">{{ message }}</p> <!-- 数组语法:<p class="class1 test test1">test</p> -->
    </div>

    <script type="text/javascript">
      const vm = Vue.createApp({
        data() {
          return {
            message: "test",
            message1: "test1",
            isActive: true,
          }
        },
      }).mount("#test")

    </script>
</body>
</html>

也可以使用计算属性

也可以在组件上绑定,和前面一样,元素上的现有 class 将不会被覆盖

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-lable class="123" v-bind:key="message1" :data="message"></test-lable>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test",
            message1: "test1",
          }
        }
      })
      app.component("test-lable", {
        props: ['data'],
        template: `<h1 :class="data">{{ data }}</h1>`, // <h1 class="test 123">test</h1>
      })
      app.mount("#test")

    </script>
</body>
</html>

要获取到当前的属性,可以用组件的变量$attrs

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-lable class="123" v-bind:key="message1"></test-lable>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test",
            message1: "test1",
          }
        }
      })
      app.component("test-lable", {
        template: `<h1 class="message">{{ $attrs.class }}</h1>`, // <h1 class="message 123">123</h1>
      })
      app.mount("#test")

    </script>
</body>
</html>

Style绑定

:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p :style="styleObject">test</p> <!-- <p style="color: red; font-size: 30px;">test</p> -->
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            styleObject: {
              color: 'red',
              fontSize: '30px',
            },
          }
        }
      })
      app.mount("#test")

    </script>
</body>
</html>

也支持通过数组绑定多个值到style中

事件处理

监听处理事件

我们可以使用 v-on 指令 (通常缩写为 @ 符号) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript。用法为 v-on:click="methodName" 或使用快捷方式 @click="methodName"

这部分前面写过了,参考事件监听

原始DOM数据

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p> {{ data }} </p>
      <p @click="clickTest('123123', $event)">click</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            data: "tempData"
          }
        },
        methods: {
          clickTest(msg, event) {
            this.data = msg
            console.log(event)
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

多事件

同时调用多个函数,用逗号分隔就行

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p> {{ data }} </p>
      <p @click="clickTest('123123'),clickTest1('333222')">click</p>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            data: "tempData"
          }
        },
        methods: {
          clickTest(msg, event) {
            this.data = msg
            console.log(event)
          },
          clickTest1(msg){
            this.data = msg
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

具体作用如下:

<!-- 阻止单击事件继续冒泡 -->
<a @click.stop="doThis"></a>

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

<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>

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

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->
<a @click.once="doThis"></a>

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

[!tip]

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self 会阻止元素本身及其子元素的点击的默认行为,而 @click.self.prevent 只会阻止对元素自身的点击的默认行为。

按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
  • 输入框按下回车触发函数
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <input v-model="data" @keyup.enter="clickTest(data)" />
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            data: "tempData"
          }
        },
        methods: {
          clickTest(msg) {
            alert(msg)
          },
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

按键别名:

Vue 为最常用的键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统修饰键

  • .ctrl
  • .alt
  • .shift
  • .meta

[!note]

在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。

.exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

<!-- 即使 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

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

表单输入绑定

基础用法

用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值。它将始终将当前活动实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

文本

  • 输入数据同步
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p>{{ message }}</p>
      <input v-model="message" />
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "tempData"
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

多行文本(textarea)

和上面一样,就是给input改成textarea而已

复选框(checkbox)

  • 单个复选框
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

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

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            checked: false
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>
  • 多个复选框(value会被同步)
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

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

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            checkedNames: []
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

单选框(radio)

  • 同步的内容是Value
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <input type="radio" id="jack" value="Jack" v-model="checkedNames" />
      <label for="jack">Jack</label>
      <input type="radio" id="rose" value="Rose" v-model="checkedNames" />
      <label for="rose">Rose</label>
      <span>Checked names: {{ checkedNames }}</span>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            checkedNames: []
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

选择框(select)

  • 识别option的值
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <select v-model="checkedNames">
        <option disabled value="">Please select one</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>
      <span>Selected: {{ checkedNames }}</span>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            checkedNames: "A"
          }
        },
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

值绑定

上面的数据都是绑定死的,我们要动态传入值的话,用v-bind绑定对应的属性就行了

修饰符(值处理)

.lazy

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

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

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

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

当输入类型为 text 时这通常很有用。如果输入类型是 number,Vue 能够自动将原始字符串转换为数字,无需为 v-model 添加 .number 修饰符。如果这个值无法被 parseFloat() 解析,则返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

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

组件基础

基础使用

为了解耦,所以多用组件,一个基础的组件如下

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label></test-label> <!-- 要用刚才定义的label,不然定位不到内容,不知道赋值到哪 -->
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({})
      app.component('test-label', { // 定义label
        template: `<h1>test</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

因为组件是可复用的实例,所以它们与根实例接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。

组件复用

就是给自己定义的label多复制几次出来就行了,每一个label是独立维护的

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label></test-label> <!-- 要用刚才定义的label,不然定位不到内容 -->
      <test-label></test-label>
      <test-label></test-label>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({})
      app.component('test-label', { // 定义label
        template: `<h1>test</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

Component Tree

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册。至此,我们的组件都只是通过 component 方法全局注册的,全局注册的组件可以在应用中的任何组件的模板中使用

通过 Prop 向子组件传递数据

因为是子组件,就不能用直接用父组件的$data值,但是可以通过props来传递

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label :data="message"></test-label> <!-- 要用刚才定义的label,不然定位不到内容 -->
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: "test message"
          }
        },
      })

      app.component('test-label', { // 定义label
        props: ['data'],
        template: `<h1>{{data}}</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>
  • 传递数组
<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label v-for="msg in message" :data="msg"></test-label> <!-- 要用刚才定义的label,不然定位不到内容 -->
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: [
              "123",
              "456",
              "789",
            ]
          }
        },
      })

      app.component('test-label', { // 定义label
        props: ['data'],
        template: `<h1>{{data}}</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

监听子组件

不知道是不是我理解有误,就是在要渲染的那边比如<test-label>里面添加方法就行了

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label v-for="msg in message" :data="msg" @click="clickTest(msg)"></test-label> <!-- 要用刚才定义的label,不然定位不到内容 -->
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            message: [
              "123",
              "456",
              "789",
            ]
          }
        },
        methods: {
          clickTest(msg) {
            alert(msg)
          }
        },
      })

      app.component('test-label', { // 定义label
        props: ['data'],
        template: `<h1>{{data}}</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

动态组件

在不同组件之间进行动态切换是非常有用的,可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <p @click="click">click</p>
      <component :is="componentId"></component>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            number: "two"
          }
        },
        methods: {
          click() {
            this.number="one"
          }
        },
        computed: {
          componentId() {
            return "test-" + this.number
          }
        }
      })
      app.component('test-one', {
        template: `<h1>test-one</h1>`
      })
      app.component('test-two', {
        template: `<h1>test-two</h1>`
      })
      app.component('test-three', {
        template: `<h1>test-three</h1>`
      })
      const vm = app.mount("#test")

    </script>
</body>
</html>

内容受限解决

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。我们可以使用特殊的 is 属性作为一个变通的办法:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

当它用于原生 HTML 元素时,is 的值必须以 vue: 开头,才可以被解释为 Vue 组件。这是避免和原生自定义元素混淆。

大小写不敏感

另外,HTML attribute 名不区分大小写,因此浏览器将所有大写字符解释为小写。这意味着当你在 DOM 模板中使用时,驼峰 prop 名称和 event 处理器参数需要使用它们的 kebab-cased (横线字符分隔) 等效值:

//  在 JavaScript 中是驼峰式

app.component('blog-post', {
  props: ['postTitle'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
})
<!--HTML 中则是横线字符分割 -->

<blog-post post-title="hello!"></blog-post>

深入组件

组件注册

组件名

app.component('my-component-name', {
  /* ... */
})

这里的app.component就是组件名

组件使用

在组件使用的时候,如果我们的组件名是kebab-case (短横线分隔命名)my-component-name,那么我们在使用这个组件的时候也应该是一样的<my-component-name>

如果用的是PascalCase (首字母大写命名),那么在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的

注册范围

全局注册

app.component用法的情况下,组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的组件实例的模板中。比如:

const app = Vue.createApp({})

app.component('component-a', {
  /* ... */
})
app.component('component-b', {
  /* ... */
})
app.component('component-c', {
  /* ... */
})

app.mount('#app')
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

局部注册

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用其中一个组件了,它仍然会被包含在最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}

然后在 components 选项中定义你想要使用的组件:

const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

const ComponentA = {
  /* ... */
}

const ComponentB = {
  components: {
    'component-a': ComponentA
  }
  // ...
}

模块系统

通过 import/require引入模块

假设在 ComponentB.jsComponentB.vue 文件中:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  }
  // ...
}

现在 ComponentAComponentC 都可以在 ComponentB 的模板中使用了。

Props

类型

以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // 或任何其他构造函数
}

输入数据类型验证

就是prop的对象类型传入数据,假如一个地方要求输入String类型,就可以用这个来限制

如下限制传入的titleString类型

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
    <div id="test">
      <test-label :title="t"></test-label>
    </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            t: "test"
          }
        },
      })
      app.component("test-label", {
        props: {
          title: String
        },
        template: `<h1>{{ title }}</h1>`
      })
      app.mount("#test")
    </script>
</body>
</html>

如果类型不匹配,就会在控制台抛出异常,类型可以是下面中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

也可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。

例如,给定下列现成的构造函数:

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

你可以使用:

app.component('blog-post', {
  props: {
    author: Person
  }
})

来验证 author prop 的值是否是通过 new Person 创建的。

大小写

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。也就是说,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

如:

const app = Vue.createApp({})

app.component('blog-post', {
  // 在 JavaScript 中使用 camelCase
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中使用 kebab-case -->
<blog-post post-title="hello!"></blog-post>

自定义事件

命名

与组件和 prop 一样,事件名提供了自动的大小写转换。如果在子组件中触发一个以 camelCase (驼峰式命名) 命名的事件,你将可以在父组件中添加一个 kebab-case (短横线分隔命名) 的监听器。

this.$emit('myEvent')
<my-component @my-event="doSomething"></my-component>

自定义

可以通过 emits 选项在组件上定义发出的事件。

app.component('custom-form', {
  emits: ['inFocus', 'submit']
})

当在 emits 选项中定义了原生事件 (如 click) 时,将使用组件中的事件替代原生事件侦听器。

1、父组件可以使用 props 把数据传给子组件。 2、子组件可以使用 $emit,让父组件监听到自定义事件 。

vm.$emit(event, arg ) //触发当前实例上的事件

v-model数据同步

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="v-model-example" class="demo">
    <p>First name: {{ firstName }}</p>
    <p>Last name: {{ lastName }}</p>
    <user-name
     v-model:first-name="firstName"
     v-model:last-name="lastName"
   ></user-name>
 </div>

    <script type="text/javascript">
      const UserName = {
      props: {
        firstName: String,
        lastName: String
      },
      template: `
        <input 
          type="text"
          :value="firstName"
          @input="$emit('update:firstName', $event.target.value)">

        <input
          type="text"
          :value="lastName"
          @input="$emit('update:lastName', $event.target.value)">
      `
    };

    const HelloVueApp = {
      components: {
        UserName,
      },
      data() {
        return {
          firstName: 'John',
          lastName: 'Doe',
        };
      },
    };

Vue.createApp(HelloVueApp).mount('#v-model-example')
    </script>
</body>
</html>

插槽

作用:保留原来的内容

插槽内容

<slot> 元素作为承载分发内容的出口,简单来说就是会把原来的内容写进去

[!tip]

下面的例子中会把<slot></slot>替换为test content

如果没有<slot>元素,那么该组件起始标签和结束标签之间的任何内容都会被抛弃

[!tip]

如果没有<slot></slot>,那么原来的内容test content会被抛弃

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>test content</test-label> <!-- <h1> h1 test content</h1> -->
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({})

      app.component('test-label', {
        template: `
        <h1>
          h1
          <slot></slot>
        </h1>
        `
      })
      app.mount("#test")
    </script>
</body>
</html>

插槽中可以是任何内容,HTML代码或者其他组件等。

作用域

插槽可以访问与模板其余部分相同的实例 property (即相同的“作用域”)。

如下:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>test {{ test }}</test-label> <!-- <h1> h1 test 1123456</h1> -->
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            test: "1123456"
          }
        },
      })

      app.component('test-label', {
        template: `
        <h1>
          h1
          <slot></slot>
        </h1>
        `
      })
      app.mount("#test")
    </script>
</body>
</html>

[!note]

父级模板里的所有内容都是在父级作用域中编译的;

子模板里的所有内容都是在子作用域中编译的

备用内容

有时候没有默认的值,那么我们就可以设置一个备用的内容,放到<slot>标签内就行了

如:如果没有可替换的内容,就会自动替换成 Submit

<slot>Submit</slot>

具名插槽(命名的插槽)

如果存在多个插槽,直接替换vue也不知道替换哪一个,所以有一个特殊的name属性来区分

一个不带 name<slot> 出口会带有隐含的名字“default”。

[!warning] 没研究明白,好像也没有达到我理解的效果,等后面熟悉了再回头来看

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>      
      <h1></h1>
      <p></p>
    </test-label>
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({})

      app.component('test-label', {
        template: `
        <p>
          <slot name="p">1233</slot>
        </p>
        9999
        <h1>
          <slot name="h1">4566</slot>
        </h1>
        `
      })

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

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。

渲染的 HTML 将会是:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

注意,v-slot 只能添加在 <template>

Provide/Inject(依赖注入)

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

对于这种情况,我们可以使用一对 provideinject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

Provide/inject scheme

举个简单的例子

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>      
    </test-label>
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        provide: {
          name: "d4m1ts"
        }
      })

      app.component('test-label', {
        inject: [
          'name'
        ],
        template: `
        <h1>{{ name }}</h1>
        ` // <h1>d4m1ts</h1>
      })

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

如果 provide 一些组件的实例属性,这将是不起作用的,如下:

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>      
    </test-label>
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            items: [
              "123",
              "456"
            ]
          }
        },
        provide: {
          name: "d4m1ts",
          len: this.items.length // Uncaught TypeError: Cannot read properties of undefined (reading 'length')
        }
      })

      app.component('test-label', {
        inject: [
          'name'
        ],
        template: `
        <h1>{{ name }}</h1>
        ` // <h1>d4m1ts</h1>
      })

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

这时候,我们应该将 provide 转换为返回对象的函数,如下

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>      
    </test-label>
 </div>

    <script type="text/javascript">
      const app = Vue.createApp({
        data() {
          return {
            items: [
              "123",
              "456"
            ]
          }
        },
        provide() {
          return {
            name: "d4m1ts",
            len: this.items.length
          }
        }
      })

      app.component('test-label', {
        inject: [
          'name',
          'len'
        ],
        template: `
        <h1>{{ name }} {{ len }}</h1>
        ` // <h1>d4m1ts 2</h1>
      })

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

[!note]

实际上,你可以将依赖注入看作是“长距离的 prop”,除了:

  • 父组件不需要知道哪些子组件使用了它 provide 的 property
  • 子组件不需要知道 inject 的 property 来自哪里

渲染函数

大多数直接用的html标签,但是有些时候可以通过一些渲染函数来自动生成HTML代码,节约时间

render()函数

<html>
<head>
  <script type="text/javascript" src="./vue.global.js"></script>
</head>

<body>
  <div id="test">
    <test-label>      
    </test-label>
 </div>

    <script type="text/javascript">
      const { createApp, h } = Vue
      const app = Vue.createApp({
        render() {
          return h(
            'h1', // 标签
            {title: "test"}, // 属性
            "123123" // 内容
            ) // <h1 title="test">123123</h1>
        },
      })

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

h()函数

h() 函数是一个用于创建 VNode 的实用程序。也许可以更准确地将其命名为 createVNode(),但由于频繁使用和简洁,它被称为 h() 。它接受三个参数:

// @returns {VNode}
h(
  // {String | Object | Function} tag
  // 一个 HTML 标签名、一个组件、一个异步组件、或
  // 一个函数式组件。
  //
  // 必需的。
  'div',

  // {Object} props
  // 与 attribute、prop 和事件相对应的对象。
  // 这会在模板中用到。
  //
  // 可选的。
  {},

  // {String | Array | Object} children
  // 子 VNodes, 使用 `h()` 构建,
  // 或使用字符串获取 "文本 VNode" 或者
  // 有插槽的对象。
  //
  // 可选的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
      someProp: 'foobar'
    })
  ]
)

约束限制

VNodes 必须唯一

组件树中的所有 VNode 必须是唯一的。这意味着,下面的渲染函数是不合法的:

render() {
  const myParagraphVNode = h('p', 'hi')
  return h('div', [
    // 错误 - 重复的 Vnode!
    myParagraphVNode, myParagraphVNode
  ])
}

如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

render() {
  return h('div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

代替指令

使用render可以完全生成HTML了,那么一些指令比如v-forv-if也可以代替了,直接在render里面写js代码就行了

详见:使用 JavaScript 代替模板功能

Copyright © d4m1ts 2023 all right reserved,powered by Gitbook该文章修订时间: 2022-06-13 14:27:16

results matching ""

    No results matching ""