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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

怎么通过id渲染页面_「快页面」动态配置化页面渲染器原理介绍

發布時間:2025/3/19 编程问答 79 豆豆
生活随笔 收集整理的這篇文章主要介紹了 怎么通过id渲染页面_「快页面」动态配置化页面渲染器原理介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

「快頁面」是知乎內部一個快速搭建后臺管理頁面的平臺,使用者僅用半小時即可將一個常規復雜度的后臺頁面開發完成。

「快頁面」平臺的基石是它的「渲染器」,一個能將 JSON 配置渲染成頁面的 React 組件。

這篇文章將會提供一種配置化渲染器實現思路。

不過在開始介紹原理之前,想先對這類工具的存在價值做一個簡單的分析評估,搞明白我們為什么要做它。

核心目標 - 提升開發效率

一些質疑

一開始產生做「快頁面」這個平臺的想法時,我也在懷疑這樣的東西真的能提高效率嗎? 它所帶來的學習成本難道不會實際上高過它帶來的收益嗎?

其實配置化頁面渲染是一個非常老舊的話題了,因為一般情況下,一個十幾人規模的前端團隊,只要不斷接到大量高度相似的管理后臺需求,內部都會催生出一個這樣的頁面配置化工具。

只是如今社區內也并沒有誕生出一個已經被廣泛使用的類似工具,大多只是作為各公司內部系統內部使用。

在「快頁面」平臺內部上線一年后的今天,我能確定它真的能提高開發效率。

許多項目一期的后臺需求都很簡單,一個表單用于創建和編輯,一個表格用來查詢,然后在表格上加個公開按鈕,這種需求使用快頁面開發,平均每個頁面用半小時,最多一小時就完成上線了。

不過同時無法忽視的一點是,為了抵消它所帶來的學習成本,必然需要做很多文檔,智能編輯器,版本管理等輔助性工作。 這將經歷一個比做出渲染器和專屬組件更為漫長和曲折的過程。

效率提升關鍵點

其實這類工具提升效率的關鍵點各不相同,「快頁面」則是通過以下三點提高效率:

  • 約束需求范圍,約定優于配置
  • 省去構建部署環節,快速上線
  • 非前端參與前端頁面開發成為可能
  • 約束需求范圍,約定優于配置

    把頁面從用代碼表達改為用配置表達,相當于創建了一種 DSL,省去了 import 語句,對效率的提升有限。

    提升效率的關鍵是分析高頻業務需求,簡化成固定流程,限制需求范圍,要放棄支持過于靈活的需求。

    比如通用的表單需求,我們把它拆解成以下幾個部分:

    「請求數據」→「設置初始值」→「指定 POST 地址」→「用戶與表單交互」→「校驗」→「提交」→「成功后跳轉」

    其他細枝末節的比如「提交按鈕放哪」「提交按鈕文案」等低頻需求不考慮。

    一些難以用配置表達的需求,比如「拿到請求數據后先處理下對象結構」「提交的時候發兩個接口」「提交的時候刪除一些字段」等等,它們其實是一種回調函數,變化多端,無窮無盡,除非是高頻需求,否則盡量放棄支持。

    我們抽象了高頻需求中的公共部分,用一目了然的配置表達,放棄了靈活性,得到了效率的提升。 這就是「約定優于配置」。

    省去構建部署環節,快速上線

    我們的配置是以 JSON 的形式存在的,區別于 js 代碼,它的好處在于簡短,可通信,可存儲。

    既然一份 JSON 對應一個頁面,如果把它存到數據庫中,用接口讀取和修改,再做一個在線編輯器,應該就能脫離項目的構建,部署流程,做到開發完成后立刻上線了。

    「快頁面」省去了構建和部署流程,實際上,在本項目中,也就是省去了原本每次代碼合并后所需要的 10 分鐘以上的等待時間,間接省去了 git clone 代碼,安裝依賴,啟動項目等等開發前的必要工作。

    非前端參與前端頁面開發成為可能

    有了在線智能編輯器和文檔,后端也能照著其他頁面的配置樣例,快速開發一個常規復雜度的前端頁面了。

    如果這個在線智能編輯器更強大一些,擺脫了對編輯 JSON 的依賴,轉為可視化交互,它就能成為一個草圖編輯器,從而使得更多的人參與到前端頁面的開發過程中去。

    當后端能借助在線智能編輯器獨立完成前端頁面開發時,這其中的溝通聯調成本也就大大降低了。

    配置樣例

    這是一個簡化的表格查詢需求配置樣例

    {

    如樣例所示,用 component 字段表示要使用的組件,組件嵌套組件形成一份能表達整個頁面內容的 JSON 配置。

    頁面渲染結果

    注意到配置中有些值含有「雙花括號」,如 {{record.id}}: {{record.community_name}}

    這種格式表示它們是動態變化的,讓組件具備隨狀態變化顯示不同 UI 的能力,支持表達式計算。 文章后面會詳細介紹這一功能。

    這個樣例中的組件樹可簡化為下圖(僅顯示有 component 的部分)

    組件樹

    其中 Layout 影響頁面的標題,邊距;

    AutoTable 是強大的表格組件,負責發請求,表格分頁等邏輯;

    Enter 是一個鏈接按鈕;

    MapBadge 常用于顯示各種狀態或類型,在 UI 上比普通文字更醒目一些。

    這份 JSON 很精煉地表達了一個頁面的內容,Layout(頁面布局) AutoTable(表格),Enter 和 MapBadge(表格中的兩列,一列是鏈接,一列是類型),比起原先 JSX 的寫法,代碼量大大減少了。

    渲染流程

    我們可以把渲染流程粗略地分為「React 組件渲染」和「雙花括號表達式渲染」

    React 組件渲染

    配置單元

    仔細觀察配置結構可以發現,嵌套的關鍵是 component,與 component 同級的那些字段將會作為組件的屬性傳入,即

    {

    我們把含有 component 的 Object 叫做一個「配置單元」,就像 React 組件可以自由作為其他組件的任意屬性傳入一樣,「配置單元」之間也可以作為對方的一個屬性形成嵌套。

    那么對每一個配置單元的基本操作就是,調用 React.createElement() 將其實例化為 React Element。

    自底向上

    當我們對一個有兩層嵌套的配置單元嘗試 React.createElement() 時便會發現,我們好像需要確定一個渲染順序。

    這個順序就是自底向上。 以上面的 Layout - AutoTable 為例:

    假設是自頂向下,那就是

    React

    其實 Layout 就是個簡單 UI 組件,沒有任何復雜邏輯,會把外界傳給它的 children 原封不動地傳給 React 的 API,這時毫無疑問會報錯。

    回過頭來看,其實自底向上的順序理是所當然的,因為 JSX 轉譯出來的 JS 代碼本來就是自底向上的,想想「函數執行?!咕兔靼琢?。

    因此渲染順序是: 自底向上。

    深度優先遍歷

    知道了渲染順序,知道了每一層都是在執行 React.createElement(),接下來寫一個深度優先遍歷就行了。 代碼簡化如下:

    function

    常見的遞歸遍歷而已,通過 dfs 收集到一個遵循組件自底向上順序的數組,接下來對其中元素逐個執行 React.createElement() 并替換即可。

    // config 是整個頁面的配置,paths 是深度優先遍歷時收集到的配置單元路徑

    其中 getComponentByName 是根據組件名找到組件的方法,也就是接下來要說的。

    根據組件名找到組件類

    先實現一個組件引用緩存管理器

    // componentProvider

    接著注入所有組件

    // injectComponents.js 文件

    根據組件名取用組件

    import

    都是非常簡單直白的邏輯

    到這里,一個基本的靜態配置渲染流程已經實現了,如果我們的頁面是像寫靜態 HTML 標簽一般沒有任何動態需求,這樣就足夠了。

    但后臺需求不會這么簡單,實際使用后我們會發現,比起寫 JSX,這種 JSON 配置有一個致命的缺陷,那就是數據在被傳給 UI 組件前,我們連對它進行一點點計算都做不到,也沒法寫回調函數。 因此就需要下面這第二部分「雙花括號表達式渲染」」。

    雙花括號表達式渲染

    表達式扮演什么角色

    首先要明白,「雙花括號表達式」在頁面配置中究竟扮演了一個什么樣的角色,我們能在傳統寫 JSX 的過程中,找到與之對應的角色嗎?

    在本項目中,「雙花括號表達式」滿足了

    • 對數據的計算處理的需要
    • 實現部分的回調函數的需要

    對數據的計算處理

    最常見的例子,往往頁面中表單請求的 HTTP 接口地址,需要受頁面當前路由的影響

    比如我們要在

    https://example.xxx.com/projects/:id

    這個頁面中請求

    https://api.xxx.com/projects/:id

    這個接口地址

    很明顯接口地址中的參數 id 是從頁面路由中得到的

    那么寫成「雙花括號表達式」就是

    'https://api.xxx.com/projects/{{match.params.id}}'

    這類計算邏輯很常見,非常重要, 而「雙花括號表達式」就可以滿足這類需求。

    部分的回調函數

    JSON 配置中只能寫數字,字符串,布爾值這些簡單類型,不能寫函數。

    那通過 eval 生成函數行不行呢? 在 JSON 中就以字符串的形式存在。

    這個思路被我們放棄了,因為它過于復雜,過于靈活了。

    我們依然是只針對高頻需求做支持

    不過這意味著我們需要做一些特殊組件,將原本需要傳入回調函數才能實現的邏輯變成僅需一小段 JSON,比如點擊按鈕后彈框填寫表單,或要求用戶確認危險操作等等

    表達式計算的實現

    實現表達式計算靠 eval 生成一個立即執行函數就可以了,這里需注意幾個關鍵點:

    • 屏蔽全局變量
    • eval 生成的函數變量命名空間與全局變量可能有交集
    • 全局變量中可能有變量名并不符合標識符命名規則
    • 打印計算過程中的報錯

    屏蔽全局變量

    這里的全局變量其實指的就是 window 對象上的屬性,由于我們利用了立即執行函數的閉包特性,因此它在執行過程中會受到 window 對象上屬性的影響,導致奇怪的計算結果。

    這種情況一旦發生,不容易發現原因,安全起見還是屏蔽掉的好。

    屏蔽的方式就是循環枚舉出 window 上的屬性,然后執行。

    let

    eval 生成的函數變量命名空間與全局變量可能有交集

    表達式的數據源中可能與全局變量有同名屬性,就不能和上面一樣賦為 undefined 了,舉例:

    // 表達式中系統預先定義了一個 prompt 變量,它和 window.prompt 重名了

    全局變量中可能有變量名并不符合標識符命名規則

    某些第三方庫可能會在 window 上注入它自定義的標識變量,但卻沒有遵循變量命名規則,使用了諸如「減號 -」等特殊符號。

    這種標識符可能會讓屏蔽全局變量的語句報錯,所以記得過濾下。

    打印計算過程中的報錯

    表達式計算是非常有可能失敗的,比如下面這個報錯大家肯定見的太多了。

    TypeError: Cannot read property 'someProp' of undefined

    通過 try catch 捕獲到并打印出來,可以極大地幫助使用者調試。

    計算表達式時的數據來源

    我們的「雙括號表達式」要影響的是 UI,

    而在 React 中,能夠即時影響 UI 的數據只有三種來源,state,props 和 context。

    state

    state 是組件的一些內部屬性,比如表格的分頁,是由表格內部自行管理的。

    props

    props 是我們給組件傳入的屬性,其實就是「配置單元」里寫死的。

    context

    借助一些狀態管理庫,如 redux + react redux,context 就變成了組件的 props。

    這三種數據源只有在組件的 render 方法中可以全部拿到,并且還能隨數據的變化立即影響 UI。

    自底向上的局限

    仍是以上面展示的樣例為例,假設目前 JSON 配置中組件樹的結構有如下三層。

    Layout|-- AutoTable|-- Enter ( href = https://example.xxx.com/resources/{{record.id}} ) // record 是表格任意一行的數據

    表達的意思很簡單,頁面中有個表格,表格中有一列要放個鏈接入口。

    按照自底向上的順序,應當是先執行 createElement(Enter) ,再執行 createElement(AutoTable)

    可我們給 Enter 傳入的 href 屬性是一個「雙花括號表達式」,表達式中依賴的 record 是自身所處表格那一整行的數據,屬于 AutoTable 組件私有的變量。

    我們原先的自底向上流程無視了私有關系,在嘗試計算表達式時發現缺少了一些私有變量。

    這就是原先自底向上的局限,看來,想要支持表達式計算,渲染流程還需要再改進。

    自底向上流程之間的接力

    既然那些變量是私有的,那就應該在遵循私有關系的前提下進行自底向上的渲染。

    怎么遵循呢? 那就是在自底向上的過程中,忽略一些組件的子級配置,由該組件自己負責子級配置的自底向上渲染。

    這樣一來,原本只有一次的自底向上渲染,由于 AutoTable 組件的存在,這個流程被分割成了兩次,好像兩次接力一般。

    我們把那些類似 AutoTable 這種負責接力的組件稱作「接力組件」。

    仍是以上面的 Layout - AutoTable - Enter 為例

    在這個流程中,由于有一個「接力組件」AutoTable 存在,需要兩次自底向上的渲染

    第一次自底向上把 AutoTable 及其所有子級字段視為一整個配置單元,這樣 AutoTable 便成了最底部的那個「配置單元」。

    第二次自底向上由 AutoTable 接力,對其所有子級字段進行自底向上的渲染。

    以此類推。 即使有更多「接力組件」,流程都是一樣的。

    本文最后會有圖片形式的流程詳細介紹。

    接力組件

    哪些組件是接力組件

    主要是那些需要提供私有變量給「雙花括號表達式」的組件,比如表格需要提供表格每一行的數據,表單需要提供表單當前值,等等其他有類似需求的組件。

    渲染器怎樣知道當前組件是不是「接力組件」

    白名單是個辦法,但這樣做的話,每新增一個「接力組件」,都需要更改白名單,渲染器和組件之間存在耦合。

    所以更好的辦法是做個 HOC

    我們把「遍歷計算并替換雙花括號表達式」「自底向上調用 React.createElement」兩個公共邏輯合并成一個方法抽象出來,就叫它 autoRender 吧。

    做一個 HOC,它有兩個功能:

  • 標記被包裝的組件是一個「接力組件」
  • 提供上面提到的 autoRender 方法給被包裝的組件,由被包裝組件使用 autoRender 渲染剩下的配置完成接力
  • 這樣一來,做一個「接力組件」就變得很簡單,只要拿這個 HOC 包裝一下,然后在被包裝的組件中隨自己想法調用 HOC 提供的 autoRender 方法即可。

    渲染器和組件之間實現了解耦。

    形如閉包的表達式變量作用域

    既然「接力組件」擁有一些私有變量,那么符合直覺的作用域應該是:

    父級不能讀取「接力組件」子級的變量,但「接力組件」可以使用父級的變量。

    就像閉包的作用域一樣,當前函數可以使用外層函數的變量,外層函數卻不可以使用當前函數的變量。

    這個的實現也不難, 一句話概括就是: 每個「接力組件」向它子級的所有「接力組件」注入數據。

    所謂注入數據就是給子級的「接力組件」添加一個特定字段,比如 __injectedData

    在本例中,就是要向 AutoTable 這個「接力組件」注入 __injectedData,內容是頁面的路由信息等數據。

    {

    (假定頁面路由中參數 id 為 20) 注入后變為

    {

    之后 AutoTable 使用 autoRender 方法時便會把這份被注入的數據和自身私有的數據合并,來渲染子級配置中的「雙花括號表達式」

    流程圖解

    上面純文字描述很不直觀,下面是一個圖片形式的完整流程。

    在這個例子中,共存在兩個「接力組件」: Page 和 AutoTable

    Page 可以為表達式提供頁面路由數據,包括參數匹配結果,即 match。

    假設頁面路由中存在參數 id,值為 3,即 match.params.id = 3。

    開始

    啟動渲染

    計算表達式,注入數據

    接力組件被視為一個整體

    createElement(AutoTable)

    開始接力

    (AutoRender 筆誤,是 AutoTable)

    Text 組件的 children 屬性的值是一個表達式,表達式中使用了 record 和 match 兩個變量

    record 是表格中每一行的數據,由 AutoTable 提供,假設 record.type = 'typeA'

    match 是頁面路由參數匹配結果,顯然 AutoTable 本身無法提供 match 數據

    但之前 Page 已向 AutoTable 注入了 injectedData,其中含有 match 變量

    因此 Text 組件的 children 屬性表達式可以計算出結果

    計算表達式

    createElement(Text)

    AutoTable 已被實例化,只剩 Layout

    createElement(Layout),流程結束

    總結

    本篇文章介紹了知乎內部一個后臺頁面搭建平臺「快頁面」,主要內容是渲染器的實現原理。

    在介紹原理之前,首先對這類工具的存在意義做了一些初步的分析;

    隨后以一份配置樣例為例,介紹了渲染器的實現原理,包括「React 組件渲染」和「雙花括號表達式渲染」兩部分。

    每一個配置化工具應該都是深度結合了業務方向,項目基礎,團隊投入等實際情況得到的結果。

    因此理論上,在業界,同類工具應該有很多,所以本文也只是一種實現思路。

    歡迎對這類工具感興趣的小伙伴在評論區交流。

    總結

    以上是生活随笔為你收集整理的怎么通过id渲染页面_「快页面」动态配置化页面渲染器原理介绍的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。