摘要:写在最后总体来说,是一个小而美的框架,值得我们来折腾一下,以上均为本人理解,如有错误还请指出,不胜感激一个硬广我所在团队工作地点在北京求大量前端社招实习,有意者可发简历至
写在前面
没错,又是一个新的前端框架,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
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/88804.html
摘要:,大家好,好久不贱呢最近因为看了一些的小说,整个人都比较致郁就在昨天,我用了一天的时间写了,又一个小而美的前端框架可能你觉得,有了和,没必要再写一个了我觉得我还是想想办法寻找一下它的存在感吧先看的组件化方案最先看到的应该是。 halo,大家好,好久不贱呢! 最近因为看了一些 be 的小说,整个人都比较致郁::>__+ {state.count--}}>- ...
摘要:,大家好,好久不贱呢最近因为看了一些的小说,整个人都比较致郁就在昨天,我用了一天的时间写了,又一个小而美的前端框架可能你觉得,有了和,没必要再写一个了我觉得我还是想想办法寻找一下它的存在感吧先看的组件化方案最先看到的应该是。 halo,大家好,好久不贱呢! 最近因为看了一些 be 的小说,整个人都比较致郁::>__+ {state.count--}}>- ...
摘要:专有的内容更少,而更多符合标准的成分。当前标签实例的方法被调用时当前标签的任何一个祖先的被调用时更新从父亲到儿子单向传播。相对来说,微型场景会更适合,不想要太多的外部依赖,又需要组件化数据驱动等更现代化框架的能力。 Riot.js是什么? Riot 拥有创建现代客户端应用的所有必需的成分: 响应式 视图层用来创建用户界面 用来在各独立模块之间进行通信的事件库 用来管理URL和浏览器回...
摘要:专有的内容更少,而更多符合标准的成分。当前标签实例的方法被调用时当前标签的任何一个祖先的被调用时更新从父亲到儿子单向传播。相对来说,微型场景会更适合,不想要太多的外部依赖,又需要组件化数据驱动等更现代化框架的能力。 Riot.js是什么? Riot 拥有创建现代客户端应用的所有必需的成分: 响应式 视图层用来创建用户界面 用来在各独立模块之间进行通信的事件库 用来管理URL和浏览器回...
阅读 1310·2019-08-30 15:44
阅读 1981·2019-08-30 13:49
阅读 1655·2019-08-26 13:54
阅读 3487·2019-08-26 10:20
阅读 3246·2019-08-23 17:18
阅读 3295·2019-08-23 17:05
阅读 2133·2019-08-23 15:38
阅读 1014·2019-08-23 14:35