沒錯,又是一個新的前端框架,hyperapp
很是的小,僅僅1kb
,固然學習起來也是很是的簡單,能夠說是1分鐘入門。聲明式:HyperApp 的設計基於Elm Architecture
(這也意味着組件更多的是純函數),支持自定義標籤以及虛擬DOM。下面先來看下怎麼使用:前端
import { h, app } from 'hyperapp'; app({ state: { count: 0 }, view: (state, actions) => ( <main> <h2>{state.count}</h2> <button onclick={actions.down}>-</button> <button onclick={actions.up}>+</button> </main> ), actions: { down: state => ({ count: state.count - 1 }), up: state => ({ count: state.count + 1 }) } });
(完整demo能夠參見這裏)
這樣就完成了一個Counter
,基本由state
,view
,actions
構成:node
state
: 與react
中的一模一樣,state
的改變會引發從新渲染view
: 至關於react
中的render
actions
: 對state
進行改變h
至關於react
的createElement
,來看下h
接收的參數:react
tag
: 標籤名,或者一個函數,傳入函數也就意味着無狀態組件data
: 至關於react
中的props
children
: 子節點須要注意的一點是,hyperapp
並不支持boolean
類型,對於boolean
類型會忽略,使用時注意將其轉化爲string類型,如:git
<h3>Test {true}</h3> // Test <h3>Test {String(true)}</h3> // Test true
至於爲何?能夠參見源碼github
下面來看一下其生命週期,對於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);
對於每個節點來講,有着三個特殊的屬性:promise
oncreate
:至關於componentDidMount
onupdate
:至關於componentDidUpdate
onremove
:與componentWillUnMount
相似,須要注意的是,加入有了這個屬性,那麼當節點須要被移除時,也不會被移除,須要本身來從dom
中移除,這樣設計是爲了便於作一些淡入淡出等效果,具體源碼能夠參見這裏,更多的使用方式以及討論能夠參見這裏 三個屬性均爲函數,接收一個參數,就是這個節點前端框架
經過上面,基本上能夠了解hyperapp
的基本寫法,下面來看一下如何自定義組件:app
const Header = ({ title, caption }) => ( <header> <h1> {title} <small>{caption}</small> </h1> </header> ); // 使用 <Header title="hyperapp-example" caption="demo" />
無狀態組件的寫法與react
基本一致,hyperapp
官方給出的自定義組件的方式僅僅有這種,可是全部的組件都要是無狀態的???答案固然是否認的,如何實現「智能組件」是一個問題:框架
咱們一般的指望業務組件具備一些基本的功能,好比數據獲取展示這種:
const Header = app({ state: { caption: 'loading' }, view(state, actions) { return ( <header>{state.caption}</header> ); }, 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) => ( <main> <Header /> <h2>{state.count}</h2> </main> ), ...
打開頁面,從ui來看已經實現組件封裝,可是這種是一種」曲線「的實現方式,爲何說它是不正規,能夠觀察其dom
層級,可能與咱們理解和指望的並不相同。咱們指望獲得的層級是:
body main header h2
可是事實上獲得的層級爲:
body header main h2
至於爲何會產生這種狀況,須要看一下源碼:
app
作了什麼?// 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
時,被插入到子節點的末尾。
<Header />
去哪兒了?<Header />
就這樣消失了,先來看下h
,就像在react
把jsx
翻譯爲createElement
,hyperapp
的jsx
會被翻譯爲以下形式:
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
就是一個函數,可是對於<Header />
來講,tag
爲app
函數返回的emit
:
function emit(name, data) { // 一個不常見的寫法,這個寫法會返回data return ( (appEvents[name] || []).map(function(cb) { var result = cb(appState, appActions, data); if (result != null) { data = result; } }), data ); }
基於目前這兩點,能夠得出:
<Header />
被轉爲了,h(emit, null)
h
返回的就是children
,也就是一個[]
<Header />
做爲子節點,會再次被h
整理一次,參照h
對數組的處理,能夠得出[]
直接就被忽略掉了render
的節點的子節點中根本就沒有<Header/>
的出現這種實現方式能夠說是很是的很差,侷限性也很大,想一想可不能夠利用其餘方法實現:
oncreate
實現// 改進Header組件 const Header = (root) => app({ root, ...同上 }); // 改進引入方式 view: (state, actions) => ( <main> <div oncreate={(e) => Header(e)}></div> <h2>{state.count}</h2> </main> ),
這種方式,利用了oncreate
方法,掛載後,載入組件(能夠考慮經過代碼分割將組件異步加載)
mixins
hyperapp
支持傳入mixins
,既然自然的支持這個,那麼將一個組件進行兩方面分割:
view
,利用「木偶組件」實現feature
,利用mixins
實現export const HeaderView = ({ text }) => ( <header>{text}</header> ); export const HeaderMixins = () => ({ state: // 同上 actions: // 同上 events: // 同上 });
使用方式:
import { HeaderView, HeaderMixins } from './HeaderView'; ... state: { count: 0 }, view: (state, actions) => ( <main> <HeaderView text={state.caption} /> <h2>{state.count}</h2> </main> ), mixins: [ HeaderMixins() ] ...
mixins
會將其屬性與自己進行一個並操做,能夠理解爲Object.assign(key, mixins[key])
,對於events
來講,爲一個典型的發佈/訂閱模式,events
的某一種類型對應一個數組,emit
時會將其所有執行。本人認爲利用這種方式能夠實現出一個比較符合框架本意的」智能「組件,可是仍然有些問題,就是state
,在使用這個組件時不得不去看一下組件內部的state
叫什麼名字,並且容易形成同名state
衝突的狀況。
整體來講,hyperapp
是一個小而美的框架,值得咱們來折騰一下,以上均爲本人理解,若有錯誤還請指出,不勝感激~
我所在團隊(工做地點在北京)求大量前端(社招 or 實習),有意者可發簡歷至:zp139505@alibaba-inc.com