我來(lái)聊聊前端應用表現層抽象 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

我來(lái)聊聊前端應用表現層抽象

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

發布人(rén):融晨科技

浏覽次數:56

我們處于(yú)變化很快的(de)時(shí)代,無論是(shì)商業還是(shì)科技。一家公司看上(shàng)去商業很成功,也(yě)許前腳剛上(shàng)市,後腳就(jiù)因爲(wéi / wèi)什麽而(ér)退市,甚至倒閉;一項看似高大(dà)上(shàng)的(de)技術橫空出(chū)世,各類媒體争先恐後地(dì / de)撰文介紹,熱度炒得老高,沒準沒多久就(jiù)出(chū)現了(le/liǎo)競争者、替代者。

在(zài)這(zhè)樣的(de)大(dà)環境下,傳統的(de)「web 前端開發」演變成了(le/liǎo)「泛客戶端開發」,前端開發者從「配置工程師」被「逼」成了(le/liǎo)「軟件工程師」。開發變得更複雜了(le/liǎo),要(yào / yāo)處理的(de)問題更多了(le/liǎo),從業難度不(bù)知提升了(le/liǎo)多少倍——前端早就(jiù)不(bù)再簡單。

在(zài)衆多必須要(yào / yāo)處理的(de)問題中的(de)一個(gè),就(jiù)是(shì)表現層運行環境的(de)兼容問題,像跨浏覽器和(hé / huò)跨端、平台、技術棧。注意,這(zhè)裏說(shuō)的(de)是(shì)「表現層」而(ér)不(bù)是(shì)「視圖層」。

「表現層」與「視圖層」

「表現層」的(de)英文是(shì)「presentation tier」或「presentation layer」,具體是(shì)哪個(gè)取決于(yú)是(shì)物理上(shàng)還是(shì)邏輯上(shàng)劃分;而(ér)「視圖層」的(de)英文是(shì)「view」。「表現層」是(shì)「視圖層」的(de)超集,根據前端應用的(de)架構設計,它們既可以(yǐ)不(bù)等又可以(yǐ)相等。

表現層

「表現層」這(zhè)個(gè)詞出(chū)自經典的(de)三層架構(或多層架構),是(shì)其中一個(gè)分層。三層架構包括數據層、邏輯層和(hé / huò)表現層,一般用在(zài) C/S 架構中。

三層架構

爲(wéi / wèi)什麽會在(zài)這(zhè)篇講前端開發的(de)文章中提到(dào)它?這(zhè)是(shì)因爲(wéi / wèi),雖然在(zài)一些前端應用中用不(bù)到(dào),尤其是(shì)快餐式應用,但在(zài)企業級複雜前端應用中就(jiù)十分需要(yào / yāo)一個(gè)前端的(de)「三層架構」。

視圖層

「視圖層」則來(lái)自表現層常用的(de)「model-view-whatever」模式中的(de)「view」,即「視圖」。至于(yú)說(shuō)的(de)時(shí)候在(zài)「視圖」後面加個(gè)「層」字合不(bù)合适,就(jiù)不(bù)在(zài)這(zhè)裏讨論了(le/liǎo),文中皆使用「視圖層」這(zhè)個(gè)詞。

運行環境兼容

跨浏覽器

由于(yú)各浏覽器廠商對标準實現的(de)不(bù)一緻以(yǐ)及浏覽器的(de)版本等原因,會導緻特性支持不(bù)同、界面顯示 bug 等問題的(de)出(chū)現。但慶幸的(de)是(shì),他(tā)們基本是(shì)按照标準來(lái)的(de),所以(yǐ)在(zài)開發時(shí)源碼的(de)語法幾乎沒什麽不(bù)同。

所謂的(de)「跨浏覽器」實際上(shàng)就(jiù)是(shì)利用浏覽器額外的(de)私有特性和(hé / huò)技術或輔以(yǐ) JS 對浏覽器的(de) bug 進行「修正」與功能支持。

跨端、平台、技術棧

現在(zài),絕大(dà)部分的(de)前端開發者是(shì)在(zài)做泛客戶端開發——開發 web 應用、客戶端應用和(hé / huò)各類小程序。

在(zài)做 web 應用時(shí)需要(yào / yāo)考慮 PC 端和(hé / huò)移動端是(shì)分開還是(shì)适配?技術選型是(shì)用 React、Vue?還是(shì)用 Web Components?或是(shì)用其他(tā)的(de)?做客戶端應用、各類小程序時(shí)這(zhè)些也(yě)會面臨技術選型的(de)問題。

如果公司某個(gè)業務的(de)功能覆蓋了(le/liǎo)上(shàng)述所有場景,該如何去支撐?與跨浏覽器不(bù)同的(de)是(shì),不(bù)同端、平台、技術棧的(de)源碼語法不(bù)一樣,要(yào / yāo)滿足業務需求就(jiù)得各開發一遍。然而(ér),這(zhè)顯然成本過高,并且風險也(yě)有些大(dà)。

那麽,要(yào / yāo)怎麽解決這(zhè)個(gè)問題呢?從源頭出(chū)發。根本的(de)源頭是(shì)業務場景,然後是(shì)産品設計,但這(zhè)些都不(bù)是(shì)開發人(rén)員可掌控的(de),幾乎無法改變。能夠完全被開發人(rén)員所左右的(de)基本隻有開發階段的(de)事情,那就(jiù)從這(zhè)個(gè)階段的(de)源頭入手——源碼編寫。

若是(shì)與業務相關的(de)代碼隻需編寫一次就(jiù)能運行在(zài)不(bù)同的(de)端、平台、技術棧上(shàng),那真是(shì)太棒了(le/liǎo)!這(zhè)将會大(dà)大(dà)地(dì / de)降低成本并減少風險!

表現層的(de)抽象

爲(wéi / wèi)了(le/liǎo)達到(dào)跨端、平台、技術棧的(de)目的(de),需要(yào / yāo)将表現層再劃分爲(wéi / wèi)抽象層、運行層和(hé / huò)适配層。其中,抽象層是(shì)爲(wéi / wèi)了(le/liǎo)統一源碼的(de)編寫方式,可以(yǐ)是(shì) DSL、配置等,它是(shì)一種協議或約定;運行層就(jiù)是(shì)需要(yào / yāo)被「跨」的(de)端、平台、技術棧;适配層則是(shì)将抽象層的(de)産物轉換爲(wéi / wèi)運行層正常運行所需要(yào / yāo)的(de)形式。

表現層中可以(yǐ)被抽象的(de)大(dà)概有視圖結構、組件外觀、組件行爲(wéi / wèi)等。

視圖結構

在(zài) web 前端開發中,HTML 就(jiù)是(shì)一種視圖結構的(de)抽象,描述了(le/liǎo)界面中都有什麽,以(yǐ)及它們之(zhī)間的(de)層級關系。最終的(de)顯示需要(yào / yāo)浏覽器解析 HTML 後調用操作系統的(de) GUI 工具庫。

對于(yú)業務支撐來(lái)說(shuō),無論是(shì) HTML 還是(shì)其他(tā)什麽拼湊界面的(de)方式,相對來(lái)說(shuō)比較低級(是(shì)「low level」而(ér)不(bù)是(shì)「low」),視圖單元的(de)劃分粒度比較細,在(zài)開發界面時(shí)就(jiù)會花費更多的(de)時(shí)間。

我們需要(yào / yāo)一種能夠屏蔽一些不(bù)必關注的(de)細節的(de)視圖結構抽象,在(zài)這(zhè)個(gè)抽象中,每個(gè)視圖單元都有着其在(zài)業務上(shàng)的(de)意義,而(ér)不(bù)是(shì)有沒有都可以(yǐ)的(de)角色。具體做法請看下文。

組件外觀

大(dà)部分已存在(zài)的(de)組件的(de)視覺呈現是(shì)固定的(de),即某個(gè)組件的(de)尺寸、形狀、顔色、字體等無法被定制。如果同樣的(de)交互隻是(shì)因爲(wéi / wèi)視覺上(shàng)有所差異就(jiù)要(yào / yāo)重新寫組件,或者在(zài)組件外部重新寫份樣式進行覆蓋,那未免也(yě)太痛苦了(le/liǎo)……

我們可以(yǐ)将那些希望能夠被定制的(de)視覺呈現抽象成「主題」的(de)一部分,這(zhè)部分可以(yǐ)被叫做「皮膚」。在(zài)進行定制時(shí),分爲(wéi / wèi)線下和(hé / huò)線上(shàng)兩種方式。

「線下」是(shì)指在(zài)應用部署前的(de)開發階段進行處理。在(zài)前端構建工具豐富的(de)現在(zài),寫頁面樣式時(shí)已經不(bù)會去直接寫 CSS,而(ér)是(shì)像 Sass 這(zhè)種可編程式的(de)預處理器。這(zhè)樣就(jiù)可以(yǐ)抽取出(chū)一些控制視覺呈現的(de) Sass 變量,需要(yào / yāo)定制時(shí)通過在(zài)外部對變量賦值進行覆蓋,而(ér)不(bù)需要(yào / yāo)費勁重寫組件或樣式。

「線上(shàng)」則是(shì)部署後根據運行時(shí)數據動态改變。在(zài)皮膚定制即時(shí)預覽和(hé / huò)低代碼平台等場景,是(shì)基本沒機會去修改 Sass 變量并走一遍構建流程的(de),即使技術上(shàng)能夠辦到(dào)。借助 CSS 自定義屬性(CSS 變量)的(de)力量可以(yǐ)較爲(wéi / wèi)方便地(dì / de)做到(dào)視覺呈現的(de)運行時(shí)變更。

組件行爲(wéi / wèi)

組件除了(le/liǎo)外觀,其行爲(wéi / wèi)也(yě)應當是(shì)可以(yǐ)定制的(de)。看到(dào)「行爲(wéi / wèi)」這(zhè)個(gè)詞,第一反應就(jiù)是(shì)跟用戶操作相關的(de)事情,然而(ér)這(zhè)裏還包括與組件内部結構相關的(de)。

對于(yú)組件的(de)外部來(lái)說(shuō),組件内部就(jiù)是(shì)個(gè)黑盒子(zǐ),其自身結構的(de)組成部分有的(de)可以(yǐ)被上(shàng)文所說(shuō)的(de)視圖結構所控制,有的(de)則無能爲(wéi / wèi)力:

搜索組件

上(shàng)圖是(shì)一個(gè)比較複雜的(de)搜索組件,雖然外觀和(hé / huò)布局看起來(lái)有所不(bù)同,但「它們」确實是(shì)同一個(gè)組件。外觀不(bù)同的(de)解決方案上(shàng)面已經大(dà)體說(shuō)明,這(zhè)類視圖結構無法控制的(de)布局問題,需要(yào / yāo)枚舉場景後在(zài)組件内進行支持,然後作爲(wéi / wèi)「主題」的(de)一部分存在(zài)。

跟用戶操作相關的(de)行爲(wéi / wèi)有組件自身的(de)交互規則及與業務邏輯的(de)結合這(zhè)兩類。

交互規則又有兩種:一種是(shì)像表單是(shì)在(zài)字段值發生改變時(shí)就(jiù)校驗還是(shì)在(zài)點擊按鈕時(shí)校驗這(zhè)樣;另一種是(shì)像字段值是(shì)在(zài)輸入框的(de)值改變( input 事件)時(shí)更新還是(shì)失焦( change 事件)時(shí)更新這(zhè)樣,或是(shì)像下拉菜單的(de)彈出(chū)層是(shì)在(zài)懸停( hover 事件)時(shí)出(chū)現還是(shì)點擊( click 事件)時(shí)出(chū)現這(zhè)樣。

前者的(de)解決方式與上(shàng)面說(shuō)的(de)視圖結構無法控制的(de)布局問題差不(bù)多,後者則是(shì)需要(yào / yāo)組件支持事件映射,即外部可以(yǐ)指定組件某些交互的(de)觸發事件。當然,這(zhè)兩者同樣也(yě)可以(yǐ)作爲(wéi / wèi)「主題」的(de)一部分。

我們在(zài)寫組件時(shí)有件事是(shì)需要(yào / yāo)極力避免卻往往難以(yǐ)避免——組件中耦合業務邏輯。組件決定的(de)應該隻是(shì)外貌與交互形态,裏面隻有交互邏輯及控制展現的(de)狀态,不(bù)應該牽扯到(dào)任何具體業務相關的(de)邏輯。隻要(yào / yāo)長得一樣、操作一樣,那麽就(jiù)應該是(shì)同一個(gè)組件,具體業務相關的(de)邏輯注入進去。

這(zhè)段十分「個(gè)性化」的(de)業務邏輯,說(shuō)白了(le/liǎo)就(jiù)是(shì)響應用戶操作的(de)變化以(yǐ)及業務數據的(de)變化去更改組件内部的(de)狀态:

{
  // 組件事件
  events: {
    // 組件的(de)一個(gè)點擊事件
    'click-a': function() {},
    // 組件的(de)另一個(gè)點擊事件
    'click-b': function() {},
    // 組件的(de)一個(gè)改變事件
    'change-c': function() {},
  },
  // 業務數據變化的(de)回調
  watch: function( contextValue ) {},
}

運行時(shí)會注入一個(gè)上(shàng)下文給上(shàng)述對象方法的(de) this ,組件還可以(yǐ)添加工具方法給上(shàng)下文。該上(shàng)下文的(de)内置屬性與方法有:

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

視圖結構描述

上(shàng)面說(shuō)了(le/liǎo)我們需要(yào / yāo)一種比 HTML 之(zhī)類的(de)更進一步的(de)視圖結構抽象,下面就(jiù)來(lái)說(shuō)說(shuō)這(zhè)部分的(de)大(dà)體思路。

技術選型

在(zài)做視圖結構抽象時(shí)最常用到(dào)的(de)技術就(jiù)是(shì) XML-based 或 XML-like 以(yǐ)及 JSON-based 的(de)某種技術。XML-base 和(hé / huò) XML-like 的(de)技術都是(shì)符合 XML 語法的(de),唯一的(de)區别是(shì)前者要(yào / yāo)完全符合 XML 的(de)标準規範,像 Angular 和(hé / huò) Vue 的(de)模闆就(jiù)是(shì)後者;同樣的(de),JSON-based 的(de)技術是(shì)完全符合 JSON 的(de)标準規範的(de)技術,像 JSON Schema。

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

JSON-based 的(de)技術對前端運行時(shí)最爲(wéi / wèi)友好,解析成本幾乎爲(wéi / wèi)零;相反的(de),其可讀性很低,JSON 結構是(shì)縱向增長的(de),指定區域内的(de)表達力十分受限,無法很直觀地(dì / de)看出(chū)層級關系與視圖單元的(de)屬性:

{
  "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è)應用的(de)設計是(shì)不(bù)需要(yào / yāo)人(rén)工寫視圖結構描述的(de)話,可以(yǐ)考慮使用 JSON-based 的(de)技術。

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

下面的(de)模闆代碼所描述的(de)内容與上(shàng)面那段 JSON 代碼一模一樣,深呼吸,好好感受一下兩者之(zhī)間的(de)差異:

<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>

至此,視圖結構描述最終該選用哪種技術,想必無須多言。

雞哥(小雞)

設計思路

毋庸置疑,模闆的(de)語法要(yào / yāo)符合 XML 語法是(shì)前提,再在(zài)此基礎上(shàng)根據需求進行定制、擴展。首先要(yào / yāo)定義标簽集。所謂的(de)「标簽集」就(jiù)是(shì)一個(gè)元素庫,其中的(de)每個(gè)元素都要(yào / yāo)具備一定語義,使其在(zài)業務上(shàng)有存在(zài)意義。然後是(shì)制定描述元素的(de) schema 并實現其對應的(de)解析、校驗等邏輯。

元素 schema 大(dà)概是(shì)長這(zhè)樣:

// 屬性值類型
type PropType = 'boolean' | 'number' | 'string' | 'regexp' | 'json';

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

// 元素 schema
type ElementSchema = {
  name: string; // 元素名
  tag?: string; // 标簽名,不(bù)指定時(shí)取元素名
  props?: {
    [key: string]: PropDescriptor;
  };
  attrs?: {
    resolve: (key: string, val: any) => any;
  };
  // 節點行爲(wéi / wèi),是(shì)作爲(wéi / wèi)父節點的(de)子(zǐ)節點還是(shì)屬性存在(zài)
  behavior?: {
    type: 'append' | 'attach';
    // 以(yǐ)下都用于(yú) `type` 是(shì) `'attach'` 時(shí)
    host?: string; // 宿主(屬性名)
    keyed?: boolean; // 是(shì)否爲(wéi / wèi)鍵值對集合,值爲(wéi / wèi) `true` 且 `merge` 爲(wéi / wèi) `false` 時(shí)以(yǐ)節點 ID 爲(wéi / wèi)鍵
    merge?: boolean; // 當值爲(wéi / wèi) `true` 時(shí)将 `reduce` 的(de)返回值與 `host` 指定的(de)屬性的(de)值進行合并後重新賦值給 `host`
    reduce?: (node: ITemplateNode) => any; // 轉換節點信息
    restore?: (reduced: any, node?: ITemplateNode) => ITemplateNode | Partial<ITemplateNode>;
  };
};

可以(yǐ)看到(dào) schema 中有 props 和(hé / huò) attrs ,它們共同組成了(le/liǎo)模闆元素的(de)屬性(XML attributes),區别是(shì):模闆解析後的(de)屬性如果是(shì)在(zài) props 中定義的(de)并且滿足屬性描述符的(de) type 和(hé / huò) required 所指定的(de)限制條件,會成爲(wéi / wèi)模闆節點的(de) props 屬性;剩餘沒在(zài) props 中定義的(de)則成爲(wéi / wèi)模闆節點的(de) attrs 屬性,通過 resolve 方法能夠對屬性根據自己的(de)規則進行值的(de)轉換。

雖然在(zài)模闆中元素總是(shì)以(yǐ)嵌套的(de)形式展示出(chū)層級關系,但一個(gè)元素并不(bù)一定就(jiù)是(shì)其父級的(de)結構,還可能是(shì)配置。因此,元素 schema 中的(de) behavior 用于(yú)設置當前元素在(zài)模闆解析後是(shì)作爲(wéi / wèi)一個(gè)節點的(de)子(zǐ)節點存在(zài)還是(shì)作爲(wéi / wèi)某個(gè)屬性存在(zài)。

上(shàng)述的(de)模闆設計是(shì)純視圖結構描述的(de),并且隻對元素這(zhè)種「塊」進行處理,我認爲(wéi / wèi)這(zhè)樣夠用了(le/liǎo)。根據情況,可以(yǐ)擴展爲(wéi / wèi)像 Angular 和(hé / huò) Vue 的(de)模闆那樣支持文本、插值和(hé / huò)指令等。

如果懶癌發作并且沒什麽特殊需求,模闆解析的(de)工作可以(yǐ)交給魔改後的(de) Vue 2.6 編譯器,再适配爲(wéi / wèi)模闆節點樹。

每個(gè)模闆節點的(de)結構大(dà)緻爲(wéi / wèi):

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

最後,通過适配層将模闆節點樹轉爲(wéi / wèi)運行層的(de)組件樹,并把渲染的(de)控制權也(yě)轉交給了(le/liǎo)最終的(de)運行環境。

總結

在(zài)一個(gè)複雜的(de)前端應用中,如果不(bù)對其進行分層,那它的(de)擴展性和(hé / huò)可維護性等真的(de)會不(bù)忍直視……通常是(shì)采用經典的(de)三層架構,從下到(dào)上(shàng)分别爲(wéi / wèi)數據層、邏輯層和(hé / huò)表現層。本文以(yǐ)表現層爲(wéi / wèi)例,将其再次劃分出(chū)抽象層、運行層和(hé / huò)适配層這(zhè)三層,實際上(shàng)數據層和(hé / huò)邏輯層也(yě)可以(yǐ)套用這(zhè)種模式——就(jiù)像在(zài)生日蛋糕上(shàng)切上(shàng)四刀——我稱其爲(wéi / wèi)「九宮格」模型。

「九宮格」模型

在(zài)表現層的(de)各種抽象中,本文着重闡述了(le/liǎo)視圖結構描述的(de)技術選型與設計思路,可以(yǐ)看出(chū) XML-like 的(de)模闆從編寫到(dào)解析再到(dào)渲染這(zhè)一整條流程,與 Angular 和(hé / huò) Vue 的(de)模闆及 HTML 大(dà)體上(shàng)一緻;其他(tā)抽象隻是(shì)稍微提了(le/liǎo)提,以(yǐ)後有機會再展開來(lái)說(shuō)。

之(zhī)前也(yě)寫過幾篇與模闆相關的(de)文章:從提效角度與「面向組件」做對比的(de)《 我來(lái)聊聊面向模闆的(de)前端開發 》;從可定制性角度講的(de)《 我來(lái)聊聊配置驅動的(de)視圖開發 》;從低代碼平台的(de)核心理念「模型驅動」出(chū)發的(de)《 我來(lái)聊聊模型驅動的(de)前端開發 》。可以(yǐ)說(shuō),本文的(de)内容是(shì)它們有關表現層描述的(de)「根基」。

無論一家公司是(shì)不(bù)是(shì)做低代碼平台的(de),或者内部有沒有低代碼平台,都應該從表現層抽象出(chū)視圖結構描述,至少要(yào / yāo)有如此意識。

相關案例查看更多