Нынешние студенты, изучающие интерфейс, знакомы с разработкой компонентов React и Vue, и мы часто говорим о пересечении границы между RN и Weex. Какую работу должен проделать фреймворк пользовательского интерфейса, чтобы добиться такого сквозного рендеринга, и можно ли использовать его технические решения для справки или даже применить к нашим собственным проектам? Это то, чем эта статья надеется поделиться.
Введение концепции
Что такое кросс-энд рендеринг? «Конец» здесь не ограничивается традиционными ПК и мобильными устройствами, а является абстрактнымРендерер. Уровень рендеринга не ограничивается DOM браузера и собственными элементами управления пользовательским интерфейсом на мобильной стороне, даже статические файлы и даже виртуальная реальность и другие среды могут быть вашим уровнем рендеринга. Это не просто красивое видение, в 8102 сегодня, в дополнение к сообществу React, чтобы.docx
/ .pdf
В дополнение к уровню рендеринга Facebook даже реализовал слой рендеринга для виртуальной реальности на основе Three.js, а именно ReactVR. Теперь рассмотрим ReactLearn Once, Write AnywhereСлоган, по сути, подчеркивает его поддержку различных слоев рендеринга:
Почему бы просто не использовать API слоя рендеринга? Проблема кросс-энд разработки заключается в стоимости обучения, использования и обслуживания различных слоев рендеринга. Будь то JSX React или Vue.vue
Однофайловые компоненты могут эффективно отделять компоненты пользовательского интерфейса, повышая эффективность разработки и удобство сопровождения кода. Поэтому, естественно, мы захотим использовать такой компонентный метод, чтобы получить контроль над уровнем рендеринга.
Прежде чем мы перейдем к тому, как адаптировать различные слои рендеринга к React/Vue, давайте рассмотрим их базовую иерархию, поскольку они выполняются в старой модели DOM. Например, все мы знаем, что при использовании React в браузере нам вообще нужно импортировать отдельноreact
а такжеreact-dom
Два разных пакета, общую структуру фронтенд-проекта можно кратко представить на следующем рисунке:
Многие студенты, изучающие интерфейс, знакомы с библиотеками пользовательского интерфейса, высокоуровневыми компонентами, управлением состоянием и т. д., которые на самом деле находятся на верхнем уровне «реализации на основе React» после инкапсуляции на рисунке. React и DOM несколько неизвестны. В Vue 2.x структура похожа. Однако в настоящее время Vue не реализует разделение, как React, и его упрощенная базовая структура показана на следующем рисунке:
Как перенести их архитектуру, предназначенную для DOM, на другой уровень рендеринга? Эти реализации описаны, в свою очередь, в следующих разделах:
- Адаптация на основе React 16 Reconciler
- Ненавязчивый метод адаптации на основе Vue EventBus
- Адаптация на основе Vue Mixin
- Адаптация на основе настройки Vue Platform
Адаптация React Reconciler
Причина, по которой React представлен первым, заключается в том, что он уже предоставляет сформированный интерфейс для адаптации. В фирменной архитектуре React 16 Fiber,react-reconciler
Для достижения модуля инкапсуляции на основе одного слоя волокна примирения. Этот модуль с нашим слоем пользовательского рендеринга нуждается в том, что это имеет значение? Его сила лежит,Пока мы предоставляем Reconciler конфигурацию среды рендеринга хоста, React может беспрепятственно выполнять рендеринг в этой среде.. На данный момент наша структура среды выполнения показана на следующем рисунке:
Основные модули, которые нам нужно реализовать на приведенном выше рисунке, этоAdapter, который является мостом для расширения возможностей React на новые среды рендеринга. Как добиться такой адаптации?
Адаптируем известную библиотеку рендеринга WebGLPIXI.jsВ качестве примера кратко опишите, как работает этот механизм. Прежде всего, конечная форма использования реализованного нами слоя адаптации должна быть следующей:
import * as PIXI from 'pixi.js'
import React from 'react'
import { ReactPixi } from 'our-react-pixi'
import { App } from './app'
// 目标渲染容器
const container = new PIXI.Application()
// 使用我们的渲染层替代 react-dom
ReactPixi.render(<App />, container)
Здесь нам нужно добитьсяReactPixi
модуль. Этот модуль представляет собой тонкую обертку вокруг рендерера:
// Renderer 需要依赖 react-reconciler
import { Renderer } from './renderer'
let container
export const ReactPixi = {
render (element, pixiApp) {
if (!container) {
container = Renderer.createContainer(pixiApp)
}
// 调用 React Reconciler 更新容器
Renderer.updateContainer(element, container, null)
}
}
От какой формы рендерера это зависит? Примерно так:
import ReactFiberReconciler from 'react-reconciler'
export const Renderer = ReactFiberReconciler({
now: Date.now,
createInstance () {},
appendInitialChild () {},
appendChild () {},
appendChildToContainer () {},
insertBefore () {},
insertInContainerBefore () {},
removeChild () {},
removeChildFromContainer () {},
getRootHostContext () {},
getChildHostContext () {},
prepareUpdate () {},
// ...
})
Эти конфигурации эквивалентны серии хуков для рендеринга Fiber. Сначала мы предоставляем серию пустых реализаций Stub, а затем реализуем код для работы с объектом PIXI по мере необходимости в соответствующей позиции. Например, нам нужноcreateInstance
Реализуйте новую операцию над объектом PIXI вappendChild
Добавьте родительский объект во входящий экземпляр дочернего объекта PIXI и т. д. Пока эти хуки правильно связаны с соответствующими API слоя рендеринга, React может рендерить его полностью и вsetState
Пришло время обновить его по требованию в соответствии с его собственным diff.
Как только связующий код для этого подключения будет завершен, мы можем использовать компоненты React для управления сторонними библиотеками рендеринга, такими как PIXI:
Это базовая реализация, основанная на доступе React к адаптации слоя рендеринга.
Ненавязчивая адаптация Vue
Поскольку Vue не предоставляет подобныхReactFiberReconciler
Это специально используется для адаптации API слоя рендеринга, поэтому в настоящее время существует множество различных реализаций адаптации слоя рендеринга на основе Vue. Сначала мы вводим «ненавязчивую» адаптацию, которая характеризуется полной реализацией в бизнес-компонентах. Его базовая структура выглядит следующим образом:
Первоначальное намерение этой реализации состояло в том, чтобы позволить нам писать компоненты слоя рендеринга следующим образом:
<div id="app">
<pixi-renderer>
<container @tick="tickInfo" @pointerdown="scaleObject">
<pixi-text :x="10" :y="10" content="hello world"/>
</container>
</pixi-renderer>
</div>
Сначала мы реализуем самый внешнийpixi-renderer
компоненты. На основе контекстно-подобного механизма предоставления/внедрения в Vue мы можем внедрить PIXI в компонент и реализовать динамическое содержимое средства визуализации на основе слота:
// renderer.js
import Vue from 'vue'
import * as PIXI from 'pixi.js'
export default {
template: `
<div class="pixi-renderer">
<canvas ref="renderCanvas"></canvas>
<slot></slot>
</div>`,
data () {
return {
PIXIWrapper: { PIXI, PIXIApp: null },
EventBus: new Vue()
}
},
provide () {
return {
PIXIWrapper: this.PIXIWrapper,
EventBus: this.EventBus
}
},
mounted () {
this.PIXIWrapper.PIXIApp = new PIXI.Application({
view: this.$refs.renderCanvas
})
this.EventBus.$emit('ready')
}
}
Таким образом, у нас есть самый внешний контейнер слоя рендеринга. Далее давайте взглянем на внутренний компонент Container (обратите внимание, что Container здесь представляет не самый внешний контейнер, а только концепцию узлов в PIXI):
// container.js
export default {
inject: ['EventBus', 'PIXIWrapper'],
data () {
return {
container: null
}
},
render (h) { return h('template', this.$slots.default) },
created () {
this.container = new this.PIXIWrapper.PIXI.Container()
this.container.interactive = true
this.container.on('pointerdown', () => {
this.$emit('pointerdown', this.container)
})
// 维护 Vue 与 PIXI 组件间同步
this.EventBus.$on('ready', () => {
if (this.$parent.container) {
this.$parent.container.addChild(this.container)
} else {
this.PIXIWrapper.PIXIApp.stage.addChild(this.container)
}
this.PIXIWrapper.PIXIApp.ticker.add(delta => {
this.$emit('tick', this.container, delta)
})
})
}
}
Это выглядит странно в этом компонентеrender
Это связано с тем, что хотя он и не требует шаблонов, он может иметь характеристики подкомпонентов. Его основная функция — поддерживать согласованность состояния между объектом слоя рендеринга и Vue. Наконец, давайте посмотрим на реализацию компонента Text в виде конечного узла:
// text.js
export default {
inject: ['EventBus', 'PIXIWrapper'],
props: ['x', 'y', 'content'],
data () {
return {
text: null
}
},
render (h) { return h() },
created () {
this.text = new this.PIXIWrapper.PIXI.Text(this.content, { fill: 0xFF0000 })
this.text.x = this.x
this.text.y = this.y
this.text.on('pointerdown', () => this.$emit('pointerdown', this.text))
this.EventBus.$on('ready', () => {
if (this.$parent.container) {
this.$parent.container.addChild(this.text)
} else {
this.PIXIWrapper.PIXIApp.stage.addChild(this.text)
}
this.PIXIWrapper.PIXIApp.ticker.add(delta => {
this.$emit('tick', this.text, delta)
})
})
}
}
Таким образом, мы моделируем опыт разработки компонентов, аналогичный React. Но здесь есть несколько проблем:
- Мы не можем визуализировать без DOM.
- Нам пришлось вручную поддерживать состояние экземпляра PIXI в каждом пользовательском компоненте.
- Используются два набора механизмов связи между компонентами, EventBus и props, и существует избыточность.
Есть ли другие реализации?
Адаптация Vue Mixin
Нарисуйте узлы DOM на холстеvnode2canvasВ библиотеке рендеринга реализована специальная технология, позволяющая отслеживать Vnodes с помощью Mixins. Это эквивалентно внедрению слоя рендеринга непосредственно в Canvas. Структура этой схемы примерно выглядит так:
Его исходного кода не так много, изюминкой является смонтированный хук этого миксина:
mounted() {
if (this.$options.renderCanvas) {
this.options = Object.assign({}, this.options, this.getOptions())
constants.IN_BROWSER && (constants.rate = this.options.remUnit ? window.innerWidth / (this.options.remUnit * 10) : 1)
renderInstance = new Canvas(this.options.width, this.options.height, this.options.canvasId)
// 在此 $watch Vnode
this.$watch(this.updateCanvas, this.noop)
constants.IN_BROWSER && document.querySelector(this.options.el || 'body').appendChild(renderInstance._canvas)
}
},
из-за здесьupdateCanvas
вернулся вVnode
(Хотя такое поведение кажется немного нелогичным с точки зрения семантики), рендеринг Canvas фактически запускается при обновлении Vnode. Таким образом, мы можем аккуратно связать обновление дерева виртуальных узлов непосредственно со слоем рендеринга.
Эта реализация действительно новая, но она несколько похожа на Hack:
- Он должен внедрить некоторые специальные методы и свойства для компонентов Vue.
- Это требует связывания структуры данных Vnode, которая является антишаблоном в React Reconciler.
- Он должен сам реализовать обход Vnode и прокси-получателя для объекта Canvas, а стоимость реализации высока.
- Он по-прежнему поставляется с собственным слоем рендеринга Vue в DOM.
Есть ли какой-то более "ортодоксальный" подход?
Пользовательская адаптация платформы Vue
Можно считать, что метод поддержки Weex в Vue 2.x является наиболее подходящим для нашего понимания пользовательского слоя рендеринга. Знаменитый mpvue также реализует слой рендеринга в апплет по этой схеме. Точно так же мы можем кратко нарисовать его структурную схему:
Что такое Платформа на картинке выше? Пока мы открываем исходный код mpvue, легко найти его новую структуру каталогов в каталоге платформ:
platforms
├── mp
│ ├── compiler
│ │ ├── codegen
│ │ ├── directives
│ │ └── modules
│ ├── runtime
│ └── util
├── web
│ ├── compiler
│ │ ├── directives
│ │ └── modules
│ ├── runtime
│ │ ├── components
│ │ ├── directives
│ │ └── modules
│ ├── server
│ │ ├── directives
│ │ └── modules
│ └── util
└── weex
├── compiler
│ ├── directives
│ └── modules
├── runtime
│ ├── components
│ ├── directives
│ └── modules
└── util
надmp
На самом деле, это недавно добавленная запись слоя рендеринга апплета. Вы можете видеть, что слой рендеринга не зависит от основного модуля Vue. Так что же здесь нужно сделать для адаптации? Вкратце это:
- Генерация объектного кода во время компиляции (это должно определяться характеристиками платформы апплета).
- runtime/eventsПреобразование событий слоя рендеринга в модулях в события в Vue.
- runtime/lifecycleСинхронизация слоя рендеринга в модуле с жизненным циклом Vue.
- runtime/renderапплет в модуле
setData
Поддержка рендеринга и оптимизация. - runtime/node-opsОбработка операций Vnode в модуле.
Самое интересное здесь то, чтоnode-ops
, вместо синхронизации состояния объектов слоя рендеринга здесь, как я изначально предполагал, реализация mpvue выглядит очень легко читаемой... вот так:
// runtime/node-ops.js
const obj = {}
export function createElement (tagName: string, vnode: VNode) {
return obj
}
export function createElementNS (namespace: string, tagName: string) {
return obj
}
export function createTextNode (text: string) {
return obj
}
export function createComment (text: string) {
return obj
}
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {}
export function removeChild (node: Node, child: Node) {}
export function appendChild (node: Node, child: Node) {}
export function parentNode (node: Node) {
return obj
}
export function nextSibling (node: Node) {
return obj
}
export function tagName (node: Element): string {
return 'div'
}
export function setTextContent (node: Node, text: string) {
return obj
}
export function setAttribute (node: Element, key: string, val: string) {
return obj
}
Разве это не похоже на это ничего не делает? В моем личном понимании это имеет больше, чтобы сделать с API апплета: это должно быть.wxml
API комбинации шаблонов увеличивает сложность взятия на себя Vue управления состоянием в соответствии с методом настройки Reconciler, поэтому таким образом сложно напрямую адаптировать апплет к слою рендеринга.Дерево компонентов и попытка синхронизировать их экономически выгодно.
Здесь мы в основном представили базовый способ поддержки слоя рендеринга Vue, добавив платформу, Преимущества этого решения очевидны:
- Это устраняет необходимость использования API слоя рендеринга в компонентах Vue.
- Это относительно менее навязчиво для бизнес-компонентов Vue.
- Ему не нужно связывать структуру данных Vnode.
- Он может буквально выйти из среды DOM.
Что касается этого решения, самая большая проблема в настоящее время должна заключаться в том, что оно должно разветвлять исходный код Vue. В дополнение к затратам на обслуживание, если такой слой рендеринга используется в собственном проекте на основе Vue, тогда будут две разные среды Vue с небольшими различиями, что звучит немного нереально... К счастью, эта часть внешнего API уже в планах Vue 3.0, стоит с нетерпением ждать XD
Суммировать
До сих пор мы суммировали основные способы настройки слоя рендеринга в React и Vue. Повторить:
- Метод адаптации на основе React 16 Reconciler прост и понятен.
- Метод неинтрузивной адаптации, основанный на Vue EventBus, прост, но имеет много деталей, открытых внешнему миру.
- Основываясь на методе адаптации Vue Mixin, Hack означает сильнее.
- Метод адаптации, основанный на настройке платформы Vue, является наиболее гибким, но требует исходного кода форка.
Видно, что на текущем временном узле проектам без зависимостей от пути проще использовать React при настройке слоя рендеринга Canvas/WebGL. Что касается выбора решения Vue, ссылка особенно у автораЗнать ответВ комментариях способ модификации исходного кода форка — это решение с лучшей обратной совместимостью.
В дополнение к приведенным выше фрагментам кода автор также реализовал несколько POC-прототипов слоя адаптации рендеринга в процессе редактирования этой статьи, которые можно найти в разделеrenderer-adapters-pocвидел в этом репозитории. Наконец, некоторые справочные ссылки прилагаются для чтения заинтересованными студентами:
- react-reconciler
- Hello World Custom React Renderer
- Vue.js Custom Component Renderers
- Визуализируйте свой виртуальный дом на холсте
- начальная фиксация mpvue
P.S. Команда редакторов нашей базы Xiamen Toss Rendering открыта для набора, пожалуйста, разместите свое резюме xuebi наgaoding.comкакие