日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > vue >内容正文

vue

前端vue适配不同的分辨率_浅析 React / Vue 跨端渲染原理与实现

發(fā)布時間:2024/10/8 vue 86 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端vue适配不同的分辨率_浅析 React / Vue 跨端渲染原理与实现 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

當(dāng)下的前端同學(xué)對 React 與 Vue 的組件化開發(fā)想必不會陌生,RN 與 Weex 的跨界也常為我們所津津樂道。UI 框架在實現(xiàn)這樣的跨端渲染時需要做哪些工作,其技術(shù)方案能否借鑒乃至應(yīng)用到我們自己的項目中呢?這就是本文所希望分享的主題。

概念簡介

什么是跨端渲染呢?這里的「端」其實并不局限在傳統(tǒng)的 PC 端和移動端,而是抽象的渲染層 (Renderer)。渲染層并不局限在瀏覽器 DOM 和移動端的原生 UI 控件,連靜態(tài)文件乃至虛擬現(xiàn)實等環(huán)境,都可以是你的渲染層。這并不只是個美好的愿景,在 8102 年的今天,除了 React 社區(qū)到 .docx / .pdf 的渲染層以外,Facebook 甚至還基于 Three.js 實現(xiàn)了到 VR 的渲染層,即 ReactVR。現(xiàn)在回顧 React 的 Learn Once, Write Anywhere 口號,實際上強調(diào)的就是它對各種不同渲染層的支持:

為什么不直接使用渲染層的 API 呢?跨端開發(fā)的一個痛點,就在于各種不同渲染層的學(xué)習(xí)、使用與維護(hù)成本。而不管是 React 的 JSX 還是 Vue 的 .vue 單文件組件,都能有效地解耦 UI 組件,提高開發(fā)效率與代碼維護(hù)性。從而很自然地,我們就會希望使用這樣的組件化方式來實現(xiàn)我們對渲染層的控制了。

在開始介紹如何為 React / Vue 適配不同渲染層之前,我們不妨回顧一下它們在老本行 DOM 中執(zhí)行時的基本層次結(jié)構(gòu)。比如我們都知道,在瀏覽器中使用 React 時,我們一般需要分別導(dǎo)入 react 與 react-dom 兩個不同的 package,這時前端項目的整體結(jié)構(gòu)可以用下圖簡略地表示:

很多前端同學(xué)熟悉的 UI 庫、高階組件、狀態(tài)管理等內(nèi)容,實際上都位于圖中封裝后「基于 React 實現(xiàn)」的最頂層,連接 React 與 DOM 的 React DOM 一層則顯得有些默默無聞。而在 Vue 2.x 中,這種結(jié)構(gòu)是類似的。不過 Vue 目前并未實現(xiàn) React 這樣的拆分,其簡化的基本結(jié)構(gòu)如下圖所示:

如何將它們這個為 DOM 設(shè)計的架構(gòu)遷移到不同的渲染層呢?下文中會依次介紹這些實現(xiàn)方案:

  • 基于 React 16 Reconciler 的適配方式
  • 基于 Vue EventBus 的非侵入式適配方式
  • 基于 Vue Mixin 的適配方式
  • 基于 Vue Platform 定制的適配方式

React Reconciler 適配

之所以首先介紹 React,是因為它已經(jīng)提供了成型的接口供適配之用。在 React 16 標(biāo)志性的 Fiber 架構(gòu)中,react-reconciler 模塊將基于 fiber 的 reconciliation 實現(xiàn)封裝為了單獨的一層。這個模塊與我們定制渲染層的需求有什么關(guān)系呢?它的威力在于,只要我們?yōu)?Reconciler 提供了宿主渲染環(huán)境的配置,那么 React 就能無縫地渲染到這個環(huán)境。這時我們的運行時結(jié)構(gòu)如下圖所示:

上圖中我們所需要實現(xiàn)的核心模塊即為 Adapter,這是將 React 能力擴展到新渲染環(huán)境的橋梁。如何實現(xiàn)這樣的適配呢?

我們以適配著名的 WebGL 渲染庫 PIXI.js 為例,簡要介紹這一機制如何工作。首先,我們所實現(xiàn)的適配層,其最終的使用形式應(yīng)當(dāng)如下:

import * as PIXI from 'pixi.js'import React from 'react'import { ReactPixi } from 'our-react-pixi'import { App } from './app'// 目標(biāo)渲染容器const container = new PIXI.Application()// 使用我們的渲染層替代 react-domReactPixi.render(, container)復(fù)制代碼

這里我們需要實現(xiàn)的就是 ReactPixi 模塊。這個模塊是 Renderer 的一層薄封裝:

// Renderer 需要依賴 react-reconcilerimport { Renderer } from './renderer'let containerexport const ReactPixi = { render (element, pixiApp) { if (!container) { container = Renderer.createContainer(pixiApp) } // 調(diào)用 React Reconciler 更新容器 Renderer.updateContainer(element, container, null) }}復(fù)制代碼

它依賴的 Renderer 是什么形式的呢?大致是這樣的:

import ReactFiberReconciler from 'react-reconciler'export const Renderer = ReactFiberReconciler({ now: Date.now, createInstance () {}, appendInitialChild () {}, appendChild () {}, appendChildToContainer () {}, insertBefore () {}, insertInContainerBefore () {}, removeChild () {}, removeChildFromContainer () {}, getRootHostContext () {}, getChildHostContext () {}, prepareUpdate () {}, // ...})復(fù)制代碼

這些配置相當(dāng)于 Fiber 進(jìn)行渲染的一系列鉤子。我們首先提供一系列的 Stub 空實現(xiàn),而后在相應(yīng)的位置實現(xiàn)按需操作 PIXI 對象的代碼即可。例如,我們需要在 createInstance 中實現(xiàn)對 PIXI 對象的 new 操作,在 appendChild 中為傳入的 PIXI 子對象實例加入父對象等。只要這些鉤子都正確地與渲染層的相應(yīng) API 綁定,那么 React 就能將其完整地渲染,并在 setState 時依據(jù)自身的 diff 去實現(xiàn)對其的按需更新了。

這些連接性的膠水代碼完成后,我們就能夠用 React 組件來控制 PIXI 這樣的第三方渲染庫了:

這就是基于 React 接入渲染層適配的基本實現(xiàn)了。

Vue 非侵入式適配

由于 Vue 暫時未提供類似 ReactFiberReconciler 這樣專門用于適配渲染層的 API,因此基于 Vue 的渲染層適配在目前有較多不同的實現(xiàn)方式。我們首先介紹「非侵入式」的適配,它的特點在于完全可在業(yè)務(wù)組件中實現(xiàn)。其基本結(jié)構(gòu)形如下圖:

這個實現(xiàn)的初衷是讓我們以這種方式編寫渲染層組件:

首先我們實現(xiàn)最外層的 pixi-renderer 組件。基于 Vue 中類似 Context 的 Provide / Inject 機制,我們可以將 PIXI 注入該組件中,并基于 Slot 實現(xiàn) Renderer 的動態(tài)內(nèi)容:

// renderer.jsimport Vue from 'vue'import * as PIXI from 'pixi.js'export default { template: `

這樣我們就具備了最外層的渲染層容器了。接下來讓我們看看內(nèi)層的 Container 組件(注意這里的 Container 不代表最外層的容器,只是 PIXI 中代表節(jié)點的概念):

// container.jsexport 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) }) // 維護(hù) 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) }) }) }}復(fù)制代碼

這個組件里顯得古怪的 render 是由于其雖然無需模板,但卻可能有子組件的特點所決定的。其主要作用即是維護(hù)渲染層對象與 Vue 之間的狀態(tài)一致。最后讓我們看看作為葉子節(jié)點的 Text 組件實現(xiàn):

// text.jsexport 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) }) }) }}復(fù)制代碼

這樣我們就模擬出了和 React 類似的組件開發(fā)體驗。但這里存在幾個問題:

  • 我們無法脫離 DOM 做渲染。
  • 我們必須在各個定制的組件中手動維護(hù) PIXI 實例狀態(tài)。
  • 使用了 EventBus 和 props 兩套組件間通信機制,存在冗余。

有沒有其它的實現(xiàn)方案呢?

Vue Mixin 適配

將 DOM 節(jié)點繪制到 Canvas 的 vnode2canvas 渲染庫實現(xiàn)了一種特殊的技術(shù),可以通過 Mixin 的方式實現(xiàn)對 Vnode 的監(jiān)聽。這就相當(dāng)于實現(xiàn)了一個直接到 Canvas 的渲染層。這個方案的結(jié)構(gòu)大致形如這樣:

它的源碼并不多,亮點在于這個 Mixin 的 mounted 鉤子:

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) }},復(fù)制代碼

由于這里的 updateCanvas 中返回了 Vnode(雖然這個行為似乎有些不合語義的直覺),故而這里實際上會在 Vnode 更新時觸發(fā)對 Canvas 的渲染。這樣我們就能巧妙地將虛擬節(jié)點樹的更新與渲染層直接聯(lián)系在一起了。

這個實現(xiàn)確實很新穎,不過多少有些 Hack 的味道:

  • 它需要為 Vue 組件注入一些特殊的方法與屬性。
  • 它需要耦合 Vnode 的數(shù)據(jù)結(jié)構(gòu),這在 React Reconciler 中是一種反模式。
  • 它需要自己實現(xiàn)對 Vnode 的遍歷與對 Canvas 對象的 getter 代理,實現(xiàn)成本較高。
  • 它仍然附帶了 Vue 自身到 DOM 的渲染層。

有沒有一些更加「正統(tǒng)」的方法呢?

Vue Platform 定制適配

可以認(rèn)為 Vue 2.x 中對 Weex 的支持方式,是最貼合我們對定制渲染層的理解的。大名鼎鼎的 mpvue 也是按照這個方案實現(xiàn)了到小程序的渲染層。類似地,我們可以簡略地畫出它的結(jié)構(gòu)圖:

上圖中的 Platform 是什么呢?我們只要打開 mpvue 的源碼,很容易找到它在 platforms 目錄下新增的目錄結(jié)構(gòu):

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復(fù)制代碼

上面的 mp 實際上就是新增的小程序渲染層入口了。可以看到渲染層是獨立于 Vue 的 core 模塊的。那么這里的適配需要做哪些處理呢?概括而言有以下這些:

  • 編譯期的目標(biāo)代碼生成(這個應(yīng)當(dāng)是小程序的平臺特性所決定的)。
  • runtime/events 模塊中渲染層事件到 Vue 中事件的轉(zhuǎn)換。
  • runtime/lifecycle 模塊中渲染層與 Vue 生命周期的同步。
  • runtime/render 模塊中對小程序 setData 渲染的支持與優(yōu)化。
  • runtime/node-ops 模塊中對 Vnode 操作的處理。

這里有趣的地方在于 node-ops,和筆者一開始設(shè)想中在此同步渲染層對象的狀態(tài)不同,mpvue 的實現(xiàn)看起來非常容易閱讀……像這樣:

// runtime/node-ops.jsconst 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}復(fù)制代碼

看起來這不是什么都沒有做嗎?個人理解里這和小程序的 API 有更多的關(guān)系:它需要與 .wxml 模板結(jié)合的 API 加大了按照配置 Reconciler 的方法將狀態(tài)管理由 Vue 接管的難度,因而較難通過這個方式直接適配小程序為渲染層,還不如通過一套代碼同時生成 Vue 與小程序的兩棵組件樹并設(shè)法保持其同步來得劃算。

到這里我們已經(jīng)基本介紹了通過添加 platform 支持 Vue 渲染層的基本方式,這個方案的優(yōu)勢很明顯:

  • 它無需在 Vue 組件中使用渲染層 API。
  • 它對 Vue 業(yè)務(wù)組件的侵入相對較少。
  • 它不需要耦合 Vnode 的數(shù)據(jù)結(jié)構(gòu)。
  • 它可以確實地脫離 DOM 環(huán)境。

而在這個方案的問題上,目前最大的困擾應(yīng)該是它必須 fork Vue 源碼了。除了維護(hù)成本以外,如果在基于原生 Vue 的項目中使用了這樣的渲染層,那么就將會存在兩個具有細(xì)微區(qū)別的不同 Vue 環(huán)境,這聽起來似乎有些不清真啊…好在這塊的對外 API 已經(jīng)在 Vue 3.0 的規(guī)劃中了,值得期待 XD

總結(jié)

到此為止,我們已經(jīng)總結(jié)了 React 與 Vue 中定制渲染層的主要方式。重復(fù)一遍:

  • 基于 React 16 Reconciler 的適配方式,簡單直接。
  • 基于 Vue EventBus 的非侵入式適配方式,簡單但對外暴露的細(xì)節(jié)較多。
  • 基于 Vue Mixin 的適配方式,Hack 意味較強。
  • 基于 Vue Platform 定制的適配方式,最為靈活但需要 fork 源碼。

可以看到在目前的時間節(jié)點上,沒有路徑依賴的項目在定制 Canvas / WebGL 渲染層時使用 React 較為簡單。而在 Vue 的方案選擇上,參考尤大在筆者知乎回答里的評論,fork 源碼修改的方式反而是向后兼容性較好的方案。

除了上文中的代碼片段外,筆者編輯本文的過程中也實現(xiàn)了若干渲染適配層的 POC 原型,它們可以在 renderer-adapters-poc 這個倉庫中看到。

倉庫地址:https://github.com/doodlewind/render-adapters-poc

原鏈接:https://juejin.im/post/5bbc99986fb9a05d3c8014b0

總結(jié)

以上是生活随笔為你收集整理的前端vue适配不同的分辨率_浅析 React / Vue 跨端渲染原理与实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。