Reflux 101

一点背景 React在设计之初就只关注在View本身上,其余部分如数据的获取,事件处理等,全然不在考虑之内。不过构建大型的Web前端应用,这些点又实在不可避免。所以Facebook的工程师提出了前端的Flux架构,这个架构的最大特点是单向数据流(后面详述)。但是Flux本身的实现有很多不合理的地方,比如单例的Dispatcher会在系统中有多种事件时导致臃肿的switch-cases等。 这里是Facebook官方提供的提供Flux的结构图。 其实整个Flux背后的思想也不是什么新东西。在很久之前,Win32的消息机制(以及很多的GUI系统)就在使用这个模型,而且这也是一种被证实可以用来构建大型软件的模型。 鉴于Flux本身只是一个架构,而且Facebook提供的参考实现又有一些问题,所以社区有了很多版本的Flux实现。比如我们这里会用到的Reflux。 Reflux简介 简而言之,Reflux里有两个组件:Store和Action。Store负责和数据相关的内容:从服务器上获取数据,并更新与其绑定的React组件(view controller);Action是一个事件的集合。Action和Store通过convention来连接起来。 具体来说,一个典型的过程是: 用户的动作(或者定时器)在组件上触发一个Action Reflux会调用对应的Store上的callback(自动触发) 这个callback在执行结束之后,会显式的触发(trigger)一个数据 对应的组件(可能是多个)的state会被更新 React组件检测到state的变化后,会自动重绘自身 一个例子 我们这里将使用React/Reflux开发一个实际的例子,从最简单的功能开始,逐步将其构建为一个较为复杂的应用。 这个应用是一个书签展示应用(数据来源于我的Google Bookmarks)。第一个版本的界面是这样的: 要构建这样一个列表应用,我们需要这样几个部分: 一个用来fetch数据,存储数据的store (BookmarkStore) 一个用来表达事件的Action(BookmarkActions) 一个列表组件(BookmarkList) 一个组件条目组件(Bookmark) 定义Actions var Reflux = require('reflux'); var BookmarkActions = Reflux.createActions([ 'fetch' ]); module.exports = BookmarkActions; 第一个版本,我们只需要定义一个fetch事件即可。然后在Store中编写这个Action的回调: 定义Store var $ = require('jquery'); var Reflux = require('reflux'); var BookmarkActions = require('../actions/bookmark-actions'); var Utils = require('../utils/fetch-client'); var BookmarkStore = Reflux.createStore({ listenables: [BookmarkActions], init: function() { this.onFetch(); }, onFetch: function() { var self = this; Utils....

November 9, 2015 3 min

这些年你都学了些什么

数据可视化 多年下来,我的Google Bookmarks里已经有近万条的书签。大部分内容是我在读过一遍之后就收藏起来的,也有很多看了一眼之后,觉得不错,然后收藏起来准备以后读的(当然,你也知道,再也没有打开过)。 有没有一个方法可以让我以可视化的方式,看到这些年我都学了那些东西呢?将书签列表作为源数据,然后将这些信息可视化出来会非常有意思:比如收藏夹中的热门词是什么,哪段时间收藏了多少条的书签(学习投入程度趋势)等等。 下图是我的书签中,排行前30的关键字排序。可以明显的看出,我对于JavaScript的喜爱程度相当高,对美食的喜爱也超过了python和linux。 这里我将使用python,结合python的一些库来实现书签可视化。简而言之,整个过程可以分成这样几个步骤: 将Google Bookmarks导出为本地文件 将书签文件解析为容易处理的内部格式(比如python的dict等) 由于书签中会有中文的句子,所以会涉及到分词 统计词语的频率,并绘制成图标 数据源 Google Bookmarks本身可以直接导出成HTML文件。该HTML文件包含了时间戳和书签的标题,我们可以通过python的库BeautifulSoup将HTML中的文本抽取出来: from bs4 import BeautifulSoup def load_bookmarks_data(): soup = BeautifulSoup(open('bookmarks_10_21_15.html').read(), "html.parser") return soup.get_text() if __name__ == "__main__": print load_bookmarks_data() BeautifulSoup提供非常好用的API来抽取结构化文档中的内容。 分词 BeautifulSoup获得的是一条条独立的句子,我们需要使用分词器将所有的句子分解成片段。这里我使用了jieba(结巴分词)分词器来完成这个任务: import jieba data = "我在出报表,你的博客写的怎么样了" seg_list = jieba.cut(data, cut_all=False) for seg in seg_list: print seg 将会输出: 我 在 出 报表 , 你 的 博客 写 的 怎么样 了 我们定义一个方法来将上一步中的文本分词: def extract_segments(data): seg_list = jieba....

November 1, 2015 2 min

Mapfile解析器

前言 Mapfile是MapServer用来描述一个地图的配置文件。它是一个很简单的声明式语言,一个地图(Map)可以有多个层(Layer),每个层可以有很多属性(键值对)。在一个层的定义中,还可以定义若干个类(Class),这个类用以管理不同的样式(Style)。而每个类或者样式都可以由若干个属性(键值对)。 这里有一个实际的例子: LAYER NAME "counties" DATA "counties-in-shaanxi-3857" STATUS default TYPE POLYGON TRANSPARENCY 70 CLASS NAME "polygon" STYLE COLOR 255 255 255 OUTLINECOLOR 40 44 52 END END END 最简单的层的定义 最简单的情形是,我们定义了一个层Layer,但是没有指定任何的属性: LAYER END 我们期望parser可以输出: {layer: null} 要做到这一步,首先需要定义符号LAYER和END,以及一些对空格,非法字符的处理等: \s+ /* skip whitespace */ \n|\r\n /* skip whitespace */ "LAYER" return "LAYER" "END" return "END" <<EOF>> return 'EOF' . return 'INVALID' 对于,空格,回车换行等,我们都直接跳过。对应的BNF也非常简单: expressions : decls EOF {return $1;} ; decls : LAYER END {$$ = {layer: null}} ; 为层添加属性 接下来我们来为层添加Name属性,首先还是添加符号NAME和对字符串的定义。这里的字符串被定义为:由双引号括起来的所有内容。...

October 5, 2015 3 min

如何手写一个解释器

在代码编写中,很多时候我们都会处理字符串:发现字符串中的某些规律,然后将想要的部分抽取出来。对于发杂一些的场景,我们会使用`正则表达式`来帮忙,正则表达式强大而灵活,主流的变成语言如`Java`,`Ruby`的标准库中都对其由很好的支持

September 30, 2015 3 min

可视化你的足迹

可视化你的足迹 上一篇文章讲述了如何在服务器端通过MapServer来生成地图。虽然MapServer发布出来的地图是标准的WMS服务,但是我们还需要一个客户端程序来展现。我们在上一篇中,通过一些小脚本将照片中的地理信息抽取到了一个GeoJSON文件中。GeoJSON是一种向量图层格式,向量数据可以在服务器端绘制成栅格图,也可以直接在客户端canvas上直接绘制出来。当数据量比较大的时候,我们更倾向于在服务器端绘制,这样只需要在网络上传输一张图片(而且可以做缓存)。大数据量的客户端绘制在性能上会比较差(当然现在已经有了一些新的解决方案,我们后续再细谈),特别是有用户交互时,会出现明显的卡顿。 在本文中,我将分别使用客户端和服务端绘制的两种方式来展现两种不同的地图:使用OpenLayers直接在客户端绘制矢量图,以及使用Leaflet来展示在服务器端绘制好的栅格图层。 使用OpenLayers3展示GeoJSON 展示GeoJSON非常容易,也是一种比较直接的方式,只需要将GeoJSON文件发送到前端,然后直接通过客户端渲染即可。使用OpenLayers3的API,代码会是这样: $.getJSON('data/places-ive-been-3857.json').done(function(geojson) { var vectorSource = new ol.source.Vector({ features: (new ol.format.GeoJSON()).readFeatures(geojson) }); }); 客户端发送一个ajax请求,得到GeoJSON数据之后,将其转换成一个向量类型。OpenLayers定义了很多中格式读取器,比如KML的,GML的,GeoJSON的等等。然后我们可以定义一个样式函数: var image = new ol.style.Circle({ radius: 5, fill: null, stroke: new ol.style.Stroke({color: '#f04e98', width: 1}) }); var styles = { 'Point': [new ol.style.Style({ image: image })] }; var styleFunction = function(feature, resolution) { return styles[feature.getGeometry().getType()]; }; 这个函数会应用到向量集的Point类型,将其绘制为一个红色,半径为5像素的圆圈。有了数据和样式,我们再来创建一个新的向量,然后生成一个新的图层: var vectorLayer = new ol.layer.Vector({ source: vectorSource, style: styleFunction }); 创建地图,为了方便对照,我们加入了另外一个ol.source.Stamen图层作为参照。这样当缩放到较小的区域时,我们可以清楚的知道当前的点和地物的对照,比如道路名称,建筑名称等,从而确定目前的位置。这是一种非常常见的GIS应用的场景,但是需要注意的是,不同的图层需要有相同的空间映射方式,OpenLayers默认才用EPSG:3857,所以需要两者都采用该投影: var map = new ol....

September 20, 2015 1 min