Vue渲染器(二):挂载与更新

渲染器(二):挂载与更新

前面介绍了渲染器的基本概念和整体框架,接下来就可以介绍渲染器的核心功能:挂载与更新。

1.挂载子节点和元素的属性:

vnode.children的值为字符串类型时,会把它设置为元素的文本内容。一个元素除了有文本子节点外,还可以包含其他元素子节点,并且子节点可以是多个。为了描述元素的子节点,我们将vnode.children定义为数组。

下面代码描述的是 “一个div具有一个子节点,子节点是p标签”。

const vnode = {
    type: 'h1',
    children: [
        {
            type: 'p',
            children: 'hello'
        }
    ]
}

可以看到,vnode.children是一个数组,它的每一个元素都是一个独立的vnode对象,这样就形成了树形结构,即虚拟DOM树。

修改mountElement完成子节点的渲染:

    function mountElement(vnode, container) {
        const el = createElement(vnode.type);
        if (typeof vnode.children === 'string') {
            setElementText(el, vnode.children);
        } else if (Array.isArray(vnode.children)) {
            vnode.children.forEach(child => {
                patch(null, child, el);
            })
        }
        insert(el, container);
    }

增加新的判断分支,判断vnode.children是否是数组,如果是数组则循环遍历它,并调用patch函数挂载数组中的虚拟节点。在挂载子节点时,注意两点:

  • 传递给patch函数的第一个参数是null,因为是挂载阶段,没有旧vnode。
  • 传递给patch函数的第三个参数是挂载点。由于我们正在挂载的子元素是div标签的子节点,所以需要把刚刚创建的div元素作为挂载点,这样才能保证这些子节点挂载到正确位置。

完成子节点的挂载后,来看看如何用vnode描述一个标签的属性,以及如何渲染这些属性。

HTML标签中有很多属性,有些属性是通用的,如id、class等,而有些属性是特定元素才有,有form元素的action属性。我们先来看看最基本的属性处理,定义vnode.props字段描述元素的属性。

const vnode = {
    type: 'h1',
    // 使用 props描述一个元素的属性
    props: {
        id: 'foo'
    },
    children: [
        {
            type: 'p',
            children: 'hello'
        }
    ]
}

vnode.props是一个对象,它的键代表元素的属性名称,值代表对应属性的值。这样就可以通过遍历props对象的方式,把这些属性渲染到对应的元素上,如下代码:

    function mountElement(vnode, container) {
        const el = createElement(vnode.type);
        if (typeof vnode.children === 'string') {
            setElementText(el, vnode.children);
        } else if (Array.isArray(vnode.children)) {
            vnode.children.forEach(child => {
                patch(null, child, el);
            })
        }
        if (vnode.props) {
            for (const key in vnode.props) {
                el.setAttribute(key, vnode.props[key]);
                // 直接设置
                el[key] = vnode.props[key];
            }
        }
        insert(el, container);
    }

检查vnode.props字段是否存在,存在则遍历它,并调用setAttribute函数将属性设置到元素上,或者通过DOM对象直接设置。

这两种方式都存在缺陷,其实为元素设置属性比想象中要复杂。不过在讨论具体缺陷以前,有必要先搞清楚两个重要的概念: HTML AttributesDOM Properties

2.HTML Attributes 和 DOM Properties:

理解HTML AttributesDOM Properties之间的差异和关联非常重要,这能帮我们合理地设计虚拟节点的结构,更是正确为元素设置属性的关键。

<input id="my-input" type="text" value="foo" />

HTML Attributes指的就是定义在 HTML标签上的属性,这里指的是 id="my-input" type="text" value="foo"。当浏览器解析这段HTML代码,会创建一个与之对应的DOM元素对象,我们可以通过JS来获取该DOM对象:

const el = document.querySelector('#my-input');

这个DOM对象包含很多属性,如下图所示:
在这里插入图片描述

DOM对象下的属性就是所谓的 DOM Properties。很多 HTML Attributes在DOM对象上有与之同名的 DOM Properties,例如 id=“my-input” 对应 el.id ,type="text"对应 el.type ,value=“foo” 对应 el.value等。

但是它们之间的名字不全是一模一样的,例如:

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

class="foo" 对应的 DOM Properties则是 el.className。另外不是所有的HTML Attributes都有与之对应的 DOM Properties。例如:

<div aira-valuenow="75"></div>

aira-*类的 HTML Attributes就没有与之对应的 DOM Properties

类似地,不是所有 DOM Properties都有与之对应的 HTML Attributes,例如可以用 el.textContent来设置元素的文本内容,但没有与之对应的 HTML Attributes来完成同样的工作。

HTML Attributes的值与 DOM Properties的值之间是有关联的,例如:

<div id="foo"></div>

上面代码描述了一个具有 id属性的div标签。其中 id="foo"对应 DOM Properties是 el.id,并且值为字符串 "foo"

这种HTML AttributesDOM Properties具有相同名称(即id)的属性看作是直接映射。

但不是所有都是直接映射关系。例如:<input value="foo" />

  • 当我们没有修改文本框内容时,通过el.value读取对应的 DOM Properties的值就是字符串 foo
  • 如果修改了文本框的值,那么 el.value的值就是当前文本框的值。

修改文本框的内容为’bar’,这时:

console.log(el.getAttribute('value')); // 仍然是foo
console.log(el.value); // bar

用户对文本框内容的修改不会影响 el.getAttribute('value')的返回值,这个现象蕴含HTML Attributes所代表的意义。即HTML Attributes的作用是设置与之对应的DOM Properties的初始值。一旦值改变,DOM Properties始终存储当前值,而getAttribute()得到的仍然是初始值。

我们也可以通过 el.defaultValue来访问初始值,如:

console.log(el.getAttribute('value')); // 仍然是foo
console.log(el.value); // bar
console.log(el.defaultValue); // foo

这说明一个 HTML Attributes可能关联多个 DOM Properties,例如上面的 value="foo"与 el.value 和 el.defaultValue都有关联。

虽然我们可以认为 HTML Attributes是用来设置与之对应的 DOM Properties的初始值的,但是有些值是受限制的,就好像浏览器内部做了默认值校验。

// 通过HTML Attributes提供的默认值不合法,
// 浏览器使用内建的和法治作为对应的 DOM Properties默认值
<input type="foo" />
console.log(el.type); // 'text'

它们两者之间的关系很复杂,但记住一个核心原则即可:HTML Attributes是用来设置与之对应的 DOM Properties的初始值的。

3.正确地设置元素属性:

前面详细讨论了HTML Attributes 和 DOM Properties相关的内容,因为它们会影响DOM属性的添加方式。

  • 对于普通的HTML文件来说,当浏览器解析HTML代码后,会自动分析 HTML Attributes并设置合适的 DOM Properties
  • 但是用户编写在Vue.js的单文件组件中的模板不会被浏览器解析,这意味着原来需要浏览器来完成的工作,现在需要框架来完成。

举例说明,如下禁用按钮:

<button disabled>Button</button>

浏览器解析这段HTML代码时,发现这个按钮存在一个叫做 disabledHTML Attributes,于是将该按钮设置为禁用状态,并将它的 el.disabled这个 DOM Properties的值设置为true,这一切都是浏览器帮我们完成的。

但是同样的代码出现在 Vue.js模板中,这情况有所不同。首先这个html模板会被编译成vnode,等价于:

const button = {
    type: 'button',
    props: {
        disabled: ''
    }
}

这里的 props.disabled值为空字符串,如果在渲染器中调用 setAttribute函数设置属性,则相当于: el.setAttribute('disabled', '')。这样做没问题,浏览器会将按钮禁用。

但考虑如下模板:

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

它对应的vnode为:

const button = {
    type: 'button',
    props: {
        disabled: false
    }
}

用户的本意是"不禁用"按钮,但如果渲染器仍然使用 setAttribute()设置属性值,则会产生意外效果,即按钮被禁用了:

el.setAttribute('disabled', false)

在浏览器上面运行这句代码,可以发现浏览器仍然将按钮禁用了。这是因为使用 setAttribute()设置的值总会被字符串化,所以它等价于:

el.setAttribute('disabled', 'false')

对于按钮来说,它的 el.disabled属性值是布尔类型的,并且它不关心具体的 HTML Attributes的值是什么。只要disabled属性存在,按钮就会被禁用。

所以我们发现,渲染器不应该总是使用 setAttribute()vnode.props对象中的属性设置到元素上。那么应该怎么解决呢?

思路:优先设置 Dom Properties,例如:el.disabled = false

这样是可以正确工作,但是又有新的问题出现了。以一开始的模板为例:

<button disabled>Button</button>
// 对应的vnode:
const button = {
    type: 'button',
    props: {
        disabled: ''
    }
}

观察可以发现,模板经过编译后得到的 vnode对象中,props.disabled的值是一个空字符串。如果直接用它设置元素的 DOM Properties,就相当于:el.disabled = '',此时浏览器会将它的值矫正为布尔类型的值,即false。等价于: el.disabled = false

这违背了用户本意,用户希望禁用按钮,但是值为false则是不禁用的意思。

这么看下来,其实无论是使用 setAttribute(),还是直接设置元素的 DOM Properties都存在缺陷。

想彻底解决这个问题就要做特殊处理,即优先设置元素的 DOM Properties,但当值为空字符串时,要手动将值矫正为true。只有这样才能保证代码行为符合预期。如下代码:

    function mountElement(vnode, container) {
        const el = createElement(vnode.type);
        if (typeof vnode.children === 'string') {
            setElementText(el, vnode.children);
        } else if (Array.isArray(vnode.children)) {
            vnode.children.forEach(child => {
                patch(null, child, el);
            })
        }
        if (vnode.props) {
            for (const key in vnode.props) {
                // 用 in 判断key是否存在对应的 DOM Properties
                if (key in el) {
                    // 获取该 DOM Properties 的类型
                    const type = typeof el[key];
                    const value = vnode.props[key];
                    // 如果是布尔类型,并且value为空字符串,矫正为true
                    if (type === 'boolean' && value === '') {
         
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Vue 中渲染 HTML 有两种方式: 1. 使用 `v-html` 指令,将数据作为 HTML 插入到模板中: ```html <div v-html="htmlData"></div> ``` ```javascript export default { data() { return { htmlData: '<h1>Hello, World!</h1>' } } } ``` 注意:使用 `v-html` 可能会存在 XSS 攻击风险,因此需要对数据进行过滤和转义。 2. 使用 `{{}}` 插值表达式,将数据作为字符串插入到模板中: ```html <div>{{ htmlData }}</div> ``` ```javascript export default { data() { return { htmlData: '<h1>Hello, World!</h1>' } } } ``` 在上述代码中,`htmlData` 中的 HTML 标签会被浏览器解析为 HTML 元素,从而实现渲染。但是,这种方式只能渲染纯文本数据,不能渲染 HTML 元素。如果需要在插值表达式中渲染 HTML 元素,需要使用 Vue 的过滤器或自定义指令来实现。 ### 回答2: Vue 是一种流行的JavaScript 框架,用于构建用户界面,其中包括渲染 HTML。Vue使用了一种基于模板的语法,通过将数据和模板绑定在一起,实现了动态更新 HTML 的功能。 在Vue中,我们可以使用双大括号({{}})语法来插入动态的数据到HTML模板中。这些双大括号中的表达式会被Vue解析并渲染为具体的数据。可以将数据从Vue的组件传递到HTML模板中,然后Vue会根据这些数据自动更新页面上相关的部分。 除了插值表达式之外,Vue还提供了一些指令来操作HTML元素。例如,v-bind 指令可以将数据绑定到HTML元素的属性上,使得属性值可以动态更新。v-if 和 v-show 指令可以根据条件控制HTML元素的显示与隐藏。v-for 指令可以根据数据循环渲染多个HTML元素。 另外,Vue还提供了组件化的思想,可以将页面划分为多个小的组件,每个组件有自己独立的HTML模板和数据。通过组件的方式,复用性增强,开发效率也提高了。 总之,Vue能够非常方便地实现HTML的渲染。无论是插值表达式还是指令,Vue都能够根据数据的变化实时地更新HTML页面,实现了动态的交互效果,提升了用户体验。 ### 回答3: Vue是一种流行的前端框架,它允许我们以声明式的方式构建用户界面。在Vue中,我们可以通过将数据和模板绑定在一起来动态地更新DOM。当数据发生变化时,Vue会自动重新渲染相关的DOM部分,从而实现了页面的响应式更新。 在Vue中,渲染HTML的方式有几种。首先,我们可以使用插值语法{{}}将数据绑定到HTML标签中。例如,我们可以在Vue实例中定义一个变量message,然后将其插入到HTML中的<p>标签中: ``` <div id="app"> <p>{{ message }}</p> </div> ``` 接下来,我们需要创建一个Vue实例,并将其挂载到一个DOM元素上: ``` new Vue({ el: "#app", data: { message: "Hello Vue!" } }) ``` 这样,Vue会自动将message的值渲染到DOM中的相应位置。当我们更新message的值时,Vue会自动重新渲染对应的DOM。 除了插值语法,我们还可以使用指令v-html来渲染HTML。v-html指令可以将一个字符串解析为HTML,并将其插入到DOM中的相应位置。但是要注意,为了防止XSS攻击,Vue会对插入的HTML内容进行转义处理。 例如,我们可以将一个包含HTML标签的字符串绑定到一个<div>标签上: ``` <div id="app"> <div v-html="htmlContent"></div> </div> ``` 然后,在Vue实例中定义一个变量htmlContent,将包含HTML标签的字符串赋值给它: ``` new Vue({ el: "#app", data: { htmlContent: "<p>This is a <strong>bold</strong> text.</p>" } }) ``` 这样,Vue会将htmlContent中的字符串解析为HTML,并将其渲染到DOM中的对应位置。 总之,Vue提供了多种方式来渲染HTML,包括插值语法和v-html指令。这些机制使得我们能够动态地更新页面内容,让用户界面更加强大和灵活。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值