<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
接下來需要解決兩個問題:
runtimeProps
如何存取到當前元件範例的 props
。這兩個問題非常重要,而恰好又可以通過良好的資料流設計一次性解決,接下來讓我們分別分析討論一下。
問題一:視覺化搭建的其他業務元素如何與畫布互動。比如拓展屬性設定面板、圖層列表、拖拽新增元件、定位錨點、主題等等
需要設計一個 Hooks API,可以存取到畫布提供的方法、資料。在 React 設計中,存取 Hooks API 需要在一定上下文內,所以可以將 <Designer>
拆為 <Designer>
與 <Canvas>
,其中 <Designer>
提供 Hooks 上下文,<Canvas>
負責渲染畫布。這樣開發者的使用方式就變成了這樣:
import { createDesigner } from 'designer' const { Designer, Canvas, useDesigner } = createDesigner() const EditPanel = { const { addComponent } = useDesigner() return <button onClick={() => addComponent(/** ... */)}>建立元件</button> } const App = () => { <Designer> <Canvas /> <EditPanel /> </Designer> }
為了支援多個 Designer 範例間隔離,通過 createDesigner
建立一套上下文獨立的 API,這樣就可以讓畫布、設定面板同時用 Designer 實現,用一套技術方案同時實現畫布與設定表單,這樣學習上下文、元件規範都可以統一為一套,表單、畫布能力也可以共用。
在 <Designer>
內的元件可以通過 useDesigner
直接存取資料與方法,比如上面例子在直接存取內建方法 addComponent
時,不需要附加任何參加,而 addComponent
方法也永遠保持參照不變,此時 useDesigner
不會導致 EditPanel
重渲染。
如果需要存取當前元件樹,並在元件樹變化時重渲染,可以通過如下方式存取:
const EditPanel = { const { componentTree } = useDesigner(state => ({ componentTree: state.componentTree })) }
該寫法的效果是,當 state.componentTree
變化了,會觸發 EditPanel
重新渲染,並拿到最新值。
同時也可以傳入第二個引數 compare
自定義對比方法,預設為 shallowEqual
:
useDesigner( (state) => ({ componentTree: state.componentTree, }), isEqual );
如此一來,無論給畫布拓展多少 UI 元素都沒有問題,而且 UI 元素可以自由的存取畫布方法與資料。
問題二:runtimeProps
如何存取到當前元件範例的 props
在 componentMeta.runtimeProps
中,我們構造一個 selector
函數用於存取當前元件 props:
const divMeta = { componentName: "div", runtimeProps: ({ selector }) => { const name = selector(({ props }) => props.name) return { fullName: `full-${name}` } } element: /** ... */ };
首先支援從 runtimeProps
回撥裡拿到 selector
,並且該 selector
支援傳入一個回撥函數,該回撥函數的引數中 props
指向當前元件範例的 props
,通過該方法就可以存取元件 props 了。
該 selector 僅在 props.name
改變時重新執行,並且也遵循 compare
對比規則,即當 props.name
變化時,selector
回撥函數的返回值通過 compare
與上一次值進行對比,如果沒有變化就返回上一次的舊值,變化了則返回新值。預設對比函數為 shallowEqual
,與 useDesigner
類似,也可以在第二個引數位置覆寫 compare
方法。
那元件元資訊如何存取內建靜態方法呢?由於靜態方法參照不變,因此可以在 selector
同級直接傳入:
const divMeta = { componentName: "div", runtimeProps: ({ addComponent }) => { return { add: () => { /** addComponent(...) */ } } } element: /** ... */ };
如此一來,我們就將資料流與元件元資訊打通了,即 UI 可以通過 useDesigner
存取與運算元據流,元件元資訊也可以直接拿到方法,或通過 selector
拿到資料,相應的也可以存取與運算元據流。這樣的設計在以後拓展更多元件元資訊函數時,都可以繼承下來,開發者只要學習一次語法,就可以獲得非常強力的拓展性。
剛才介紹了一些內建的狀態(componentTree
)與方法(addComponent
),在下一接會系統介紹筆者梳理了哪些內建狀態與方法。首先拋開內建狀態與方法不談,應用肯定需要定義自己的狀態與方法,我們可以提供兩種模式給使用者。
第一種是應用的狀態與方法定義在外部,對應受控模式。
假設你的應用在對接 Designer 之前就已經用 Redux、Dva、Zustand 等狀態管理庫,那麼就可以使用受控模式直接接入:
const App = () => { // 虛擬碼,不管是 useState 還是其他資料流管理狀態,假這裡拿到了資料與方法 const { getAppInfo } = useSomeLib(); const { userName } = useSomeLib("userName"); return <Designer actions={{ getAppInfo }} state={{ userName }} />; };
將方法傳給 actions
,狀態傳給 state
。
第二種是應用的狀態與方法通過 <Designer>
定義,對用非受控模式。
假設你的應用之前沒有使用任何資料流,那麼也可以直接將 Designer 的資料流作為專案資料流使用:
import { createMiddleware, createDesigner } from "designer"; const middleware1 = createMiddleware({ state: { userName: "bob " }, actions: { getAppInfo: () => {} }, }); const { Designer } = createDesigner(middleware1); const App = () => { return <Designer />; };
通過 createMiddleware
建立一箇中介軟體定義狀態與函數,傳入 createDesigner
即可生效。
也可以在 createMiddleware
裡通過第二個引數定義自定義 hooks,或者拿到方法更改 State:
const middleware1 = createMiddleware( { state: { userName: "bob " }, }, ({ setState }) => { const setUserName = React.useCallback((newName: string) => { setState((state) => ({ ...state, userName: newName, })); }); return { setUserName }; } );
Designer 內部採用最樸素的 Redux 管理狀態,提供了最基礎的 getState
與 setState
獲取與修改狀態,基於它們封裝業務函數即可。
無論是受控模式,還是非受控模式(亦或兩種模式同時使用),定義的狀態與方法都可以在以下兩個位置存取,第一個位置是 useDesigner
:
const { /** 自定義函數 */, setUserName, /** 自定義函數 */ getAppInfo, /** 內建函數 */ addComponent, // 內建變數 componentTree, // 自定義變數 userNamee } = useDesigner(state => ({ componentTree: state.componentTree, userName: state.userName }))
第二個位置是元件元資訊上的回撥函數,比如 runtimeProps
:
const divMeta = { componentName: "div", runtimeProps: ({ selector, /** 自定義函數 */, setUserName, /** 自定義函數 */ getAppInfo, /** 內建函數 */ addComponent }) => { const { /** 內建變數 */ componentTree, /** 自定義變數 */ userName } = selector(({ state }) => ({ componentTree: state.componentTree, userName: state.userName })) return { componentTree, userName } } element: /** ... */ };
至此,我們實現了一套完整的資料流定義,包括:
useDesigner
,還是在元件元資訊通過 selector
都可存取這些變數與函數。一個基本可用的視覺化搭建框架在本章就算設計完了。但這只是視覺化搭建問題的冰山一角,未來的章節,筆者會逐漸為大家介紹更多視覺化搭建的設計。
但無論框架未來怎麼發展,也永遠會基於這前三章的基本設定,總結一下,這三章的基本設定就是:設計一個邏輯與 UI 分離的視覺化搭建協定,資料流、元件元資訊、元件範例是永遠的鐵三角,資料流可以對接任意已存在的實現,或基於 Designer 規範實現,元件元資訊與元件範例僅儲存最基本資訊,得益於資料流的自定義能力,以及無論何處都有完全的資料流存取能力,使業務框架既遵循規則,又可以千變萬化。
拋開具體 API 設計或者命名不談,一個有簡潔、抽象,又提供極少量 API 卻能滿足所有業務客製化訴求,是視覺化搭建永遠追求的目標。只要熟悉了這套規範,就可以幾乎僅根據業務表現,一眼猜出是基於哪些 API 封裝實現的,那麼維護成本與理解成本將大大降低,規範的意義就體現在這裡。
也許有同學會覺得,現在各個大廠都有無數視覺化搭建的實現,視覺化搭建概念都已經爛大街了,為什麼還要重新設計一個呢?
因為也許數量不代表質量,維護的時間越久,參與的同學越多,越容易使設計變得冗餘,概念變得複雜,要對抗這些遞增的熵,唯有不斷重新設計,從零開始反思方案。
下一講理論思考會少一些,介紹視覺化搭建框架會考慮內建哪些變數與方法,更多關於JS畫布與元件元資訊資料流的資料請關注it145.com其它相關文章!
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共用 3.0 許可證)
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45