欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

我來(lái)聊聊前端應(yīng)用表現(xiàn)層抽象 - 新聞資訊 - 云南小程序開(kāi)發(fā)|云南軟件開(kāi)發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開(kāi)發(fā)/軟件開(kāi)發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X(jué)表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷(xiāo)的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷(xiāo)工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁(yè) » 新聞資訊 » 小程序相關(guān) >

我來(lái)聊聊前端應(yīng)用表現(xiàn)層抽象

發(fā)表時(shí)間:2021-1-6

發(fā)布人:葵宇科技

瀏覽次數(shù):56

我們處于變化很快的時(shí)代,無(wú)論是商業(yè)還是科技。一家公司看上去商業(yè)很成功,也許前腳剛上市,后腳就因?yàn)槭裁炊耸?,甚至倒閉;一項(xiàng)看似高大上的技術(shù)橫空出世,各類(lèi)媒體爭(zhēng)先恐后地撰文介紹,熱度炒得老高,沒(méi)準(zhǔn)沒(méi)多久就出現(xiàn)了競(jìng)爭(zhēng)者、替代者。

在這樣的大環(huán)境下,傳統(tǒng)的「web 前端開(kāi)發(fā)」演變成了「泛客戶(hù)端開(kāi)發(fā)」,前端開(kāi)發(fā)者從「配置工程師」被「逼」成了「軟件工程師」。開(kāi)發(fā)變得更復(fù)雜了,要處理的問(wèn)題更多了,從業(yè)難度不知提升了多少倍——前端早就不再簡(jiǎn)單。

在眾多必須要處理的問(wèn)題中的一個(gè),就是表現(xiàn)層運(yùn)行環(huán)境的兼容問(wèn)題,像跨瀏覽器和跨端、平臺(tái)、技術(shù)棧。注意,這里說(shuō)的是「表現(xiàn)層」而不是「視圖層」。

「表現(xiàn)層」與「視圖層」

「表現(xiàn)層」的英文是「presentation tier」或「presentation layer」,具體是哪個(gè)取決于是物理上還是邏輯上劃分;而「視圖層」的英文是「view」?!副憩F(xiàn)層」是「視圖層」的超集,根據(jù)前端應(yīng)用的架構(gòu)設(shè)計(jì),它們既可以不等又可以相等。

表現(xiàn)層

「表現(xiàn)層」這個(gè)詞出自經(jīng)典的三層架構(gòu)(或多層架構(gòu)),是其中一個(gè)分層。三層架構(gòu)包括數(shù)據(jù)層、邏輯層和表現(xiàn)層,一般用在 C/S 架構(gòu)中。

三層架構(gòu)

為什么會(huì)在這篇講前端開(kāi)發(fā)的文章中提到它?這是因?yàn)?,雖然在一些前端應(yīng)用中用不到,尤其是快餐式應(yīng)用,但在企業(yè)級(jí)復(fù)雜前端應(yīng)用中就十分需要一個(gè)前端的「三層架構(gòu)」。

視圖層

「視圖層」則來(lái)自表現(xiàn)層常用的「model-view-whatever」模式中的「view」,即「視圖」。至于說(shuō)的時(shí)候在「視圖」后面加個(gè)「層」字合不合適,就不在這里討論了,文中皆使用「視圖層」這個(gè)詞。

運(yùn)行環(huán)境兼容

跨瀏覽器

由于各瀏覽器廠商對(duì)標(biāo)準(zhǔn)實(shí)現(xiàn)的不一致以及瀏覽器的版本等原因,會(huì)導(dǎo)致特性支持不同、界面顯示 bug 等問(wèn)題的出現(xiàn)。但慶幸的是,他們基本是按照標(biāo)準(zhǔn)來(lái)的,所以在開(kāi)發(fā)時(shí)源碼的語(yǔ)法幾乎沒(méi)什么不同。

所謂的「跨瀏覽器」實(shí)際上就是利用瀏覽器額外的私有特性和技術(shù)或輔以 JS 對(duì)瀏覽器的 bug 進(jìn)行「修正」與功能支持。

跨端、平臺(tái)、技術(shù)棧

現(xiàn)在,絕大部分的前端開(kāi)發(fā)者是在做泛客戶(hù)端開(kāi)發(fā)——開(kāi)發(fā) web 應(yīng)用、客戶(hù)端應(yīng)用和各類(lèi)小程序。

在做 web 應(yīng)用時(shí)需要考慮 PC 端和移動(dòng)端是分開(kāi)還是適配?技術(shù)選型是用 React、Vue?還是用 Web Components?或是用其他的?做客戶(hù)端應(yīng)用、各類(lèi)小程序時(shí)這些也會(huì)面臨技術(shù)選型的問(wèn)題。

如果公司某個(gè)業(yè)務(wù)的功能覆蓋了上述所有場(chǎng)景,該如何去支撐?與跨瀏覽器不同的是,不同端、平臺(tái)、技術(shù)棧的源碼語(yǔ)法不一樣,要滿(mǎn)足業(yè)務(wù)需求就得各開(kāi)發(fā)一遍。然而,這顯然成本過(guò)高,并且風(fēng)險(xiǎn)也有些大。

那么,要怎么解決這個(gè)問(wèn)題呢?從源頭出發(fā)。根本的源頭是業(yè)務(wù)場(chǎng)景,然后是產(chǎn)品設(shè)計(jì),但這些都不是開(kāi)發(fā)人員可掌控的,幾乎無(wú)法改變。能夠完全被開(kāi)發(fā)人員所左右的基本只有開(kāi)發(fā)階段的事情,那就從這個(gè)階段的源頭入手——源碼編寫(xiě)。

若是與業(yè)務(wù)相關(guān)的代碼只需編寫(xiě)一次就能運(yùn)行在不同的端、平臺(tái)、技術(shù)棧上,那真是太棒了!這將會(huì)大大地降低成本并減少風(fēng)險(xiǎn)!

表現(xiàn)層的抽象

為了達(dá)到跨端、平臺(tái)、技術(shù)棧的目的,需要將表現(xiàn)層再劃分為抽象層、運(yùn)行層和適配層。其中,抽象層是為了統(tǒng)一源碼的編寫(xiě)方式,可以是 DSL、配置等,它是一種協(xié)議或約定;運(yùn)行層就是需要被「跨」的端、平臺(tái)、技術(shù)棧;適配層則是將抽象層的產(chǎn)物轉(zhuǎn)換為運(yùn)行層正常運(yùn)行所需要的形式。

表現(xiàn)層中可以被抽象的大概有視圖結(jié)構(gòu)、組件外觀、組件行為等。

視圖結(jié)構(gòu)

在 web 前端開(kāi)發(fā)中,HTML 就是一種視圖結(jié)構(gòu)的抽象,描述了界面中都有什么,以及它們之間的層級(jí)關(guān)系。最終的顯示需要瀏覽器解析 HTML 后調(diào)用操作系統(tǒng)的 GUI 工具庫(kù)。

對(duì)于業(yè)務(wù)支撐來(lái)說(shuō),無(wú)論是 HTML 還是其他什么拼湊界面的方式,相對(duì)來(lái)說(shuō)比較低級(jí)(是「low level」而不是「low」),視圖單元的劃分粒度比較細(xì),在開(kāi)發(fā)界面時(shí)就會(huì)花費(fèi)更多的時(shí)間。

我們需要一種能夠屏蔽一些不必關(guān)注的細(xì)節(jié)的視圖結(jié)構(gòu)抽象,在這個(gè)抽象中,每個(gè)視圖單元都有著其在業(yè)務(wù)上的意義,而不是有沒(méi)有都可以的角色。具體做法請(qǐng)看下文。

組件外觀

大部分已存在的組件的視覺(jué)呈現(xiàn)是固定的,即某個(gè)組件的尺寸、形狀、顏色、字體等無(wú)法被定制。如果同樣的交互只是因?yàn)橐曈X(jué)上有所差異就要重新寫(xiě)組件,或者在組件外部重新寫(xiě)份樣式進(jìn)行覆蓋,那未免也太痛苦了……

我們可以將那些希望能夠被定制的視覺(jué)呈現(xiàn)抽象成「主題」的一部分,這部分可以被叫做「皮膚」。在進(jìn)行定制時(shí),分為線下和線上兩種方式。

「線下」是指在應(yīng)用部署前的開(kāi)發(fā)階段進(jìn)行處理。在前端構(gòu)建工具豐富的現(xiàn)在,寫(xiě)頁(yè)面樣式時(shí)已經(jīng)不會(huì)去直接寫(xiě) CSS,而是像 Sass 這種可編程式的預(yù)處理器。這樣就可以抽取出一些控制視覺(jué)呈現(xiàn)的 Sass 變量,需要定制時(shí)通過(guò)在外部對(duì)變量賦值進(jìn)行覆蓋,而不需要費(fèi)勁重寫(xiě)組件或樣式。

「線上」則是部署后根據(jù)運(yùn)行時(shí)數(shù)據(jù)動(dòng)態(tài)改變。在皮膚定制即時(shí)預(yù)覽和低代碼平臺(tái)等場(chǎng)景,是基本沒(méi)機(jī)會(huì)去修改 Sass 變量并走一遍構(gòu)建流程的,即使技術(shù)上能夠辦到。借助 CSS 自定義屬性(CSS 變量)的力量可以較為方便地做到視覺(jué)呈現(xiàn)的運(yùn)行時(shí)變更。

組件行為

組件除了外觀,其行為也應(yīng)當(dāng)是可以定制的??吹健感袨椤惯@個(gè)詞,第一反應(yīng)就是跟用戶(hù)操作相關(guān)的事情,然而這里還包括與組件內(nèi)部結(jié)構(gòu)相關(guān)的。

對(duì)于組件的外部來(lái)說(shuō),組件內(nèi)部就是個(gè)黑盒子,其自身結(jié)構(gòu)的組成部分有的可以被上文所說(shuō)的視圖結(jié)構(gòu)所控制,有的則無(wú)能為力:

搜索組件

上圖是一個(gè)比較復(fù)雜的搜索組件,雖然外觀和布局看起來(lái)有所不同,但「它們」確實(shí)是同一個(gè)組件。外觀不同的解決方案上面已經(jīng)大體說(shuō)明,這類(lèi)視圖結(jié)構(gòu)無(wú)法控制的布局問(wèn)題,需要枚舉場(chǎng)景后在組件內(nèi)進(jìn)行支持,然后作為「主題」的一部分存在。

跟用戶(hù)操作相關(guān)的行為有組件自身的交互規(guī)則及與業(yè)務(wù)邏輯的結(jié)合這兩類(lèi)。

交互規(guī)則又有兩種:一種是像表單是在字段值發(fā)生改變時(shí)就校驗(yàn)還是在點(diǎn)擊按鈕時(shí)校驗(yàn)這樣;另一種是像字段值是在輸入框的值改變( input 事件)時(shí)更新還是失焦( change 事件)時(shí)更新這樣,或是像下拉菜單的彈出層是在懸停( hover 事件)時(shí)出現(xiàn)還是點(diǎn)擊( click 事件)時(shí)出現(xiàn)這樣。

前者的解決方式與上面說(shuō)的視圖結(jié)構(gòu)無(wú)法控制的布局問(wèn)題差不多,后者則是需要組件支持事件映射,即外部可以指定組件某些交互的觸發(fā)事件。當(dāng)然,這兩者同樣也可以作為「主題」的一部分。

我們?cè)趯?xiě)組件時(shí)有件事是需要極力避免卻往往難以避免——組件中耦合業(yè)務(wù)邏輯。組件決定的應(yīng)該只是外貌與交互形態(tài),里面只有交互邏輯及控制展現(xiàn)的狀態(tài),不應(yīng)該牽扯到任何具體業(yè)務(wù)相關(guān)的邏輯。只要長(zhǎng)得一樣、操作一樣,那么就應(yīng)該是同一個(gè)組件,具體業(yè)務(wù)相關(guān)的邏輯注入進(jìn)去。

這段十分「?jìng)€(gè)性化」的業(yè)務(wù)邏輯,說(shuō)白了就是響應(yīng)用戶(hù)操作的變化以及業(yè)務(wù)數(shù)據(jù)的變化去更改組件內(nèi)部的狀態(tài):

{
  // 組件事件
  events: {
    // 組件的一個(gè)點(diǎn)擊事件
    'click-a': function() {},
    // 組件的另一個(gè)點(diǎn)擊事件
    'click-b': function() {},
    // 組件的一個(gè)改變事件
    'change-c': function() {},
  },
  // 業(yè)務(wù)數(shù)據(jù)變化的回調(diào)
  watch: function( contextValue ) {},
}

運(yùn)行時(shí)會(huì)注入一個(gè)上下文給上述對(duì)象方法的 this ,組件還可以添加工具方法給上下文。該上下文的內(nèi)置屬性與方法有:

interface IDomainSpecificComponentContext {
  getState(key: string): any;
  setState(key: string, value: any): void;
  setState(stateMap: { [key: string]: any }): void;
}

視圖結(jié)構(gòu)描述

上面說(shuō)了我們需要一種比 HTML 之類(lèi)的更進(jìn)一步的視圖結(jié)構(gòu)抽象,下面就來(lái)說(shuō)說(shuō)這部分的大體思路。

技術(shù)選型

在做視圖結(jié)構(gòu)抽象時(shí)最常用到的技術(shù)就是 XML-based 或 XML-like 以及 JSON-based 的某種技術(shù)。XML-base 和 XML-like 的技術(shù)都是符合 XML 語(yǔ)法的,唯一的區(qū)別是前者要完全符合 XML 的標(biāo)準(zhǔn)規(guī)范,像 Angular 和 Vue 的模板就是后者;同樣的,JSON-based 的技術(shù)是完全符合 JSON 的標(biāo)準(zhǔn)規(guī)范的技術(shù),像 JSON Schema。

自從 React 問(wèn)世以來(lái),其帶來(lái)的 XML-like 的 JSX 也會(huì)被用于視圖結(jié)構(gòu)抽象,但基本僅限于編輯時(shí)(edit time)。一段 JSX 代碼并不是純聲明式的,作為視圖結(jié)構(gòu)描述來(lái)說(shuō)可讀性較低,解析難度較高,并且通用性很低。

JSON-based 的技術(shù)對(duì)前端運(yùn)行時(shí)最為友好,解析成本幾乎為零;相反的,其可讀性很低,JSON 結(jié)構(gòu)是縱向增長(zhǎng)的,指定區(qū)域內(nèi)的表達(dá)力十分受限,無(wú)法很直觀地看出層級(jí)關(guān)系與視圖單元的屬性:

{
  "tag": "view",
  "attrs": {
    "widget": "form"
  },
  "children": [{
    "tag": "group",
    "attrs": {
      "title": "基本信息",
      "widget": "fieldset",
    },
    "children": [{
      "tag": "field",
      "attrs": {
        "name": "name",
        "label": "姓名",
        "widget": "input"
      }
    }, {
      "tag": "field",
      "attrs": {
        "name": "gender",
        "label": "性別",
        "widget": "radio"
      }
    }, {
      "tag": "field",
      "attrs": {
        "name": "age",
        "label": "年齡",
        "widget": "number"
      }
    }, {
      "tag": "field",
      "attrs": {
        "name": "birthday",
        "label": "生日",
        "widget": "date-picker"
      }
    }]
  }, {
    "tag": "group",
    "attrs": {
      "title": "寵物",
      "widget": "fieldset",
    },
    "children": [{
      "tag": "field",
      "attrs": {
        "name": "dogs",
        "label": ":dog:",
        "widget": "select"
      }
    }, {
      "tag": "field",
      "attrs": {
        "name": "cats",
        "label": ":cat:",
        "widget": "select"
      }
    }]
  }]
}

如果一個(gè)應(yīng)用的設(shè)計(jì)是不需要人工寫(xiě)視圖結(jié)構(gòu)描述的話(huà),可以考慮使用 JSON-based 的技術(shù)。

像 Angular 和 Vue 的模板那種 XML-like 的技術(shù)是相對(duì)來(lái)說(shuō)最適合做視圖結(jié)構(gòu)描述的——純聲明式,結(jié)構(gòu)是向水平與垂直兩個(gè)方向增長(zhǎng),無(wú)論是可讀性還是表達(dá)力都更強(qiáng),解析難度適中,并且具備通用性。

下面的模板代碼所描述的內(nèi)容與上面那段 JSON 代碼一模一樣,深呼吸,好好感受一下兩者之間的差異:

<view widget="form">
  <group title="基本信息" widget="fieldset">
    <field name="name" label="姓名" widget="input" />
    <field name="gender" label="性別" widget="radio" />
    <field name="age" label="年齡" widget="number" />
    <field name="birthday" label="生日" widget="date-picker" />
  </group>
  <group title="寵物" widget="fieldset">
    <field name="dogs" label=":dog:" widget="select" />
    <field name="cats" label=":cat:" widget="select" />
  </group>
</view>

至此,視圖結(jié)構(gòu)描述最終該選用哪種技術(shù),想必?zé)o須多言。

雞哥(小雞)

設(shè)計(jì)思路

毋庸置疑,模板的語(yǔ)法要符合 XML 語(yǔ)法是前提,再在此基礎(chǔ)上根據(jù)需求進(jìn)行定制、擴(kuò)展。首先要定義標(biāo)簽集。所謂的「標(biāo)簽集」就是一個(gè)元素庫(kù),其中的每個(gè)元素都要具備一定語(yǔ)義,使其在業(yè)務(wù)上有存在意義。然后是制定描述元素的 schema 并實(shí)現(xiàn)其對(duì)應(yīng)的解析、校驗(yàn)等邏輯。

元素 schema 大概是長(zhǎng)這樣:

// 屬性值類(lèi)型
type PropType = 'boolean' | 'number' | 'string' | 'regexp' | 'json';

// 屬性描述符
type PropDescriptor = {
  type: PropType | PropType[];
  required: boolean; // 是否必需
};

// 元素 schema
type ElementSchema = {
  name: string; // 元素名
  tag?: string; // 標(biāo)簽名,不指定時(shí)取元素名
  props?: {
    [key: string]: PropDescriptor;
  };
  attrs?: {
    resolve: (key: string, val: any) => any;
  };
  // 節(jié)點(diǎn)行為,是作為父節(jié)點(diǎn)的子節(jié)點(diǎn)還是屬性存在
  behavior?: {
    type: 'append' | 'attach';
    // 以下都用于 `type` 是 `'attach'` 時(shí)
    host?: string; // 宿主(屬性名)
    keyed?: boolean; // 是否為鍵值對(duì)集合,值為 `true` 且 `merge` 為 `false` 時(shí)以節(jié)點(diǎn) ID 為鍵
    merge?: boolean; // 當(dāng)值為 `true` 時(shí)將 `reduce` 的返回值與 `host` 指定的屬性的值進(jìn)行合并后重新賦值給 `host`
    reduce?: (node: ITemplateNode) => any; // 轉(zhuǎn)換節(jié)點(diǎn)信息
    restore?: (reduced: any, node?: ITemplateNode) => ITemplateNode | Partial<ITemplateNode>;
  };
};

可以看到 schema 中有 propsattrs ,它們共同組成了模板元素的屬性(XML attributes),區(qū)別是:模板解析后的屬性如果是在 props 中定義的并且滿(mǎn)足屬性描述符的 typerequired 所指定的限制條件,會(huì)成為模板節(jié)點(diǎn)的 props 屬性;剩余沒(méi)在 props 中定義的則成為模板節(jié)點(diǎn)的 attrs 屬性,通過(guò) resolve 方法能夠?qū)傩愿鶕?jù)自己的規(guī)則進(jìn)行值的轉(zhuǎn)換。

雖然在模板中元素總是以嵌套的形式展示出層級(jí)關(guān)系,但一個(gè)元素并不一定就是其父級(jí)的結(jié)構(gòu),還可能是配置。因此,元素 schema 中的 behavior 用于設(shè)置當(dāng)前元素在模板解析后是作為一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)存在還是作為某個(gè)屬性存在。

上述的模板設(shè)計(jì)是純視圖結(jié)構(gòu)描述的,并且只對(duì)元素這種「塊」進(jìn)行處理,我認(rèn)為這樣夠用了。根據(jù)情況,可以擴(kuò)展為像 Angular 和 Vue 的模板那樣支持文本、插值和指令等。

如果懶癌發(fā)作并且沒(méi)什么特殊需求,模板解析的工作可以交給魔改后的 Vue 2.6 編譯器,再適配為模板節(jié)點(diǎn)樹(shù)。

每個(gè)模板節(jié)點(diǎn)的結(jié)構(gòu)大致為:

interface ITemplateNode {
  id: string;
  name: string;
  tag: string;
  props: {
    [key: string]: any;
  };
  attrs: {
    [key: string]: any;
  };
  parent: ITemplateNode | null;
  children: ITemplateNode[];
}

最后,通過(guò)適配層將模板節(jié)點(diǎn)樹(shù)轉(zhuǎn)為運(yùn)行層的組件樹(shù),并把渲染的控制權(quán)也轉(zhuǎn)交給了最終的運(yùn)行環(huán)境。

總結(jié)

在一個(gè)復(fù)雜的前端應(yīng)用中,如果不對(duì)其進(jìn)行分層,那它的擴(kuò)展性和可維護(hù)性等真的會(huì)不忍直視……通常是采用經(jīng)典的三層架構(gòu),從下到上分別為數(shù)據(jù)層、邏輯層和表現(xiàn)層。本文以表現(xiàn)層為例,將其再次劃分出抽象層、運(yùn)行層和適配層這三層,實(shí)際上數(shù)據(jù)層和邏輯層也可以套用這種模式——就像在生日蛋糕上切上四刀——我稱(chēng)其為「九宮格」模型。

「九宮格」模型

在表現(xiàn)層的各種抽象中,本文著重闡述了視圖結(jié)構(gòu)描述的技術(shù)選型與設(shè)計(jì)思路,可以看出 XML-like 的模板從編寫(xiě)到解析再到渲染這一整條流程,與 Angular 和 Vue 的模板及 HTML 大體上一致;其他抽象只是稍微提了提,以后有機(jī)會(huì)再展開(kāi)來(lái)說(shuō)。

之前也寫(xiě)過(guò)幾篇與模板相關(guān)的文章:從提效角度與「面向組件」做對(duì)比的《 我來(lái)聊聊面向模板的前端開(kāi)發(fā) 》;從可定制性角度講的《 我來(lái)聊聊配置驅(qū)動(dòng)的視圖開(kāi)發(fā) 》;從低代碼平臺(tái)的核心理念「模型驅(qū)動(dòng)」出發(fā)的《 我來(lái)聊聊模型驅(qū)動(dòng)的前端開(kāi)發(fā) 》??梢哉f(shuō),本文的內(nèi)容是它們有關(guān)表現(xiàn)層描述的「根基」。

無(wú)論一家公司是不是做低代碼平臺(tái)的,或者內(nèi)部有沒(méi)有低代碼平臺(tái),都應(yīng)該從表現(xiàn)層抽象出視圖結(jié)構(gòu)描述,至少要有如此意識(shí)。

相關(guān)案例查看更多