Подробное объяснение рендеринга в Vue (функции рендеринга и JSX)

Vue.js

Я считаю, что каждый видел или использовал визуализацию в коде более или менее. Если вы все еще запутались в этом, получите на автобусе! Принесите это вам сегодня.


@[toc]


A, Профиль данных рендеринга

Функция рендеринга — это недавно добавленная функция в Vue2.x, которая в основном используется для повышения производительности узлов и рассчитывается на основе JavaScript. Используйте функцию Render, чтобы преобразовать узлы в Template в виртуальный Dom.

Vue рекомендует использовать шаблоны для создания вашего HTML в подавляющем большинстве случаев. Однако в некоторых сценариях вам действительно нужны все возможности программирования JavaScript. Затем вы можете использовать функции рендеринга, которые ближе к компилятору, чем шаблоны.

Проще говоря, в Vue мы используем шаблонный синтаксис HTML для создания страниц, а с помощью функции Render мы можем использовать язык Js для построения DOM.

Поскольку Vue — это виртуальный DOM, когда вы получаете шаблон шаблона, его необходимо преобразовать в функцию VNode, а с помощью функции Render для построения DOM Vue избегает процесса преобразования.

2. Первое знакомство с Render

Когда вы впервые столкнулись с этим, это может выглядеть так:

  • IView
render:(h, params)=>{
    return h('div', {style:{width:'100px',height:'100px',background:'#ccc'}}, '地方')
}
  • Element
<el-table-column :render-header="setHeader">
</el-table-column>
setHeader (h) {
 return h('span', [
    h('span', { style: 'line-height: 40px;' }, '备注'),
      h('el-button', {
        props: { type: 'primary', size: 'medium', disabled: this.isDisable || !this.tableData.length },
        on: { click: this.save }
      }, '保存当前页')
    ])
  ])
},

или это:

renderContent (createElement, { node, data, store }) {
	return createElement('span', [
		// 显示树的节点信息
		createElement('span', node.label)
		// ......
	])
}

Так как же это выглядит на самом деле? Это также начинается с его происхождения.

2.1, узлы, деревья

Прежде чем углубляться в функции рендеринга, важно понять, как работают некоторые браузеры. Возьмите следующий HTML в качестве примера:

<div>
  <h1>My title</h1>
  Some text content
  <!-- TODO: Add tagline -->
</div>

Когда браузер читает эти коды, он создаетДерево узлов DOMЧтобы отслеживать все, как вы нарисовали семейное дерево, чтобы отследить разработку членов семьи.

Дерево узлов DOM, соответствующее приведенному выше HTML, показано на следующем рисунке:

在这里插入图片描述
Каждый элемент является узлом. Каждый фрагмент текста также является узлом. Даже комментарии являются узлами. Узел является частью страницы. Как и в генеалогическом дереве, у каждого узла могут быть дочерние элементы (то есть каждый раздел может содержать другие разделы).

Эффективное обновление всех этих узлов может быть затруднено, но, к счастью, вам не нужно делать это вручную. Вам просто нужно указать Vue, какой HTML вы хотите на странице, это может быть в шаблоне:

<h1>{{ blogTitle }}</h1>

Или в функции рендеринга:

render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

В обоих случаях Vue будет автоматически обновлять страницу, даже если изменится заголовок блога.

2.2, виртуальный DOM

Vue отслеживает, как он хочет изменить реальный DOM, создавая виртуальный DOM. Пожалуйста, внимательно посмотрите на эту строку кода:

return createElement('h1', this.blogTitle)

createElementЧто именно он вернет? На самом деле это не _actual_ элемент DOM. его более точное название может бытьcreateNodeDescription, потому что содержащаяся в нем информация скажет Vue, какой тип узла необходимо отобразить на странице, включая описания его дочерних узлов. Мы описываем такие узлы как "виртуальный узел", который часто сокращается как "VNode«Виртуальный DOM» — это то, что мы называем всем деревом VNode, построенным из дерева компонентов Vue.

Примечание:==当使用render函数描述虚拟 DOM 时,vue 提供一个函数,这个函数是就构建虚拟 DOM 所需要的工具。官网上给他起了个名字叫 createElement。还有约定的简写叫 h,将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。 ==

Интересно~ На самом деле этоcreateElement, давайте подойдем немного ближе и глубже поймем это ~

Третье, назначения и визуализация

3.1 параметры createElement

CreateeEdement (Tagname, опция, контент) принимает три параметраcreateElement(" 定义的元素 ",{ 元素的性质 }," 元素的内容"/[元素的内容])

  • официальная документация
// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中属性对应的数据对象。可选。
  {
    // (详情见下一节-3.2 深入数据对象)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

3.2 Погружение в объекты данных

{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 属性内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层属性
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

3.3 Возьмите небольшой каштан

render:(h) => {
  return h('div',{
&emsp;&emsp;&emsp;//给div绑定value属性
     props: {
         value:''
     },
&emsp;&emsp;&emsp;//给div绑定样式
&emsp;&emsp;&emsp;style:{
&emsp;&emsp;&emsp;&emsp;&emsp;width:'30px'
&emsp;&emsp;&emsp;},&emsp;
&emsp;&emsp;&emsp;//给div绑定点击事件&emsp;&emsp;
     on: {
         click: () => {
            console.log('点击事件')
         }
     },
  })
}

3.4 Ограничения

Он также имеет небольшой темперамент ~ Помните об этом ограничении ~

  • VNode должен быть уникальным

Все VNODE в дереве компонентов должны быть уникальными. Это означает, что следующая функция рендеринга недопустима:

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // 错误 - 重复的 VNode
    myParagraphVNode, myParagraphVNode
  ])
}

Если вам действительно нужны элементы/компоненты, которые повторяются много раз, вы можете использовать для этого фабричные функции. Например, следующая функция рендеринга совершенно корректно отображает 20 одинаковых абзацев:

render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}

Все вышеперечисленное — это основы, о которых следует помнить каждому, вот некоторые из его особенностей.


4. Маленькая личность рендера

4.1 v-если и v-для

Функции рендеринга Vue не предлагают проприетарной альтернативы, пока это можно легко сделать в родном JavaScript. Например, в используемом шаблонеv-ifа такжеv-for:

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>

Они могут быть использованы в функции рендеринга с помощью JavaScriptif/elseа такжеmapпереписать:

props: ['items'],
render: function (createElement) {
  if (this.items.length) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p', 'No items found.')
  }
}

4.2 v-model

Прямого аналога v-model в функции рендеринга нет — приходится реализовывать соответствующую логику самостоятельно:

props: ['value'],
render: function (createElement) {
  var self = this
  return createElement('input', {
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}

Это цена углубления, но это дает вам больше контроля над деталями взаимодействия, чем v-model.

4.3 События и ключевые модификаторы

для.passive,.captureа также.onceДля этих модификаторов событий Vue предоставляет соответствующие префиксы, которые можно использовать дляon:

модификатор приставка
.passive &
.capture !
.once ~
.capture.onceили.once.capture ~!

Например:

on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

Для всех остальных модификаторов частные префиксы не требуются, потому что вы можете использовать методы событий в обработчике событий:

модификатор Эквивалентные операции в функциях-обработчиках
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target !== event.currentTarget) return
кнопка:.enter, .13 if (event.keyCode !== 13) return(Для других ключевых модификаторов используйте13изменить на другой код ключа)
Клавиши модификатора:.ctrl, .alt, .shift, .meta if (!event.ctrlKey) return(БудуctrlKeyсоответственно изменены наaltKey,shiftKeyилиmetaKey)

Вот пример использования всех модификаторов:

on: {
  keyup: function (event) {
    // 如果触发事件的元素不是事件绑定的元素
    // 则返回
    if (event.target !== event.currentTarget) return
    // 如果按下去的不是 enter 键或者
    // 没有同时按下 shift 键
    // 则返回
    if (!event.shiftKey || event.keyCode !== 13) return
    // 阻止 事件冒泡
    event.stopPropagation()
    // 阻止该元素默认的 keyup 事件
    event.preventDefault()
    // ...
  }
}

4.4 Слоты

ты можешь пройтиthis.$slotsПолучите доступ к содержимому статических слотов, каждый из которых представляет собой массив VNode s:

render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div', this.$slots.default)
}

также черезthis.$scopedSlotsДоступ к слотам с ограниченной областью действия, каждый из которых является функцией, возвращающей несколько VNodes:

props: ['message'],
render: function (createElement) {
  // `<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

Если вы хотите передать слот с заданной областью дочернему компоненту с помощью функции рендеринга, вы можете использовать объект данных VNode вscopedSlotsПоле:

render: function (createElement) {
  return createElement('div', [
    createElement('child', {
      // 在数据对象中传递 `scopedSlots`
      // 格式为 { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}

Пятый, бой

Дерево в элементе

// 树节点的内容区的渲染回调
renderContent(h, { node, data, store }) {
    let aa = () => {
        console.log(data)
    }
    return  h('span', [
        h('span', {
            class: "custom-tree-node"
        }, [
            h('i', { class: "icon-folder" }), h('span', { props: { title: node.label }, class: "text ellipsis" }, node.label),
            h('el-popover', {
                props: {
                    placement: "bottom",
                    title: "",
                    width: "61",
                    popperClass: "option-group-popover",
                    trigger: "hover"
                }
            }, [
                h('ul', { class: "option-group" }, [
                    h('li', {
                        class: "pointer-text",
                        on: {
                            click: aa
                        }
                    }, '编辑'),
                    h('li', { class: "pointer-text" }, '删除'),
                    h('li', { class: "pointer-text" }, '添加')
                ]),
                h('i', { slot: "reference", class: "el-icon-more fr more-icon",
                    on: {
                        click: (e) => {
                            e.stopPropagation();
                        }
                    }
                })
            ])
        ])
    ])
},

6. Расширение — JSX

Если вы пишете много функций рендеринга, вам может показаться, что следующий код очень болезненный:

createElement(
  'anchored-heading', {
    props: {
      level: 1
    }
  }, [
    createElement('span', 'Hello'),
    ' world!'
  ]
)

Особенно когда соответствующий шаблон такой простой:

<anchored-heading :level="1">
  <span>Hello</span> world!
</anchored-heading>

Вот почему существует плагин Babel для использования синтаксиса JSX в Vue, который возвращает нас к чему-то более близкому к шаблонам.

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

Кодировать слова непросто. Если вы считаете, что это полезно, поставьте лайк и поддержите~


在这里插入图片描述

Отсканируйте QR-код выше, чтобы подписаться на мой номер подписки~