博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小而美的框架—hyperapp
阅读量:6143 次
发布时间:2019-06-21

本文共 5972 字,大约阅读时间需要 19 分钟。

写在前面

没错,又是一个新的前端框架,hyperapp非常的小,仅仅1kb,当然学习起来也是非常的简单,可以说是1分钟入门。声明式:HyperApp 的设计基于Elm Architecture(这也意味着组件更多的是纯函数),支持自定义标签以及虚拟DOM。下面先来看下怎么使用:

hello world

import { h, app } from 'hyperapp';app({    state: {        count: 0    },    view: (state, actions) => (        

{state.count}

), actions: { down: state => ({ count: state.count - 1 }), up: state => ({ count: state.count + 1 }) }});复制代码

这样就完成了一个Counter,基本由stateviewactions构成:

  • state: 与react中的如出一辙,state的改变会引起重新渲染
  • view: 相当于react中的render
  • actions: 对state进行改变

h相当于reactcreateElement,来看下h接收的参数:

  • tag: 标签名,或者一个函数,传入函数也就意味着无状态组件
  • data: 相当于react中的props
  • children: 子节点

需要注意的一点是,hyperapp并不支持boolean类型,对于boolean类型会忽略,使用时注意将其转化为string类型,如:

Test {
true}

// Test

Test {String(true)}

// Test true复制代码

至于为什么?可以参见

生命周期

下面来看一下其生命周期,对于hyperapp的整个运行过程,可以参见下图:

执行流程图

  • load:相当于reactcomponentWillMount
  • update:相当于reactcomponentWillUpdate
  • render:调用view函数之前调用
  • action:调用actions之前,一般用来进行log
  • resolve:调用actions之后,对于一个异步操作来说,actions返回一个promise,生命周期resolve来处理,返回一个函数update => result.then(update),即框架内部调用update来更新state,重新渲染
    具体代码可以参考:
// 生命周期: action  ->  actions[key]  ->  resolve// 异步请求需要利用resolveemit('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官方给出的自定义组件的方式仅仅有这种,但是所有的组件都要是无状态的???答案当然是否定的,如何实现“智能组件”是一个问题:

“智能”组件

利用app方法实现

我们通常的期望业务组件具有一些基本的功能,比如数据获取展现这种:

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做了什么?
// 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,就像在reactjsx翻译为createElementhyperappjsx会被翻译为如下形式:

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 />来说,tagapp函数返回的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) => (    
Header(e)}>

{state.count}

),复制代码

这种方式,利用了oncreate方法,挂载后,载入组件(可以考虑通过代码分割将组件异步加载)

“木偶”组件+mixins

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) => (    

{state.count}

),mixins: [ HeaderMixins()]...复制代码

mixins会将其属性与本身进行一个并操作,可以理解为Object.assign(key, mixins[key]),对于events来说,为一个典型的发布/订阅模式,events的某一种类型对应一个数组,emit时会将其全部执行。本人认为利用这种方式可以实现出一个比较符合框架本意的”智能“组件,但是仍然有些问题,就是state,在使用这个组件时不得不去看一下组件内部的state叫什么名字,而且容易造成同名state冲突的情况。

写在最后

总体来说,hyperapp是一个小而美的框架,值得我们来折腾一下,以上均为本人理解,如有错误还请指出,不胜感激~

一个硬广

我所在团队(工作地点在北京)求大量前端(社招 or 实习),有意者可发简历至:zp139505@alibaba-inc.com

转载于:https://juejin.im/post/59d8eecef265da066a106c40

你可能感兴趣的文章
Nginx配置URL转向tomcat
查看>>
极客Web前端开发资源大荟萃#001
查看>>
让div固定在某个位置
查看>>
Java开发环境Docker镜像
查看>>
从无到有,WebService Apache Axis2初步实践
查看>>
任务调度(一)——jdk自带的Timer
查看>>
UIKit框架(15)PCH头文件
查看>>
整理看到的好的文档
查看>>
Linux磁盘管理和文件系统管理
查看>>
linux运维人员的成功面试总结案例分享
查看>>
Windows DHCP Server基于MAC地址过滤客户端请求实现IP地址的分配
查看>>
命令查询每个文件文件数
查看>>
《跟阿铭学Linux》第8章 文档的压缩与打包:课后习题与答案
查看>>
RAC表决磁盘管理和维护
查看>>
Apache通过mod_php5支持PHP
查看>>
发布一个TCP 吞吐性能测试小工具
查看>>
java学习:jdbc连接示例
查看>>
PHP执行批量mysql语句
查看>>
Extjs4.1.x 框架搭建 采用Application动态按需加载MVC各模块
查看>>
Silverlight 如何手动打包xap
查看>>