Vue模板专题

203 阅读3分钟

Vue模板

模板语法

  • Vue->template中的HTML字符串->Vue的特性:表达式、指令、属性等
  • Vue的模板都是基于HTML的

Vue编译过程

  1. template
  2. 分析 HTML字符串
  3. 转成AST树
  4. 变成表达式、指令、属性
  5. 虚拟DOM
  6. render 真实节点

为什么需要虚拟DOM

为什么需要虚拟DOM?
当存在一个<span>Vue2</span>
使用span.innerText = 'Vue2' 更改文本内容时
其实更新的是同一个值 这时候就没必要进行重新渲染 
转成虚拟DOM对象 就可以去对比新值和旧值 如一致则不进行操作,减少少做DOM次数

插值表达式

{{ }} -> Mustache
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>
</head>
<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script type="module" src="./app.js"></script>
</body>
</html>

js文件

/**
 * {{  }}
 * 插值表达式
 * Mustache
 * npm install mustache --save
 */
import { h } from '@vue/runtime-core';
import Mustache from 'mustache';

function MustacheTest() {

    const data = {
        title: 'This is my MUSTACHE'
    }

    const html = Mustache.render(
        `<div>
            <h1>{{ title }}</h1>
        </div>`,
        data
    )

    document.getElementById('app').innerHTML = html
}


function Test() {
    const App = {
        data() {
            return {
                title: 'This is my TITLE'
            }
        },
        template: `
            <div>
                <p>{{ title }}</p>
            </div>
        `
    }

    Vue.createApp(App).mount('#app')
}

/**
 * 也可以使用render + h 函数来渲染
 */

function Test2() {
    const App = {
        data() {
            return {
                title: 'This is my TITLE',
                author: 'aPeng',
                dateTime: Date.now(),
                content: 'This is my CONTENT'
            }
        },
        render() {
            return h(
                'div',
                {},
                [
                    h(
                        'h1',
                        {
                            class: 'title'
                        },
                        this.title
                    ),
                    h(
                        'span',
                        {
                            class: 'author'
                        },
                        this.author
                    ),
                    `- ${ this.dateTime }`,
                    h(
                        'p',
                        {
                            title: this.content
                        },
                        this.content
                    )
                ]
            )
        }
    }

    Vue.createApp(App).mount('#app')
}

export {
    Test,
    Test2,
    MustacheTest
}

Vue指令

directive指令 模板按照特定的逻辑进行渲染或者绑定的行为,template中属性以v-*都是指令
Vue内置的指令:v-if v-else-if v-else v-show v-if v-for v-once v-html

v-once

只会插值一次,后续改变值也不会重新渲染;v-once指令下的所有子元素也会受影响,不建议使用。

function DirectiveTest() {
    // 可以单独在实例外定义一个变量
    let title = 'This is my TITLE'
    const App = {
        data() {
            return {
                // title: 'This is my TITLE',
                author: 'aPeng'
            }
        },
        // 要想视图上的数据变量是响应式的 那么变量就必须声明在实例上
        // 这样就可以达到v-once只插值一次 后续永不更新的效果
        template: `
            <div>
                <p class="title" v-once>
                    ${title} -----
                    <span>{{ author }}</span>
                </p>
                <button @click="changeTitle">CHANGE TITLE</button>
            </div>
        `,
        methods: {
            changeTitle() {
                // this.title = 'This is your TITLE'
                title = 'This is your TITLE'
                this.author = '张三'
            }
        }
    }

    Vue.createApp(App).mount('#app')
}
export {
    DirectiveTest
}

v-html

当内容是普通的HTML文本时 直接使用{{}}插值表达式是不会进行渲染的,因为v-html中的内容不会经过Vue的模板编译 无法使用Vue的指令 属性 方法等...,所有不要把v-html里面的内容作为子模板。
不要随便使用v-html来渲染任意HTML内容,容易遭受XSS攻击;防止在html中利用某些标签进行脚本注入 img onerror。

function DirectiveTest2() {
    const show = false
    const App = {
        data() {
            return {
                content: '<h1 v-if="show">This is my CONTENT</h1><img src="sas" onerror="alert(1)" />'
            }
        },
        // 这样会原封不动的直接渲染这个字符串 不会渲染成HTML
        // template: `
        //     <div>
        //         {{ content }}
        //     </div>
        // `
        template: `
            <div v-html="content">
            </div>
        `
    }

    Vue.createApp(App).mount('#app')
}
export {
    DirectiveTest,
    DirectiveTest2
}

v-if v-else-if v-else v-show

v-if删除,只会渲染满足条件的元素;每次切换都会在切换的地方插入一个注释节点,
每次需要移除、插入和删除都通过这个注释节点来操作。
v-show隐藏节点,元素会全部渲染出来,通过控制元素的display:none来控制元素的隐藏和显示

const App = {
        data() {
            return {
                linkIndex: 0,
                urls: [
                    'https://www.baidu.com',
                    'https://www.tmall.com',
                    'https://www.jd.com'
                ]
            }
        },
        template: `
            <!-- 为了方便演示v-if v-show 才这个例子 其实不推荐这么写 有更好的方式 比如对象属性obj[key]-->
            <div>
                <div>
                <!--
                    <p v-if="linkIndex === 0">
                        <a :href="urls[linkIndex]" target="_blank">淘宝商城</a>
                    </p>
                    <p v-else-if="linkIndex === 1">
                        <a :href="urls[linkIndex]" target="_blank">天猫商城</a>
                    </p>
                    <p v-else>
                        <a :href="urls[linkIndex]" target="_blank">京东商城</a>
                    </p>
                -->
                <p v-show="linkIndex === 0">
                    <a :href="urls[linkIndex]" target="_blank">淘宝商城</a>
                </p>
                <p v-show="linkIndex === 1">
                    <a :href="urls[linkIndex]" target="_blank">天猫商城</a>
                </p>
                <p v-show="linkIndex === 2">
                    <a :href="urls[linkIndex]" target="_blank">京东商城</a>
                </p>
                </div>
                <div>
                    <button @click="changeIndex(0)">淘宝</button>
                    <button @click="changeIndex(1)">天猫</button>
                    <button @click="changeIndex(2)">京东</button>
                </div>
            </div>
        `,
        methods: {
            changeIndex(index) {
                this.linkIndex = index
            }  
        }
    }

    Vue.createApp(App).mount('#app')

v-bind v-on

v-bind绑定属性,插入表达式,v-bind:href="url",简写:href;支持动态绑定属性,通过在[]里面填写动态参数的变量;属性无法绑定null,可以通过将动态属性设置为null,以达到移除属性效果。
v-on事件绑定,绑定事件处理函数,v-on:click="handleClick", 简写@click;支持动态绑定事件,通过在[]里面填写动态事件的变量

const App = {
        data() {
            return {
                attrName: 'data-name',
                eventName: 'click'
            }
        },
        template: `
            <div>
                <p :data-name="'p'">v-bind</p>
                <p :[attrName]="'attrName'">attrName</p>
                <!-- 属性绑定 是无法做拼接的 因为在HTML元素属性里面无法出现'' "" 空格等符号 -->
                <!-- <p :[attrName + 's']="'attrName'">attrName</p> -->
                <p>绑定的事件:{{ eventName }}</p>
                <button @click="changeAttr('title')">CHANGE ATTR</button>
                <button @click="deleteAttr(null)">DELETE ATTR</button>
                <button @[eventName]="changeEvent('change')">CHANGE EVENT</button>
            </div>
        `,
        methods: {
            changeAttr(attrName) {
                this.attrName = attrName
            },
            deleteAttr(attrName) {
                this.attrName = attrName
            },
            changeEvent(fn) {
                this.eventName = fn
            }
        }
    }

    Vue.createApp(App).mount('#app')

最后,简单写了一个思维导读以便于回顾和记忆:

Vue内置指令.png