高擴展性、可二開的前端開發框架—sifo

最近,阿里巴巴 github 倉庫裏開源了一個新的項目:schema-plugin-flow(簡稱 Sifo [sɪfɔ])[☆歡迎加星☆],這是一個前端開發框架,主要特色是高擴展性與可二次開發。javascript

這裏的高擴展包含但不限於頁面結構的修改、渲染組件的替換、組件屬性的變動、組件事件的監聽與阻斷等。html

可二次開發(簡稱可二開)主要體如今:對一個用 Sifo 開發的頁面,二次開發者能夠在不接觸源業務代碼的狀況下,實現對頁面的高擴展。前端

Sifo 是開發框架,自己是與UI框架解耦的,React 框架下可使用 sifo-react ,Vue 框架下可使用 sifo-vuevue

Sifo 的另外一個特色是插件式開發,這使得不管是在 React 下仍是 Vue 下,開發者寫的邏輯代碼幾乎是同樣的 ,二次開發者一樣如此。java

1、初探

schema-plugin-flow 倉庫裏帶了 react/vue 下的體驗示例,你們能夠按照 README 的說明本身嘗試。react

一、演示圖

在這個演示中,有7個擴展插件,寫在7個獨立的js文件中,複選框決定應該註冊哪些插件,每一個插件控制不一樣的邏輯,全部的插件共同組成展現頁面。

這個圖是演示的Sifo的多層擴展能力,從圖中能夠看到,不一樣的插件是能夠任意組合的,邏輯獨立而又能共同協做。有的插件控制頁面結構,有的進行事件響應,第 7 個插件註冊了新組件並替換了原頁面的組件。git

二、hello world

這個例子演示瞭如何監聽一個按鈕組件的點擊事件,並在點擊事件中修改其它組件的屬性,也演示了多個插件的情形,同時也有一個二開的插件控制了在按鈕點擊時改變文本顏色。github

源碼在:sifo-reactsifo-vue,能夠看到在 React 下和 Vue 下,除了組件的用法不一樣,其它部分是同樣的,下一章節會深刻地看看。後端

三、替換渲染組件

sifo-vue 的體驗示例裏有一個二次開發怎樣替換渲染組件的例子:在按鈕點擊3次時,將原日期組件替換爲自定義的組件。api

二開完整寫法也在下一章節講,這裏只列代碼主要部分:

a. 註冊自定義組件: test-ext-button

const components = {
  'test-ext-button': {
    data: function () {
      return {
        count: 0
      }
    },
    template: `
  <button v-bind:style="{ margin: '0 8px' }" v-on:click="count++">
  You clicked me {{ count }} times.
  </button>`
  }
};

b. 替換爲用自定義組件渲染

let count = 0;
    mApi.addEventListener(event.key, 'click', () => {
    count++;
    if (count >= 3) { 
      mApi.replaceComponent('date', 'test-ext-button');
    }

React 下也是同樣的,有興趣的同窗能夠本身試試。

2、深刻

Sifo 的核心是 sifo-model,主要有這幾部分:

  • namespace 是二次擴展的惟一標識;
  • plugins 是插件列表,插件有三類:模型插件、頁面插件與組件插件;
  • schema 是頁面結構定義。

mApi 是整個 Sifo 的核心接口。更多詳細內容可見:sifo-model

一、hello world 代碼

這裏用 Vue 的 hello world 例子代碼來看。

首先,寫根組件模板,

<template>
  <sifo-app
    :namespace="namespace"
    class="quick-start-demo"
    :plugins="plugins"
    :components="components"
    :schema="schema"
    :openLogger="openLogger"
  />
</template>

而後,定義組件(這是 Vue 局部組件,全局組件不用寫),這裏定義了三個組件:Container、Slogan 和 Button。

<script>
import SifoApp from "@schema-plugin-flow/sifo-vue";
// register local components
const components = {
  Container: {
    template: "<div><slot></slot></div>",
  },
  Slogan: {
    template: "<h2>{{content}}</h2>",
    props: ["content"],
  },
  Button: {
    template: `<button @click="$emit('click')">click to change</button>`,
  },
};

再而,是定義頁面結構,用一個叫 schema 的結構描述了頁面上組件的組合方式。

// schema 定義了初始的頁面結構
const schema = {
  component: "Container",
  id: "mainId",
  attributes: {},
  children: [
    {
      component: "Slogan",
      id: "slogan_id",
      attributes: {
        content: "hello world",
      },
    },
    {
      component: "Button",
      id: "test_btn_id",
      attributes: {},
    },
  ],
};

接下來,是寫插件,監聽按鈕 click 事件(按慣例,react 下是監聽 onClick)。這是一個「組件插件」,是以 schema 的節點 id 爲維度的。

const componentPlugin1 = {
  test_btn_id: {
    onComponentInitial: (params) => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, "click", () => {
        mApi.setAttributes("slogan_id", {
          content: "hello sifo",
        });
      });
    },
  },
};

同時,能夠加第二個插件。咱們能夠給一個節點上加多個插件,來實現不一樣的邏輯,二次開發也是這樣。

const componentPlugin2 = {
  test_btn_id: {
    onComponentInitial: (params) => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, "click", () => {
        console.log("test_btn_id clicked!");
      });
    },
  },
};

最後,是參數使用

const plugins = [
  { componentPlugin: componentPlugin1 },
  { componentPlugin: componentPlugin2 },
];
export default {
  name: "quick-start",
  components: { SifoApp },
  beforeCreate: function () {
    const sifoAppProps = {
      namespace: "quick-start",
      plugins: plugins,
      components,
      schema,
      openLogger: true,
    };
    Object.keys(sifoAppProps).forEach((key) => {
      this[key] = sifoAppProps[key];
    });
  },
};
</script>

React 下是這樣:

const components = { Container, Slogan, Button };
const plugins = [
  { componentPlugin: componentPlugin1 },
  { componentPlugin: componentPlugin2 }
];
class App extends React.Component {
  render() {
    return (
      <SifoApp
        className='quick-start'
        namespace='quick-start'
        components={components}
        schema={schema}
        plugins={plugins}
        openLogger={false}
      />
    );
  }
}

討論:
看完 hello world ,可能會有人想怎麼這麼多代碼,直接寫大概幾行代碼的事兒。不過看看咱們對這個 hello world 還能作的事情:修改頁面結構、替換渲染組件、變動組件屬性、給組件添加事件的監聽等,在有擴展需求的場景下仍是能夠接受的。同時,schema 做爲一份配置通常會寫在單獨的地方來維護,plugins 也能夠按功能區分放到不一樣的文件裏。

二、二次開發擴展

對 hello world 例子,那個在按鈕點擊時改變 Slogan 顏色的擴展插件,是寫在其它地方的(真實業務時多是從其它地方加載的js資源),使用了 sifo-singleton,插件寫法相同。

import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
const singleton = new SifoSingleton('quick-start'); // namespace: quick-start
const componentPlugin = {
  test_btn_id: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'onClick', () => {
        mApi.setAttributes('slogan_id', {
          style: { color: 'red' }
        });
      })
    }
  }
};
singleton.registerItem('ext-quick-start', () => {
  return {
    plugins:[
      { componentPlugin }
    ],
    components: {},
    openLogger: true
  }
});

二開能夠註冊自定義組件、自定義插件(組件插件和頁面插件)和打開控制檯sifo log等。

三、渲染優化

模型插件能夠實現更底層的能力,好比能夠利用中間件對 mApi 進行擴展(如 sifo log),好比包裝 ant design form,又好比能夠實現一個表單校驗模型插件用來實現表單場景。

這裏講一下_渲染優化模型插件_,sifo-react/sifo-vue 都是 top-down (自頂自下)的渲染模式,在複雜的頁面上,可使用這個模型插件來優化渲染。

sifo-react 能夠引入 sifo-mplg-react-optimize,sifo-vue 內置了渲染優化模型插件,使用 optimize: true 屬性來開啓。

3、發散

在咱們進行業務開發過程當中,特別是作企業 SaaS 應用業務時,常常會遇到的一個問題是:某客戶想在咱們提供的應用中增長個性化功能,而這個功能是不具有通用性的,咱們該怎麼辦?顯然,咱們不會將這類功能放到咱們的標準業務中,但如何知足客戶的需求以使客戶能從咱們的應用中受益呢?

Sifo 的高擴展性、可二次開發能力就提供了一種解決上述問題的方案。那麼,Sifo 還有哪些可能的使用場景呢?

元數據驅動/頁面配置化

能夠經過配置、服務端模型生成等方式,產出一份 schema,配合合適的 render,將頁面渲染出來。

視圖與邏輯解耦

組件的組合造成了視圖,插件處理業務邏輯,組件是能夠直接替換的,這在一些組件庫變動場景時也許會更容易。

邏輯複用/功能組合

插件與 UI 是鬆耦合的,一些經常使用的邏輯能夠獨立出來,在各個須要的地方使用。

經過不一樣的插件組合,來提供不一樣的能力,實現不一樣的業務需求。除了在業務中組合不一樣的頁面插件和組件插件外,更顯著的例子是組合模型插件的能力。

功能擴展

插件機制造成了擴展能力,同時,這種擴展能力是可被控制的,也就是插件的可插拔。利用  sifo-singleton 這類全局擴展容器,能夠作到不接觸原始頁面代碼的狀況下,對頁面進行擴展。

邏輯歸一

將一些可集中處理、成批處理、單獨處理的邏輯放到單獨的插件中。

好比界面有不少下拉字段的下拉項是從接口返回的,能夠在 schema 上(或其它合適的方式)描述接口信息,使用一個模型插件將全部取數賦值動做作完;又好比在界面上配置了聯動規則,可使用一個模型插件來處理規則的解析與執行,而不是侵入到每一個頁面代碼內部;

後端數據驅動

利用統一的api,能夠實現一個模型插件,用來與服務端通訊,將界面變化通知服務端,並執行服務端返回的指令,改變界面狀態,實現後端數據驅動。

其它

能夠發揮想象力來實現不一樣的模型插件

4、結尾

歡迎 ☆加星☆,測試反饋與貢獻代碼~~

阿里巴巴CBU技術部工業品採銷團隊招前端,萬億規模工業電商,十萬億規模產業互聯網等你加入, 歡迎投遞簡歷至:fanchong.xs@alibaba-inc.com

相關文章
相關標籤/搜索