摘要:寫在最后總體來說,是一個小而美的框架,值得我們來折騰一下,以上均為本人理解,如有錯誤還請指出,不勝感激一個硬廣我所在團隊工作地點在北京求大量前端社招實習,有意者可發簡歷至
寫在前面
沒錯,又是一個新的前端框架,hyperapp非常的小,僅僅1kb,當然學習起來也是非常的簡單,可以說是1分鐘入門。聲明式:HyperApp 的設計基于Elm Architecture(這也意味著組件更多的是純函數),支持自定義標簽以及虛擬DOM。下面先來看下怎么使用:
hello worldimport { h, app } from "hyperapp"; app({ state: { count: 0 }, view: (state, actions) => (), actions: { down: state => ({ count: state.count - 1 }), up: state => ({ count: state.count + 1 }) } }); {state.count}
(完整demo可以參見這里)
這樣就完成了一個Counter,基本由state,view,actions構成:
state: 與react中的如出一轍,state的改變會引起重新渲染
view: 相當于react中的render
actions: 對state進行改變
h相當于react的createElement,來看下h接收的參數:
tag: 標簽名,或者一個函數,傳入函數也就意味著無狀態組件
data: 相當于react中的props
children: 子節點
需要注意的一點是,hyperapp并不支持boolean類型,對于boolean類型會忽略,使用時注意將其轉化為string類型,如:
Test {true}
// TestTest {String(true)}
// Test true
至于為什么?可以參見源碼
生命周期下面來看一下其生命周期,對于hyperapp的整個運行過程,可以參見下圖:
load:相當于react的componentWillMount
update:相當于react的componentWillUpdate
render:調用view函數之前調用
action:調用actions之前,一般用來進行log
resolve:調用actions之后,對于一個異步操作來說,actions返回一個promise,生命周期resolve來處理,返回一個函數update => result.then(update),即框架內部調用update來更新state,重新渲染
具體代碼可以參考:
// 生命周期: action -> actions[key] -> resolve // 異步請求需要利用resolve emit("action", { name: name, data: data }); var result = emit("resolve", action(appState, appActions, data)); return typeof result === "function" ? result(update) : update(result);
對于每一個節點來說,有著三個特殊的屬性:
oncreate:相當于componentDidMount
onupdate:相當于componentDidUpdate
onremove:與componentWillUnMount類似,需要注意的是,加入有了這個屬性,那么當節點需要被移除時,也不會被移除,需要自己來從dom中移除,這樣設計是為了便于做一些淡入淡出等效果,具體源碼可以參見這里,更多的使用方式以及討論可以參見這里
三個屬性均為函數,接收一個參數,就是這個節點
自定義組件通過上面,基本上可以了解hyperapp的基本寫法,下面來看一下如何自定義組件:
“木偶”組件const Header = ({ title, caption }) => (); // 使用 {title} {caption}
無狀態組件的寫法與react基本一致,hyperapp官方給出的自定義組件的方式僅僅有這種,但是所有的組件都要是無狀態的???答案當然是否定的,如何實現“智能組件”是一個問題:
“智能”組件我們通常的期望業務組件具有一些基本的功能,比如數據獲取展現這種:
const Header = app({ state: { caption: "loading" }, view(state, actions) { return ({state.caption} ); }, actions: { fetchData(state) { return new Promise((resolve) => { // 模擬fetch數據 setTimeout(() => { state.caption = "ok"; resolve(state); }, 1000); }); } }, events: { load(state, actions) { actions.fetchData(state); }, resolve(state, actions, result) { if (result && typeof result.then === "function") { return update => result.then(update); } } } }); export default Header;
按照如下方式使用:
import Header from "./Header"; ... state: { count: 0 }, view: (state, actions) => (), ... {state.count}
打開頁面,從ui來看已經實現組件封裝,但是這種是一種”曲線“的實現方式,為什么說它是不正規,可以觀察其dom層級,可能與我們理解和期望的并不相同。我們期望得到的層級是:
body main header h2
但是事實上得到的層級為:
body header main h2
至于為什么會產生這種情況,需要看一下源碼:
// app接收一個對象 function app(props) { ... // appRoot 就是需要掛載到的根節點 var appRoot = props.root || document.body ... // 注意此處,下文會用到 return emit; ... // 利用raf調用render渲染ui function render(cb) { element = patch( appRoot, ... ); } ... function patch(parent, ...) { if (oldNode == null) { // 第一次渲染,將節點插入到appRoot中 // 只要是第一次掛載,element為null element = parent.insertBefore(createElement(node, isSVG), element); } ... } }
所以說將Header組件掛載的原因并不是我們通過jsx寫出了這層結構,而是在import的時候,就已經將其掛載到了document.body下,main在掛載到document.body時,被插入到子節點的末尾。
h(tagName, props, children)
來簡單的看下h的實現:
function h(tag, data) { // 根據后續參數,生成children while (stack.length) { if (Array.isArray((node = stack.pop()))) { // 處理傳入的child為數組 for (i = node.length; i--; ) { stack.push(node[i]); } } ... } ... return typeof tag === "string" ? { tag: tag, data: data || {}, children: children } : tag(data, children); }
可以得出的是,tag接收函數傳入,比如木偶組件,tag就是一個函數,但是對于
function emit(name, data) { // 一個不常見的寫法,這個寫法會返回data return ( (appEvents[name] || []).map(function(cb) { var result = cb(appState, appActions, data); if (result != null) { data = result; } }), data ); }
基于目前這兩點,可以得出:
h返回的就是children,也就是一個[]
由于
需要render的節點的子節點中根本就沒有
這種實現方式可以說是非常的不好,局限性也很大,想想可不可以利用其他方法實現:
// 改進Header組件 const Header = (root) => app({ root, ...同上 }); // 改進引入方式 view: (state, actions) => (), Header(e)}>{state.count}
這種方式,利用了oncreate方法,掛載后,載入組件(可以考慮通過代碼分割將組件異步加載)
hyperapp支持傳入mixins,既然天然的支持這個,那么將一個組件進行兩方面分割:
view,利用“木偶組件”實現
feature,利用mixins實現
組件定義:
export const HeaderView = ({ text }) => ({text} ); export const HeaderMixins = () => ({ state: // 同上 actions: // 同上 events: // 同上 });
使用方式:
import { HeaderView, HeaderMixins } from "./HeaderView"; ... state: { count: 0 }, view: (state, actions) => (), mixins: [ HeaderMixins() ] ... {state.count}
mixins會將其屬性與本身進行一個并操作,可以理解為Object.assign(key, mixins[key]),對于events來說,為一個典型的發布/訂閱模式,events的某一種類型對應一個數組,emit時會將其全部執行。本人認為利用這種方式可以實現出一個比較符合框架本意的”智能“組件,但是仍然有些問題,就是state,在使用這個組件時不得不去看一下組件內部的state叫什么名字,而且容易造成同名state沖突的情況。
寫在最后總體來說,hyperapp是一個小而美的框架,值得我們來折騰一下,以上均為本人理解,如有錯誤還請指出,不勝感激~
一個硬廣我所在團隊(工作地點在北京)求大量前端(社招 or 實習),有意者可發簡歷至:zp139505@alibaba-inc.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/88804.html
摘要:,大家好,好久不賤呢最近因為看了一些的小說,整個人都比較致郁就在昨天,我用了一天的時間寫了,又一個小而美的前端框架可能你覺得,有了和,沒必要再寫一個了我覺得我還是想想辦法尋找一下它的存在感吧先看的組件化方案最先看到的應該是。 halo,大家好,好久不賤呢! 最近因為看了一些 be 的小說,整個人都比較致郁::>__+ {state.count--}}>- ...
摘要:,大家好,好久不賤呢最近因為看了一些的小說,整個人都比較致郁就在昨天,我用了一天的時間寫了,又一個小而美的前端框架可能你覺得,有了和,沒必要再寫一個了我覺得我還是想想辦法尋找一下它的存在感吧先看的組件化方案最先看到的應該是。 halo,大家好,好久不賤呢! 最近因為看了一些 be 的小說,整個人都比較致郁::>__+ {state.count--}}>- ...
摘要:專有的內容更少,而更多符合標準的成分。當前標簽實例的方法被調用時當前標簽的任何一個祖先的被調用時更新從父親到兒子單向傳播。相對來說,微型場景會更適合,不想要太多的外部依賴,又需要組件化數據驅動等更現代化框架的能力。 Riot.js是什么? Riot 擁有創建現代客戶端應用的所有必需的成分: 響應式 視圖層用來創建用戶界面 用來在各獨立模塊之間進行通信的事件庫 用來管理URL和瀏覽器回...
摘要:專有的內容更少,而更多符合標準的成分。當前標簽實例的方法被調用時當前標簽的任何一個祖先的被調用時更新從父親到兒子單向傳播。相對來說,微型場景會更適合,不想要太多的外部依賴,又需要組件化數據驅動等更現代化框架的能力。 Riot.js是什么? Riot 擁有創建現代客戶端應用的所有必需的成分: 響應式 視圖層用來創建用戶界面 用來在各獨立模塊之間進行通信的事件庫 用來管理URL和瀏覽器回...
閱讀 1317·2019-08-30 15:44
閱讀 2031·2019-08-30 13:49
閱讀 1660·2019-08-26 13:54
閱讀 3494·2019-08-26 10:20
閱讀 3268·2019-08-23 17:18
閱讀 3303·2019-08-23 17:05
閱讀 2137·2019-08-23 15:38
閱讀 1022·2019-08-23 14:35