Эссе Vue: функция Render() и JSX

Vue.js

1. Введение

1.1 Дерево HTML DOM и виртуальный DOM Vue

  • Мы знаем, что когда браузеры анализируют HTML-файлы, они анализируют теги HTML в дереве DOM (tree of DOM nodes). Структурируя элементы узлов, браузеры могут легко отслеживать состояние всей страницы, но частые локальные обновления узлов обходятся дорого.
  • Чтобы более эффективно отображать HTML,Vue.jsа такжеReactтак же какEmber.jsТочно так же строится соответствующий JS-объект по отображению реального DOM, то есть виртуального DOM (Virtual DOM). Создайте буферную зону между данными и DOM, не обновляя DOM каждый раз.Подробности смотрите на схематической диаграмме React Virtual DOM ниже:
Схематическая диаграмма React Virtual DOM

1.2 Слоты в компонентах Vue

  • Vue.jsМеханизм Slot означает, что когда родительский компонент вкладывает дочерний компонент, он может передавать фрагменты HTML-кода дочернему компоненту для рендеринга, но вам не нужно заботиться о том, где HTML должен быть размещен в дочернем компоненте.
  • Вы запутались и не понимаете? Например:
<!-- 【需求】
  封装一个文章标题组件,但是要根据情况给组件传递"分享到站外"的html代码片段
  因为在调用组件的时候,才能根据当前用户角色确定是分享到微信还是微博的按钮 -->

<!-- 组件<article>期望的最终渲染的代码是这样的 -->  
<div>
    <h1>
        vue.js Render()函数&JSX
    </h1>
    <!-- 这里是可能传递的代码片段1 -->
    <span><Button>分享到新浪微博</Button></span>
    <!-- 这里是可能传递的代码片段2 -->
    <span><Button>分享到微信</Button></span>
</div>
  • мы начинаем сVvue.jsСлот механизм для достижения
  1. определениеarticle.vueкомпоненты
<tempalte>
    <div>
        <h1>
            {{title}}
        </h1>
        <!-- 子组件只关心slot摆放的位置,而不关心传进来的是什么 -->
        <slot></slot>
    </div>
</tempalte>
<script>
export default{
    name : 'Article',
    props : ['title']
}
</script>
  1. main.vueИспользуйте заголовок
<template>
    <div>
        <article :title="titleStr">
            <span>
                <Button v-if="userType === 'weibo'">分享到新浪微博</Button>
                <Button v-if="userType === 'weixin'">分享到微信</Button>
            </span>
        </article>
    </div>
</template>
<script>
export default{
    name : 'Main',
    data() {
        return: {
            userType: 'weibo',
            titleStr: 'vue.js Render()函数&JSX'
        }
    }
}
</script>   
  • скажи большеslot, как подсказывает название,Vue.jsРаспространение контента достигается за счет настройки механизма, аналогичного слоту/монетному слоту для программы:
  1. Подкомпонент определяет положение слота;
  2. Родительский компонент может вставить любой фрагмент кода, соответствующий спецификации Slot;
  • Таким образом, он не только реализует развязку программы, но и очень удобен в использовании.
  • конечно,Vue.jsОн также поддерживает именованные слоты, то есть путем определения имен ключей для различения различных фрагментов кода слотов, которые здесь не обсуждаются.

2. функция рендеринга()

2.1 Зачем использовать функцию render()

  • использовалVue.jsВсе мои друзья знают, что большую часть времени они используют метод шаблона для создания HTML, потому что vue предоставляетv-if,v-forЖдем серию инструкций по управлению, чтобы сделать нашу разработку легкой и приятной.
  • Но кроме того, на самом делеVue.jsтакже обеспечиваетrender()функция для создания HTML. Давайте создадим HTML более гибко через код логики JS.
  • Как сказано на примере официального сайта Vue: при инкапсуляции компонента заголовка статьи неизвестно, что будет в конечном итоге сгенерировано, в частности теги h1~h6. В это время, если вы используете<code>v-if</code>Делайте выводы, тогда объем кода слишком велик, но с<code>render()</code>Реализовать функцию очень просто.
<!-- 用template实现 -->
<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>

Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
<!-- 用render()函数实现 -->
Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 由子节点构成的数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

2.2 Параметры функции render()

render(crateElmentFn,context){}

  • createElemntFnЭто основной метод, используемый Vue для динамического создания HTML [Примечание: наименование функции createElement как h является общим соглашением vue.js, помните, что это одно и то же]
  • contextконтекст экземпляра компонента, включая все свойства экземпляра компонента
    • реквизит: объект, предоставляющий реквизит
    • Children: массив дочерних узлов VNode
    • слоты: объект слотов
    • data: объект данных, переданный компоненту
    • parent: ссылка на родительский компонент

3. Функция createElement()

3.1 Параметры createElement()

createElemnt(newNode,newNodeConfig,childVNodeList)

  • createElemnt()Функция имеет три основных параметра:
    • newNode: Создаваемый узел [Обязательный параметр]
      • Тип параметра: {String|Object|Function}, который может быть именем создаваемого тега HTML, объектом компонента или асинхронной функцией, возвращаемой как String или Vue Object.
    • newNodeConfig: Объект конфигурации нового узла [необязательно]
    • childVNodeList: Набор дочерних узлов, которые будут включены в новый узел [необязательно]
      • Тип параметра: {Строка | Массив}
      • Примечание: в официальном руководстве по vue указано, что переданные VNodes должны быть уникальными.Если вы хотите многократно создавать один и тот же элемент HTML, вам нужно использовать фабричную функцию для достижения этого.
  • Пример вызова:
// @returns {VNode}
createElement(
  'div',
  {},
  [
    'Some text comes first.',
    createElement('h1', 'A headline'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

3.2 Подробное объяснение параметров newNodeConfig

{
  // 和 `v-bind:class` 的 API 相同【注意:由于是关键字,要用单引号包含】
  'class': {
    foo: true,
    bar: false
  },
  // 和 `v-bind:style` 的 API 相同【注意:由于是关键字,要用单引号包含】
  'style': {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML 属性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件处理程序嵌套在 `on` 字段下,
  // 然而不支持在 `v-on:keyup.enter` 中的修饰符。
  // 因此,你必须手动检查
  // 处理函数中的 keyCode 值是否为 enter 键值。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,
  // 用于监听原生事件,而不是组件内部
  // 使用 `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。
  // 注意,由于 Vue 会追踪旧值,
  // 所以不能对`绑定`的`旧值`设值
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽(scoped slot)的格式如下
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果此组件是另一个组件的子组件,
  // 需要为插槽(slot)指定名称
  slot: 'name-of-slot',
  // 其他特殊顶层(top-level)属性
  key: 'myKey',
  ref: 'myRef'
}

3.3 Используйте createElement() вместо шаблона для создания HTML

3.3.1 Условное суждение и рендеринг цикла

код шаблона:

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

код рендеринга:

props: ['items'],
render: function (createElement) {
  // 使用原生JS代码来做条件判断
  if (this.items.length) {
    // 使用map()来循环调用createElement()函数
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p', 'No items found.')
  }
}

3.3.2 Привязка данных

код рендеринга:

props: ['value'],
render: function (createElement) {
  // 【注意:由于JS可以用function作为函数的参数传递,为了避免this指针的混乱,在render函数里,要记得对this指针进行缓存】
  var self = this
  return createElement('input', {
    // 手动vue emit事件的触发,来手动控制value值的维护,这是深入底层实现需要付出的代价
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}

3.3.3 Привязка событий

  • Vue.js вcreateElemnt()В функции отображается ряд префиксов модификаторов инструкции события:
модификатор приставка
.passive &
.capture !
.once ~
.capture.onceили.once.capture ~!
  • Пример:
createElement('input',{
    on: {
      '!click': this.doThisInCapturingMode,
      '~keyup': this.doThisOnce,
      '~!mouseover': this.doThisOnceInCapturingMode
    }
})

3.3.4 передача слота

  • Если, как упоминалось выше, родительский компонент хочет передать слот при вызове дочернего компонента, вы можете передатьthis.$slotsвозражать против доступа
render: function (createElement) {
  // 默认传递所有Slot
  // 等同于:<div><slot></slot></div>
  return createElement('div', this.$slots.default)
}
  • Если вы хотите передать параметры при передаче Slot, вы можете передатьthis.$scopedSlots
props: ['message'],
render: function (createElement) {
  // 等同于`<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

4. JSX

  • несмотря на то чтоrender()Функция создания фрагментов HTML-кода очень гибкая, но весь код конфигурации JS действительно плохо читается. Чтобы упростить код, мы можем использовать JSX для написания HTML непосредственно в коде JS:
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
  • Но для поддержки JSX вам нужно передать плагин JSX Babel. Пример кода конфигурации выглядит следующим образом:

package.json

devDependencies: {
    "babel-helper-vue-jsx-merge-props": "2.0.3",
    "babel-plugin-syntax-jsx": "6.18.0",
    "babel-plugin-transform-vue-jsx": "3.7.0"
}

.babelrc

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

использованная литература