聲明式編程風(fēng)格的視圖文檔解決方案

背景

很多渲染引擎或者可視化編輯器都會將視圖保存成文件落地裙椭,很多都會選xml作為數(shù)據(jù)格式來存儲躏哩,少數(shù)會使用json,然而筆者之前做的可視化編輯器揉燃,就是用json作為數(shù)據(jù)格式的扫尺,方便快捷,隨取隨存炊汤,甚是快活正驻,直到。婿崭。拨拓。

json vs xml(盜圖)

直到脫離編輯器時,想手寫視圖文檔的時候氓栈,才發(fā)現(xiàn)是那么的痛苦渣磷!而且,隨著object和array的嵌套多起來授瘦,才是真的噩夢醋界!后來沉思了許久,要不試試xml吧提完,以前在用白鷺的exml的時候感覺還行形纺,幾乎可以很直觀地在腦中映射出視圖來。
對于json和xml的對比徒欣,有人已經(jīng)給出了詳盡的結(jié)論:http://www.reibang.com/p/1ff2bc9161f1
于是就增加了xml的文檔解釋器逐样,然而痛苦確實減輕了許多,但是打肝,使用xml并沒有結(jié)合筆者的引擎的特殊性來考慮(筆者的引擎是基于E/C架構(gòu)的實現(xiàn))脂新,一個視圖節(jié)點可以劃分成4個部分:類名、節(jié)點屬性粗梭、子節(jié)點争便、組件組。如果用xml的話断医,實現(xiàn)是可以實現(xiàn)的滞乙,只不過需要做很多特殊處理,比如組件組就會和子節(jié)點處于同一層鉴嗤,需要通過判斷tagName來特殊處理斩启;再比如節(jié)點屬性里如果有object,那么就需要xml的子節(jié)點來存放醉锅,就又和子節(jié)點處于同一層了兔簇,需要通過首字母大小寫來特殊處理。
上述的都是使用json和xml的痛點,直到想起同事曾今跟筆者說過的聲明式編程男韧。

聲明式編程

那么啥是聲明式編程,是否和函數(shù)式編程差不多的名詞呢默垄?
筆者經(jīng)過查閱也是漸漸明白此虑,現(xiàn)行的編程方式大概就3種:命令式編程、函數(shù)式編程和聲明式編程口锭。前兩個就不多贅述了朦前,搜索一下很多很多,唯獨聲明式編程鹃操,筆者才知道不久韭寸。
這樣來理解:越接近自然語言,聲明式編程的比重就越高荆隘,否則就是命令式編程的占比更高恩伺。
如果你還沒理解,那么還是上個大開發(fā)論壇看看吧椰拒。

swiftUI

蘋果新出的swift語言晶渠,就可以很自然地使用聲明式編程,各種鏈式編程和流暢的語法燃观,很似自然語言褒脯,所以官方給出了swiftUI的支持。
筆者也沒用過缆毁,也不能多加描述番川,若是有興趣可以去看看。https://developer.apple.com/xcode/swiftui/

簡單實現(xiàn)

筆者是從事HTML5游戲開發(fā)的脊框,自然會使用js颁督,所以使用js來實現(xiàn)了相對可讀性可編輯性更好的聲明式編程實現(xiàn)。
看個示例:

const {Doc} = require('qunity');
const {Node, Rect, Text, StarBezier} = require('qunity-pixi');

return Doc({type: 'scene', name: 'main'}).kv({
    root: Node({name: 'Scenes'}).c([
        Node({name: 'MainScene', active: true}).c([
            Rect({name: 'bg', shapeWidth: 750, shapeHeight: 1334, fillColor: 0xcc202e,}),
            Node({name: 'slogan', y: 200, angle: "-3"}).c([
                Text({
                    name: 'title1',
                    x: 150,
                    text: 'LEAP',
                    style: {fill: 0xFFFFFF, fontSize: 160, fontFamily: 'Arial Black'}
                }),
                Text({
                    name: 'title1',
                    x: 150,
                    y: 140,
                    text: 'ON!',
                    style: {fill: 0xFFFFFF, fontSize: 220, fontFamily: 'Arial Black'}
                }),
            ]),
            Node({name: 'InfoBoard', x: -100, y: 600, angle: -3,}).c([
                Rect({name: 'bg', y: 70, shapeWidth: 1000, shapeHeight: 300, fillColor: 0x000, alpha: 0.7}),
                Text({
                    name: 'title',
                    x: 375,
                    text: "最新數(shù)據(jù)",
                    fontWeight: 'bold',
                    alpha: 0.7,
                    style: {fill: 0x000, fontSize: 60, fontFamily: '方正粗圓_GBK'}
                }),
            ]).s([
                {script: "/scripts/InfoBoard"}
            ]),
        ]),
    ]),
    asset: [
        {
            path: "images/1.png",
            uuid: "dd22921b-e7ff-4fbc-9e79-ebfe517b9943",
            url: "assets/images/1.png",
        }
    ],
})
模仿Leap-on游戲的首頁

如上缚陷,就是視圖文檔解釋出來的視圖适篙。

拆分

聲明式編程什么?鏈式編程箫爷、相對豐富的方法庫(可擴展)嚷节,還有一個簡易的解釋器。

文檔結(jié)構(gòu)設(shè)計

看示例虎锚,導(dǎo)入幾個方法硫痰,然后return整個文檔實例,就是這么簡單窜护。

方法導(dǎo)入

筆者使用了commonjs的風(fēng)格效斑,用require來導(dǎo)入方法。因為筆者的引擎是通過分模塊的柱徙,所以做了兩個導(dǎo)入來源缓屠。其中qunity只提供了Doc方法奇昙,用于創(chuàng)建并返回文檔實例,qunity-pixi則提供了視圖節(jié)點的創(chuàng)建方法敌完,用于創(chuàng)建各種視圖節(jié)點實例储耐。(關(guān)于qunity這個庫,筆者這里就不打廣告了滨溉,之后會開源什湘,一個基于EC架構(gòu)實現(xiàn)的HTML5游戲引擎,一筆帶過)

文檔導(dǎo)出

文檔導(dǎo)出并沒有使用export晦攒,而是一個return闽撤,原因下面會講到。

解釋器

解釋器比較簡單脯颜,主要是提供上述要導(dǎo)入的方法和接受返回的文檔實例哟旗。
如果你熟悉commonjs工作原理,那可以跳過

Doc方法

let func = new Function('require', docSource);
let doc = func(requireMethod);
return doc;

很簡單伐脖,通過一個Function來包裹執(zhí)行(這就是為什么在視圖文檔里是直接用return來返回整個文檔實例热幔,如果用commonjs的風(fēng)格,則需要提供module參數(shù)用來接收返回值)讼庇,避免使用eval而外部入侵绎巨。傳入一個require參數(shù),而這個require方法則會接受一個id的參數(shù)蠕啄。

function requireMethod(id) {
    return requireContext[id];
}

id可能會是qunityqunity-pixi场勤,然后根據(jù)id返回內(nèi)容。
那么requireContext是一個怎么樣的結(jié)構(gòu)呢歼跟?

const requireContext = {
    'qunity': {
        Doc: function (props) {
            let obj = {
                kv,
                p,
            };
            setTimeout(function () {
                delete obj['kv'];
                delete obj['p'];
            });
            return obj.p(props);
        }
    },
    'qunity-pixi': pixiNodes,
};

看到了嗎和媳,就是一個map,分別是qunityqunity-pixi的內(nèi)容哈街。
qunity里有一個Doc的方法留瞳,實例化一個object,然后存放兩個方法骚秦,kvp她倘,最后執(zhí)行obj.p(props)并返回obj
那么kvp這兩個方法是什么呢作箍?直接上代碼:

function kv(props) {
    for (let key in props) {
        this[key] = props[key];
    }
    return this;
}
function p(props) {
    injectProps(app, this, props);

    if (props.active !== false && this.setActive) {
        this.setActive(true);
    }

    return this;
}

代碼比較簡單硬梁,kv是做鍵值對賦值的,p是深度賦值用的(耦合度較高)胞得,因為筆者的引擎需要荧止,Doc方法只給出了這兩個方法。
至此,Doc方法完成了跃巡,在視圖文檔中只要調(diào)用Doc(...).kv(...).p(...)可以生成一個文檔的實例了危号。

節(jié)點方法

節(jié)點方法全部由qunity-pixi導(dǎo)出。

const entityNames = Object.keys(app.entityDefs);
for (let entityName of entityNames) {
    pixiNodes[entityName] = function (props) {
        let entity = app.createEntity(entityName);
        entity['kv'] = kv;
        entity['p'] = p;
        entity['c'] = c;
        entity['s'] = s;
        setTimeout(function () {
            delete entity['kv'];
            delete entity['p'];
            delete entity['c'];
            delete entity['s'];
        });
        return p.call(entity, props);
    }
}

筆者的引擎里注冊的節(jié)點的定義全部在app.entityDefs上素邪,然后遍歷存放到pixiNodes上葱色,每個節(jié)點都有相同的鏈式調(diào)用方法,其中c和s方法看下面代碼:

function c(children) {
    for (let child of children) {
        app.addDisplayNode(child, this);
    }
    return this;
}

function s(components) {
    Object.defineProperty(this, '$componentConfigs', {
        value: components,
        writable: false,
        enumerable: false,
    });
    return this;
}

c方法是用來添加視圖子節(jié)點的娘香,s方法是用來存放組件的配置信息的。
為了能達到鏈式編程的目的办龄,每個方法都需要返回自身烘绽。
為了不污染原本的節(jié)點屬性,需要把存放在節(jié)點上的kv/p/c/s之類的方法都刪除掉俐填,但是也不能使用就刪除安接,所以筆者想到的時候用setTimeout,在回調(diào)里將他們?nèi)縿h除英融。

小結(jié)

解釋器主要是為視圖文檔的執(zhí)行提供上下文盏檐,并能實行視圖文檔和接受視圖文檔。

其他顧慮

編輯器schema

筆者在手寫視圖文檔的時候驶悟,發(fā)現(xiàn)智能提示功能并沒效果胡野,這是肯定的,因為并沒有對這些上下文方法給出定義痕鳍。
答案是直接使用typescript的聲明文件來做硫豆,自己寫d.ts文件或者用工具生成上下文的d.ts,這樣笼呆,就能有很好的智能提示了熊响。(注:目前idea的支持較好)


智能提示

這樣就可以很舒服地使用智能提示來手寫視圖文檔了。

設(shè)計時映射到視圖節(jié)點樹

這個是什么應(yīng)用場景呢诗赌?就是編輯器一般都會有一個視圖節(jié)點樹的組件來展示層級關(guān)系汗茄,那怎么在設(shè)計時從視圖文檔映射出這個視圖節(jié)點樹呢?
筆者想到的就是ast铭若,用抽象語法樹來處理洪碳,使用esprima之類的庫來靜態(tài)生成ast,然后分析ast奥喻,得到最終的視圖節(jié)點樹偶宫。

其他顧慮暫未想到。环鲤。纯趋。

總結(jié)

目前,簡單版本的解釋器還在開發(fā)和使用中,會不斷迭代更新吵冒,解耦纯命,達到最終的方便快捷直觀。
聲明式編程對于視圖節(jié)點文檔實在是太友好了痹栖,但也不值局限于次亿汞,聲明式編程可以用于更多的DSL中,結(jié)果必定超預(yù)期揪阿!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疗我,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子南捂,更是在濱河造成了極大的恐慌吴裤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溺健,死亡現(xiàn)場離奇詭異麦牺,居然都是意外死亡,警方通過查閱死者的電腦和手機鞭缭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門剖膳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岭辣,你說我怎么就攤上這事吱晒。” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涯捻。 經(jīng)常有香客問我皇忿,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上矗烛,老公的妹妹穿的比我還像新娘。我一直安慰自己箩溃,他們只是感情好瞭吃,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涣旨,像睡著了一般歪架。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霹陡,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天和蚪,我揣著相機與錄音止状,去河邊找鬼。 笑死攒霹,一個胖子當(dāng)著我的面吹牛怯疤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播催束,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼集峦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抠刺?” 一聲冷哼從身側(cè)響起塔淤,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎速妖,沒想到半個月后凯沪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡买优,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挺举。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杀赢。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖湘纵,靈堂內(nèi)的尸體忽然破棺而出脂崔,到底是詐尸還是另有隱情,我是刑警寧澤梧喷,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布砌左,位于F島的核電站,受9級特大地震影響铺敌,放射性物質(zhì)發(fā)生泄漏汇歹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一偿凭、第九天 我趴在偏房一處隱蔽的房頂上張望产弹。 院中可真熱鬧,春花似錦弯囊、人聲如沸痰哨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斤斧。三九已至,卻和暖如春霎烙,著一層夾襖步出監(jiān)牢的瞬間撬讽,已是汗流浹背蕊连。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锐秦,地道東北人咪奖。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像酱床,于是被迫代替她去往敵國和親羊赵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容