Skip to content

点线面 Vue3:把模板语法这条线串起来!

我把一个初学者学习新技术分成3个大阶段8个小阶段,分别是:

阶段一:入门和熟悉

  1. 先用起来:从一个工作多年的Vue初学者角度学习Vue3:初识Vue组件
  2. 熟悉API:当前阶段

阶段二:使用和输出

  1. 做一个实际的项目
  2. 编写扩展插件
  3. 输出总结文章

阶段三:原理和再造

  1. 了解总体架构和设计
  2. 分析源码
  3. 做一个迷你版本

上一篇文章我们创建了一个Vue3的工程,并成功把它跑起来了,【先用起来】这个步骤算是完成。

【先用起来】这个步骤的主要目标是对新技术有一个感性的印象,这个步骤完成之后,我们大致能知道:

  1. 这个新技术是干嘛的
  2. 它的基本构造(基本概念)有哪些
  3. 怎么用(跑)起来

通过上一篇文章:

从一个工作多年的Vue初学者角度学习Vue3:初识Vue组件

我们基本上完成了上述目标:

  1. 了解到Vue3是一个渐进式的JavaScript框架,用来构建UI层
  2. 它的基本构造是Vue组件,Vue组件由template/script/style三部分组成
  3. 通过Vite工程可以将Vue3项目跑起来,并体验了一把Vite非常丝滑的热更新(HMR)

第一个阶段我们只需要获取感性印象即可,把它跑起来、去体验,然后改改这个,改改那个,看下效果是什么。把这个打开,看下里面有什么,然后盖回去。对那个感兴趣,就去官网扫一眼API,或者亲自尝试下它是干嘛的。

而第二个阶段我们需要对这个新技术形成更广泛的认识,要了解它的全貌,要记住高频使用的API。根据二八定律,一般20%的API就能覆盖到80%的项目场景,因此最重要的任务就是熟悉API,并熟记那20%的高频API。

这时我们就需要看Vue3的官方文档了: https://vue3js.cn/docs/zh/guide/introduction.html

1 带着思考去看文档

文档写作者在写文档的时候,一般都会把文档的读者当成“傻瓜”来写,这样写出来的文档才能更清晰、更完整,读者才能更容易读懂。

但实际上读者并不是没有任何基础的“傻瓜”,不同的读者有不同的经历和背景,对文档的接受和理解程度也不一样。

因此我们看文档的时候,不一定要完全按照顺序去看,而是要结合自己的实际情况,有针对性、有目的地去看。

文档只是一个参考,我们学习新技术的方式有很多,看文档只是一方面,因此看文档的时候一定要带着思考和问题去看,而不是盲目地从头看到尾。

上一个阶段我们了解到 Vue 组件分成三个部分:

  • template
  • script
  • style

不妨从这个思路去看文档。

先看基础部分的文档:

大部分都是在讲template部分(红框),也讲了一些script的内容(蓝框)。

在第一阶段我们已经对template有一定的直观印象:

  • template里面可以使用Vue的模板语法,进行数据的动态渲染
  • 文本插值(双大括号包裹)是一种非常基础的模板语法
  • @click是Vue事件绑定的语法

我们了解到了三个关键字:模板语法文本插值事件绑定

所以看文档的第一步应该是去深入了解下这几个概念到底是什么。然后通过这几个单点的概念,串起来整个模板语法的线。

其中的模板语法文本插值的文档在:https://vue3js.cn/docs/zh/guide/template-syntax.html

事件绑定的文档在:https://vue3js.cn/docs/zh/guide/events.html

文章较长,如果想直接看小结,可以跳转到以下章节: 6 小结

2 模板语法概览

模板语法的文档其实就讲了两个东西:插值指令

插值我们已经有一定的了解,并且知道了:

文本插值就是把一个Vue组件的变量和模板绑在一起,变量的值最终会渲染到模板里面。

html
<h1>{{ msg }}</h1>

<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />

最终渲染出来就是:

html
<h1>Hello Vue 3 + TypeScript + Vite</h1>

通过阅读文档,我们的目的是由点到线、由线到面地将零散的认识串起来。

文本插值只是一个点,线是什么?

所有的插值类型是线,整个模板语法是线,模板编译的原理也是线。

让我们一条一条线串起来吧。

3 插值

从文档我们可以了解到,除了文本插值,还有

  • HTML插值
  • 属性插值
  • JavaScript表达式支持

3.1 HTML插值

关于HTML插值,文档给了一个非常好的例子,非常清晰:

html
<p>Using mustaches: {{ rawHtml }}</p>

<p>Using v-html directive: <span v-html="rawHtml"></span></p>
js
rawHtml: '<span style="color: red">This should be red.</span>'

同样一个HTML字符串文本,通过文本插值,会直接把这个HTML字符串文本显示出来;而HTML插值则会将HTML字符串渲染出来(这里是一个红色的文本)。

这里借助了一个Vue指令:v-html

这是我们接触到的第2个Vue指令,前面文本插值时也出现了一个v-once指令,用于一次性插值。

我们可以先不深究Vue指令到底是什么,后面会专门学习,只需要知道

  • 它是以v-开头的
  • 用在html元素或Vue组件元素上的一个“命令”
  • 使用了这个“命令”的元素,它的表现或行为会发生一定的变化

知道这些就够了。

3.2 属性插值

除了可以将变量插到元素里面,还可以插到元素的属性上,比如id/disabled/title/src等属性。

属性插值需要借助另一个Vue指令:v-bind

html
<div v-bind:id="dynamicId"></div>
js
dynamicId: 'name1'

最终渲染出来是:

html
<div id="name1"></div>

这是我们学习的第3个指令,后面还接触到更多Vue内置指令。

这些指令在实际工作中使用的频率非常高,都需要记住,属于那20%的API。

这个v-bind:id和之前的v-oncev-html指令都不一样,这个v-bind指令是带参数的,这里的参数是id,代表需要绑定id这个属性。

由于v-bind指令太常用了,因此Vue给它提供了一个缩写形式,比如v-bind:id可以直接写成:id

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

<=>

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

3.3 使用JavaScript表达式

之前的插值,都是只绑定一个变量,其实还可以绑定一个JavaScript表达式,比如:

html
<h1>{{ hasMsg ? msg : 'Hello World' }}</h1>

以上文本插值绑定了一个三目运算符,里面有两个变量和一个字符串:

  • 当hasMsg为true时,实际绑定的是msg的值
  • 当hasMsg为false时,绑定'Hello World'字符串

属性插值也可以绑定表达式:

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

以上属性插值绑定了一个字符串拼接的表达式,实际绑定字符串'list-'和变量id的值拼接之后的值。

这样我们就把插值的线连起来了。

让我们回顾一下:

  • 插值就是将组件实例的变量绑定到dom中
  • 共有文本插值HTML插值属性插值3种插值类型
  • 插值除了支持变量,还支持JavaScript表达式
  • 了解了Vue指令就是一个用在元素上的“命令”,会影响元素的表现和行为
  • 学习了三个常用的Vue指令:v-once/v-html/v-bind(v-bind是带参数指令)

4 指令

模板语法这条线上除了插值这条线,还有一条指令的线。

指令我们前面已经有了初步的了解,并且学习了3个简单的指令,也了解到指令其实是可以带参数的。

这些都是孤立的点,通过阅读文档,我们就可以将已知的孤立点补充完整,串成一条线。

指令(Directives)是带有v-前缀的特殊属性,它的职责是:

当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。

文档中介绍了一个v-if指令的例子:

html
<p v-if="seen">现在你看到我了</p>

如果seen变量为true,那么最终渲染成:

<p>现在你看到我了</p>

如果seen为false,则不会渲染p元素:

<!--v-if-->

v-if指令是我们学习到的第4个Vue内置指令,也非常常用。

通过文档我们了解到除了一般的指令参数指令外,还有:

  • 动态参数指令
  • 带修饰符的指令

4.1 动态参数指令

参数指令我们前面介绍属性插值时提到过:

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

v-bind就是一个典型的参数指令,可以用于响应式地更新HTML属性,比如上面的例子用于更新id属性。

还有一个参数指令是v-on,用于事件绑定,比如想要给按钮绑定一个点击事件:

html
<button type="button" v-on:click="confirm">确定</button>

v-on后面的click的就是这个指令的参数,代表绑定的是click点击事件。

v-bind:id其实等于是绑死了id这个元素,其中的参数部分还可以写一个JavaScript表达式,让其变成动态参数,比如:

html
<div v-bind:[attributeName]="dynamicValue">测试动态参数指令</div>

attributeNameid的时候,绑定的就是属性id,当attributeName变成title时,绑定的属性就变成title了,所以叫:动态参数指令。

v-on指令一样也可以带动态参数,比如:

html
<a v-on:[eventName]="doSomething">测试v-on指令的动态参数</a>

当eventName是click时,绑定的是点击事件;当eventName是focus时,绑定的是聚焦事件。

4.2 带修饰符的指令

修饰符 (modifier) 是以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

v-on指令除了可以带参数之外,还可以带修饰符(比如带.prevent修饰符)。

html
<a v-on:click.prevent="confirm" href="https://devui.design/">带prevent修饰符的超链接</a>

带了.prevent修饰符会在触发事件之后调用event.preventDefault(),以阻止超链接默认的跳转动作,所以上面的超链接点击之后只会执行confirm方法,不会跳转。

如果不带.prevent修饰符,则执行完confirm事件之后,还会执行超链接的默认跳转行为,并跳转到https://devui.design/链接:

html
<a v-on:click="confirm" href="https://devui.design/">普通超链接</a>

需要注意的是:

Vue指令的参数只能有一个,而修饰符可以有多个

目前Vue的大部分修饰符都存在于v-on指令。

v-on指令是我们学到的第5个Vue内置指令,用于绑定事件,该指令非常常见,因此和v-bind指令一样,Vue也给v-on指令提供了特定的简写形式,比如v-on:click可以简写成@click

html
<button type="button" v-on:click="confirm">确定</button>

<=>

<button type="button" @click="confirm">确定</button>

回顾下目前为止我们学习到的Vue内置指令:

  1. v-once:一次性插值,并缓存该值,不可改变
  2. v-html:HTML插值,用于渲染HTML内容,容易导致XSS攻击,谨慎使用
  3. v-bind:非常常用,属性绑定,用于响应式的更新HTML属性,可带参数/动态参数,v-bind:attributeName可简写成:attributeName
  4. v-if:非常常用,用于动态插入/移除元素和组件,会将元素从DOM树中实际移除
  5. v-on:常用常用,用于事件绑定,可带参数/动态参数,可带修饰符,v-on:eventName可简写成@eventName

这样我们把指令这条线也基本串起来了。

其实到这里,模板语法线基本上就已经串起来了,不过还漏了点内容:

  1. 指令是Vue里面非常关键的概念,内容也非常多,Vue3文档的基础部分其实大部分都是在讲Vue的一些内置指令,因此指令线非常长,我们目前串起来的指令线是不完整的,后续还会继续延伸指令线
  2. 在众多内置指令里面,v-on事件绑定算是一个非常基础又非常重要的存在,而且前面也多次提到过,因此也放在本篇文章
  3. 模板语法里面还有一个概念没有在基础里面出现,而是在深入组件的文档里,就是模板引用,这部分内容非常简单,没什么内容,不过我觉得它也算模板语法线的一部分,因此也一并放进来

非常感谢你能阅读到这里,还有最后5分钟就阅读完了,先喝杯水放松下吧😋

4.3 事件绑定

事件绑定其实我们在第一篇文章就已经见过:

html
<button type="button" @click="count++">count is: {{ count }}</button>

不过当时是以单点的方式有一个感官的认识。

前面我们在介绍动态参数指令带修饰符的指令中又多次接触到v-on事件绑定,现在我们知道它属于指令这条线的一部分,而指令线又属于模板语法这条长线的一部分,这样知识点就串起来。

如果我们学习的时候每次只记得一些点,没有串成线,就很难进行知识的联想,而且零散的知识点也容易忘记。

一旦将点串成线,线织成面,我们就能从全局的角度、连接的角度看待这门新技术,便于记忆,并能够举一反三地灵活运用这门新技术。

在Vue中事件绑定都是通过v-on指令来进行的,而v-on:eventName指令可以简写成@eventName

前面我们已经知道如何绑定事件:

html
<button type="button" @click="count++">count is: {{ count }}</button>

每次点击按钮,count变量都会自增1。

以上例子中的count++是一个表达式,通过阅读官方文档,我们了解到,除了写表达式,还可以写:

  • 事件处理方法
  • 内联处理器中的方法
  • 多事件处理器
  • 事件修饰符
  • 按键修饰符
  • 系统修饰键

4.3.1 事件处理器

事件处理器就是给事件绑定一个方法名。

html
<button type="button" @click="add">count is: {{ count }}</button>
js
setup() {
  const count = ref(0)

  const add = () => {
    count.value++
  }

  return {
    count,
    add,
  }
}

除了绑定方法名,还可以直接执行方法(内联处理器):

html
<button type="button" @click="add()">count is: {{ count }}</button>

除了执行一个方法,还可以通过逗号分隔,一次执行多个方法:

html
<button type="button" @click="add(),double()">count is: {{ count }}</button>
js
setup() {
  const count = ref(0)

  const add = () => {
    count.value++
  }

  const double = () => {
    count.value = count.value * count.value
  }

  return {
    count,
    add,
    double,
  }
}

4.3.2 事件修饰符

事件修饰符我们在讲带修饰符的指令时也初步了解过,修饰符是由点开头的指令后缀来表示的。

以下是一些常见的事件修饰符:

  • .stop 阻止事件传播
  • .prevent 阻止默认事件行为
  • .capture 使用事件捕获模式
  • .self 点中自己时才触发(点中内部元素不触发)
  • .once 事件只触发一次
  • .passive 滚动事件的默认行为 (即滚动行为) 将会立即触发,而不会等待 onScroll 完成

前面4个多用于click点击事件,我做了几个demo帮助大家理解它们的含义。

html
<div @click="containerClick" style="border: solid 1px red">
  Container
  <div @click="outClick" style="border: solid 1px green; margin: 20px; padding: 20px;">
    Button wrapper
    <button type="button" @click="add">Add</button>
  </div>
</div>
js
setup() {
  const containerClick = () => {
    console.log('containerClick')
  }

  const outClick = () => {
    console.log('outClick')
  }

  const add = () => {
    console.log('add')
  }

  return {
    containerClick,
    outClick,
    add,
  }
}

从外到内一共有三个元素、三个点击点:

  • 最外层的容器元素(Container 红色圆圈)
  • 按钮的外部元素(Button wrapper 绿色圆圈)
  • 按钮元素(Button 蓝色圆圈)

默认情况下,事件遵循冒泡的传播方式(从内到外)。

我们点击按钮元素,会依次打印:

add
outClick
containerClick

点击按钮外层元素,会依次打印:

outClick
containerClick

.prevent修饰符前面讲带修饰符的指令时已经举过🌰,不再重复,直接从.stop修饰符开始。

stop修饰符

我们给按钮元素加.stop修饰符,将会阻止事件往上传播。

html
<button type="button" @click.stop="add">Add</button>

此时只会打印:

add
self修饰符

如果给按钮外层的元素加.self修饰符,将只有真正点击了该元素才会触发,点击里面的按钮不会触发。

html
<div @click.self="outClick" style="border: solid 1px green; margin: 20px; padding: 20px;">
  Button wrapper
  <button type="button" @click="add">Add</button>
</div>

此时如果点击按钮,只会打印:

add

如果点击按钮外层元素,则会打印:

outClick
containerClick
capture修饰符

给某个元素的点击事件增加捕获修饰符,意味着该元素的事件传播遵循从外到内的捕获模式,只影响该元素的事件传播模式。

给按钮外部元素增加.capture修饰符:

html
<div @click.capture="outClick" style="border: solid 1px green; margin: 20px; padding: 20px;">
  Button wrapper
  <button type="button" @click="add">Add</button>
</div>

此时点击按钮元素,将打印:

outClick
add
containerClick

事件修饰符中还有两种非常有用:

  • 按键修饰符 enter / tab / delete / space 等
  • 系统修饰键 ctrl / alt / shift / meta 等

涉及的修饰符比较多,也比较好理解,大家感兴趣可以自行阅读官网文档即可。

按键修饰符

系统修饰键

加上事件处理这条线,指令这条长线就变得更加完整了。

5 模板引用

对于模板语法这条线来说,还差一个模板引用。

先看下官网文档怎么解释模板引用的:

尽管存在 prop 和事件,但有时你可能仍然需要直接访问 JavaScript 中的子组件。为此,可以使用 ref attribute 为子组件或 HTML 元素指定引用 ID。

所以其实模板引用就是为了获取到template模板中的具体元素,从而操纵它。而template模板里面就只有两类元素:

  • 原生HTML元素
  • 组件元素

对应两种模板引用。

5.1 HTML元素引用

HTML元素引用获取到的是DOM元素。

我们给img标签增加一个ref属性,绑定img标签的引用imgRef。

html
<img alt="Logo" src="./assets/logo.png" ref="imgRef" />
<br />
<button type="button" @click="changeImg">Change img src</button>
js
setup() {
  const imgRef = ref(null)

  const changeImg = () => {
    imgRef.value.src = 'src/assets/devui.png'
    imgRef.value.width = 200
  }

  return {
    imgRef,
    changeImg,
  }
}

5.2 子组件引用

子组件引用获取到的是子组件实例。

还是举一个🌰

html
<HelloWorld ref="helloWorldRef" msg="Hello everyone! I'm learning Vue 3 + TypeScript + Vite" />
<button type="button" @click="add">Add</button>
js
setup: () => {
  const helloWorldRef = ref(null)

  const add = () => {
    // 拿到子组件的引用之后,就可以获取组件实例的变量和方法啦~
    helloWorldRef.value.add()
    
    // 也可以直接修改子组件实例的count变量
    // helloWorldRef.value.count++
  }

  return { 
    helloWorldRef,
    add,
  }
}

hello-world.vue

html
<p>{{ count }}</p>
js
setup: () => {
  const count = ref(0)

  // 该方法为组件的实例方法,可以通过模板引用的方式调用
  const add = () => {
    count.value++
  }

  return { 
    count,
    add,
  }
}

至此,整个模板语法线就完整得串起来了。

6 小结

最后用这条模板语法的线,作为本文的小结吧。

参考:

Vue3 中文文档 - 模板语法

发布时间:

Made with ❤ by