没错,又是一个新的前端框架,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