为什么要学习Vue的模板语法

1. 了解了Vue模板语法的作用

其实Vue的模板语法里面有很多知识, 特别是在理解了模板语法的特性后对学习Vue的背后的源码有很大的作用。本文结合了几个简单的例子能更清楚的了解到以下的一些知识:

  1. 清楚的了解Vue模板语法哪些合法哪些不合法以及背后的逻辑;
  2. 理解Vue的指令背后简单的逻辑;
  3. Vue插值表达式为什么不能像React使用全局变量, 这里有一个核心概念受限列表;
  4. 如何简单进行动态绑定属性名、方法名以及快速解绑;
  5. Vue为什么对于null和undefined会进行选择性忽略, 以及我们如何利用这一特性来开发代码;

2. 了解Vue模板语法的背景知识

什么是模板语法?

在html字符串里面除了本身的特性还会有Vue的特性, 例如 文本、表达式、属性和指令。

Vue的模板语法和React的jsx有什么不同?

最大的不同点在于Vue的模板语法是基于html的, 而不是自己造的模板语法。它的背后有一套强大的html解析系统(后文有说明), 在解析模板后将非原生的html (例如: v-if @click等)的东西变为原生html上的东西, 或者直接使用JS去表达。并且模板中直接写的html语言都是能被html解析器解析的。

Vue模板编译系统过程

  1. 开发者写好template

  2. 分析html字符串

  3. 转化为AST树

  4. 利用AST树转化模板上的表达式、自定义属性、指令 (例如 v-for 回进行循环拼接一些html 将指令最终转化成html)

  5. 虚拟dom树

  6. 最后渲染成真实dom

为什么需要虚拟dom ?

简单的一个例子

<p>hello</p>
// 通过js对标签进行操作 p.innerText = 'hello'
复制代码

此时将p标签的内部文本修改和之前是一样的,但是浏览器还是会进行渲染,会造成无意义的性能浪费。 如果我们在渲染前进行比较一下如果不同的话再去更新, 这样就会节约性能, 但是拿真实dom对比很麻烦, 所以将真实dom变为数据形式进行对比, 如果有区别然后进行更新。

3. 模板语法 - 插值表达式

因为表达式的方式{{}}像一个胡子, 所以也成为胡子表达式Mustache

起源于mustache.js一个库

这里感兴趣可以去github上看github.com/janl/mustac…, 它是一个0依赖的库, vue虽然没有使用这个库但是有很多地方都有借鉴。

插值表达式使用规范

  1. 在插值表达式中只能是JS表达式, 不能是语句(如: if else)、功能模块、函数、变量声明以及变量声明并赋值(如:let a = 10)。

  2. 插值表达式中可以使用JS api

data() {
    retrun {
        x: 1,
        y: 2    
    }
}
// 合法的表达式
{{ x + y }}   
{{ x > 2 ? x : y }}
{{ x + y + 'str' }}
{{ x || y }}
{{ x && y }}
{{ String(x) + String(y) }}
{{ x = 10 }} // 赋值表达式可以,因为能计算出一个结果10

// 不合法的表达式
{{ if(x){ return x } }}
{{ var a = x }} // 声明式表达式不行
复制代码

插值表达式受限列表概念

Vue的插值表达式与React有一个很大的区别在于Vue的插值内容不能使用全局变量, 如果这样做会报错 :Property "str" was accessed during render but is not defined on instance.

let str = 'is a string';
const App = {
    template: `
       <div>
            <h1>{{ str }}</h1> 
       </div>`,
}
createApp(App).mount('#app')
复制代码

因为Vue强大的编译系统背后有很多规范进行支撑的, 可以检查你写的东西到底是否合法, 例如插值变量能使用的全局的类型就在核心库的globalsWhitelist.ts中, 下面这些可以在插值中直接使用, 我们可以这些内容自己进行扩展, 这些技巧后期会进行说明。

image.png

使用h函数来代替模板语法

使用插值表达式的写法

let { createApp } = Vue;
const Todo = {
    data() {
        return {
            name: 'xiaoming',
            age: 18
        }
    },
    template: `
        <div>
            <h1>{{ name }}</h1>
            <p>{{ age }}</p>
        </div>
    `
}
createApp(Todo).mount('#app')
复制代码

使用h函数也同样可以来渲染dom,先混一个脸熟后面会详细说明这个

let { createApp, h } = Vue;
const Todo = {
    data() {
        return {
            name: 'xiaoming',
            age: 18
        }
    },
   render() {
        return h(
            'div', // 标签名
            {}, // 标签属性 
            [
                h('h1', {}, this.name),
                h('p', {}, this.age)
            ] // 子标签
        )
   }
}
createApp(Todo).mount('#app')
复制代码

4. 模板语法 - 指令

这里特别要注意深度学习指令, 先简单的看一下各个指令的使用有什么技巧,这样后期才能根据指令的特点来进行深度分析和重写

指令的含义和作用:

所有在vue中模板上属性v-* 都是指令,通过指令我们可以规定模板应该按照怎样的逻辑进行渲染或绑定行为。

Vue提供了大量的内置指令v-if v-else v-html v-once等,开发者也可以给Vue扩展指令, 自定义指令必须v-开头

了解v-once

第一次插值后面数据变了也不更新,** 会影响整个标签内部所有的东西都不会更新**.

const App = {
    data() {
        return {
            name: 'xiaoming',
            age: 18
        }
    },
    template: `<div @click="change" v-once><span>{{ name }}</span></div>`,
    methods: {
        change() {
            this.name = 'xiaohong'
        }
    }
}
createApp(App).mount('#app')
复制代码

了解v-html

作用: 解决插值表达式中无法解析html的一个方案

const App = {
    data() {
        return {
            title: 'h1-title'
        }
    },
    template: `
        <div>{{ '<h1>' + title + '</h1>' }}<div/>
    `
}
复制代码

image.png

页面上渲染还是一个字符串不能解析为标签。

重点理解:为什么会有这样的规定?以及两个对v-html使用不得当的案例

插值表达式不会解析html为了安全, 因为插值是JS表达式没有对dom的操作。

不好的栗子1: 使用v-html做子模板

let show = false;
const App = {
    data() {
        return {
            sub: `<h1 v-if="${show}">title</h1>`
        }
    },
    template: `<div v-html="sub"></div>`
}

createApp(App).mount('#app');
复制代码

image.png

我们发现实际上dom节点上已经有了, v-if 这个指令但是页面还是渲染出来了,因为vue本身有一个底层的模板编译系统,而不是直接使用字符串来渲染。所以使用v-html做子模板不可取会导致很多问题。

不好的栗子2: 安全性问题, 如果内容是用户提供的并且和v-html连用很容易产生xss攻击 现在html做了很多优化防止这种行为, 例如下面这样的代码是不会产生作用的

const App = {
    data() {
        return {
            content: `<script>alert('Surprise!')</script>`
        }
    },
    template: `<div v-html="content"></div>`
}

createApp(App).mount('#app')
复制代码

虽然dom节点渲染了,但是里面的代码没有执行

image.png

但是如果换一个思路将content内容改为: <img src="xxx" onerror="alert('Surprise!')"/>通过一个图片报错的形式进行触发脚本执行

image.png

这些脚本内容可以是各式各样的例如读取cookie session等一些隐私信息, 所以使用v-html需要特别注意。

了解v-bind与v-on

  1. 先了解attribute与property两个属性的区别:

attribute为dom属性是html语言的扩展,例如title、scr、herf通常表示有什么作用。property通常是对象内部存储的数据, 来描述数据结构

  1. 使用[ ]运算符动态绑定属性名和方法
const App = {
    data() {
        return {
            attrType: 'data-id',
            eventType: 'click'
        }
    },
    template: `
       <div>
            <h1 :[attrType]="123">
                <button @[eventType]="handleClick()">点击</button>
            </h1> 
       </div>`,
    methods: {
        handleClick() {
            console.log(123);
        }
    }
}
复制代码

虽然可以使用[]求值,但是不能 <h1 :[ 'data' + attrType ] > 这种形式进行拼接属性, 因为属性名中不允许有引号和空格,这样会报语法错误

Vue为什么会忽略null与undefined ? 以及我们如何利用这个特性

当绑定的属性值为null的时候例如:

渲染成真实dom后并不会有这个属性。这里就体现除了Vue对于这两个值的态度, 因为null与undefined往往是取值发生错误时候,或者设置方法有问题产生的数据结构错误, 可能开发者自己不知道,如果真的渲染上去可能会导致其他的一些问题, 所以会忽略这些属性。 我们可以利用这一特点实现简单的属性切换:

// 快速给h1标签添加和删除id属性
const App = {
    data() {
        return {
            attrType: 'data-id',
        }
    },
    template: `
       <div>
            <h1 :[attrType]="123">
                <button @click="handleClick()">添加或删除属性</button>
            </h1> 
       </div>`,
    methods: {
        handleClick() {
            this.attrType === null ? (this.attrType = 'id') : (this.attrType = null);
        }
    }
}
复制代码

v-if 与 v-show

这里简单说明一下两者实现的区别(之后有简单的源码实现案例): v-if 的语法会在dom上增加一个注释节点进行占位, 通过逻辑判断后把要显示的内容替换到注释节点上。

image.png

v-show是通过样式display: none进行节点的隐藏和显示。 这就是我们常说的如果需要经常切换显示的内容使用v-show会更好, 如果值渲染一次或者大概率不渲染的情况下使用v-if会节约性能。

5. 总结

以上就是一些基础的模板语法特性的说明, 虽然只是大概简单的说了一些但是里面还是有很多知识点在里面, 之后会慢慢选几个指令配合案例来简单的实现, 希望能帮助大家了解一下Vue指令背后的基本逻辑。

猜你喜欢

转载自juejin.im/post/7085350456783798303