最近,阿里巴巴 github 倉庫裏開源了一個新的項目:schema-plugin-flow(簡稱 Sifo [sɪfɔ])[☆歡迎加星☆],這是一個前端開發框架,主要特色是高擴展性與可二次開發。javascript
這裏的高擴展包含但不限於頁面結構的修改、渲染組件的替換、組件屬性的變動、組件事件的監聽與阻斷等。html
可二次開發(簡稱可二開)主要體如今:對一個用 Sifo 開發的頁面,二次開發者能夠在不接觸源業務代碼的狀況下,實現對頁面的高擴展。前端
Sifo 是開發框架,自己是與UI框架解耦的,React 框架下可使用 sifo-react ,Vue 框架下可使用 sifo-vue . vue
Sifo 的另外一個特色是插件式開發,這使得不管是在 React 下仍是 Vue 下,開發者寫的邏輯代碼幾乎是同樣的 ,二次開發者一樣如此。java
schema-plugin-flow 倉庫裏帶了 react/vue 下的體驗示例,你們能夠按照 README 的說明本身嘗試。react
在這個演示中,有7個擴展插件,寫在7個獨立的js文件中,複選框決定應該註冊哪些插件,每一個插件控制不一樣的邏輯,全部的插件共同組成展現頁面。
這個圖是演示的Sifo的多層擴展能力,從圖中能夠看到,不一樣的插件是能夠任意組合的,邏輯獨立而又能共同協做。有的插件控制頁面結構,有的進行事件響應,第 7 個插件註冊了新組件並替換了原頁面的組件。git
這個例子演示瞭如何監聽一個按鈕組件的點擊事件,並在點擊事件中修改其它組件的屬性,也演示了多個插件的情形,同時也有一個二開的插件控制了在按鈕點擊時改變文本顏色。github
源碼在:sifo-react 、sifo-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 下也是同樣的,有興趣的同窗能夠本身試試。
Sifo 的核心是 sifo-model,主要有這幾部分:
mApi 是整個 Sifo 的核心接口。更多詳細內容可見:sifo-model
這裏用 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 屬性來開啓。
在咱們進行業務開發過程當中,特別是作企業 SaaS 應用業務時,常常會遇到的一個問題是:某客戶想在咱們提供的應用中增長個性化功能,而這個功能是不具有通用性的,咱們該怎麼辦?顯然,咱們不會將這類功能放到咱們的標準業務中,但如何知足客戶的需求以使客戶能從咱們的應用中受益呢?
Sifo 的高擴展性、可二次開發能力就提供了一種解決上述問題的方案。那麼,Sifo 還有哪些可能的使用場景呢?
元數據驅動/頁面配置化
能夠經過配置、服務端模型生成等方式,產出一份 schema,配合合適的 render,將頁面渲染出來。
視圖與邏輯解耦
組件的組合造成了視圖,插件處理業務邏輯,組件是能夠直接替換的,這在一些組件庫變動場景時也許會更容易。
邏輯複用/功能組合
插件與 UI 是鬆耦合的,一些經常使用的邏輯能夠獨立出來,在各個須要的地方使用。
經過不一樣的插件組合,來提供不一樣的能力,實現不一樣的業務需求。除了在業務中組合不一樣的頁面插件和組件插件外,更顯著的例子是組合模型插件的能力。
功能擴展
插件機制造成了擴展能力,同時,這種擴展能力是可被控制的,也就是插件的可插拔。利用 sifo-singleton 這類全局擴展容器,能夠作到不接觸原始頁面代碼的狀況下,對頁面進行擴展。
邏輯歸一
將一些可集中處理、成批處理、單獨處理的邏輯放到單獨的插件中。
好比界面有不少下拉字段的下拉項是從接口返回的,能夠在 schema 上(或其它合適的方式)描述接口信息,使用一個模型插件將全部取數賦值動做作完;又好比在界面上配置了聯動規則,可使用一個模型插件來處理規則的解析與執行,而不是侵入到每一個頁面代碼內部;
後端數據驅動
利用統一的api,能夠實現一個模型插件,用來與服務端通訊,將界面變化通知服務端,並執行服務端返回的指令,改變界面狀態,實現後端數據驅動。
其它
能夠發揮想象力來實現不一樣的模型插件
歡迎 ☆加星☆,測試反饋與貢獻代碼~~
阿里巴巴CBU技術部工業品採銷團隊招前端,萬億規模工業電商,十萬億規模產業互聯網等你加入, 歡迎投遞簡歷至:fanchong.xs@alibaba-inc.com