edfward

我的 Yelp Bookmark Map

本着实用和学习的态度,最近做了一个加强版 yelp 书签浏览站。repo 戳这里,demo 戳这里

初衷很简单:当通过各种渠道了解到一个可能还不错的餐厅于是兴致勃勃地在 yelp 上标记之后,用不了多久就会忘记当初是怎么知道这家店的。有时候是午餐间同事提起,有时候是来自关注的微信公众号或者网站推送,甚至有时候是热心的 uber 司机倾情推荐——然而当我看到 yelp 上一个个孤零零的书签时,这些有趣的语境往往就遗失了…(另外一个理由是 yelp 的网页版 map 实在太难用 :(

所以就想做一个私用的加强版 yelp 书签。刚好自己初到 SF 时写的觅食用 slack 机器人会通过 yelp 网页抓取书签(很遗憾官方没有提供此类 API),便打算利用这个信息,重新画地图并加上小笔记功能快速开发出个原型出来,当一次前端小能手 :-)

二月末有了这个想法,最开始的时候用 leaflet 简单画了画地图后就一直搁浅着没有动手做下去;三月中发现鄙司开源了一个 react 的 map component,便想着用 react 重新架构整套逻辑;四月初总算有所眉目,开始实现。上面的截图大概是四月中下旬的状态,当时整个页面的雏形已经有了,而截至写此文的时间(05/03)这个 plan 的各个 bug/feature 都搞定了 ;)

前端 Frontend


最难的部分果然是开头。

首先把整个网页分成 map 和侧边栏显示书签(侧边栏本质上就是个稍微 fancy 点的 todo list),map 端显示标记过的书签位置,书签栏用小卡片显示基本信息比如餐厅种类。刚搭起来的时候效果如下:

第一个想要有的效果是同步点击:选中地图上的小红点可以令侧边栏 scroll 到对应的书签,选中侧边栏的书签能让地图移动到对应的小红点。以前的话自己大概会在 master component 定义好各种 event handler 然后传给这两个 component,不过这次选择了入 redux 的坑。题外话:State of the Art JavaScript in 2016 是篇非常赞的趋势/总结文,也是坚定我尝试一下这些新东西的诱因之一。

(其实私以为 redux 对我的小网页来说是 overkill,统一的 state 管理和同步可能对规模稍微大一点的项目更有帮助,对我这种层面的小东西来说反而增加了复杂程度。不过这并不能阻止我瞎折腾:P

首先 follow 了 redux 作者的 Getting started with Redux,算是深入浅出的教学视频…但其实学习曲线还是有的,按 reducer, action, component 和 container 组织代码 conceptually 简化了 app 的逻辑,但另一个后果是增加了学习成本——纯粹通过阅读代码来了解架构的难度提升了。比较关键的一步是了解各个部分到底做了什么:

  1. Reducer: 给定 App 本身的状态集合和一个 action,返回新的状态集合——这样的函数就是 reducer。
  2. Action: 顾名思义,app 所支持的操作(与 UI 无关)。按之前提到的效果来说 select bookmark是一个 action,而点击小圆点和点击书签卡片都会触发它。
  3. Component: 在 redux 的架构里 component 基本只是作为 presentational element 存在。它们不知道具体的 action 是什么,而只是通过props 将传入的函数与内部的 event(比如点击)串起来。
  4. Container: 将 App 的状态集合以及 action 的 dispatch 映射到对应 component 的 props。

具体一点,我要做的书签地图的状态集合可以总结为:

state = {
  bookmarks,  // A list of bookmarks, containing 
              // names, ratings and others.
  selected,   // Currently selected bookmark.
}

而 action 包括书签的初始化、书签的选中,以及之后的书签自定义内容编辑和书签的搜索。Component 分成了 map 和 map 里的小圆点,书签栏和每个书签本身。Container 除了把 state map 给 component,也指定好了具体的事件回调,有些是单纯的 dispatch action 而有些涉及到了 api 的调用。

如此这般,整个网页的架构就算是定下来了。

Data model 来说,每一个书签有三个自定义内容:context, review 和 mark。如前文所言,context 大概用来记录为什么标记这个店以及这个店可能会让人惊喜的地方;review 就是私人的评论,比如和谁去了、为什么去、期间发生了什么有趣的故事之类;mark 就是个很基本的好评/差评,方便客户端 filter。

最后,虽然有点不情愿,但还是用了 material-ui

后端 Backend


稍微扩展了之前 slack 机器人的后端,顺便用来 serve 这个书签应用。当初选了 Hack 和 HHVM,可惜纯粹只是当 php 使(大材小用的感觉…);刚开始的时候书签相关的自定义信息是直接存成一个 JSON 文件,读取和写入都会 serialize/deserialize 它(因为内容少文件小)…后来觉得这实在是懒过头了于是老老实实迁移到 mysql,建表建索引重写 API,也算是有了相对比较正经的后端。同时也设好 cron job 每天 dump 数据库上传到 dropbox 里去。

Authentication 这边也用了最省事的办法——检查 cookies。毕竟不想让别人随意删掉自己的内容。

其余的话,似乎就没什么值得一提的了。这个应用里后端真的只是随便写写,能把内容存下来就好。

More features


正在做以及接下来的功能大概是屏幕左边会有个 menu:

menu

提供的功能可能会有搜索(餐厅名字或者种类),filter(好评、差评或者未评),ranking(没想好,可能是按当前位置或者用户 drop 的 pin 算距离),以及小小的 about。


总结一下,感觉写这个 side project 的过程还是很有趣的。平时上班主要做后端开发(python 和 node,以及偶尔做做 data science 写写 notebook),所以觉得额外的时间能碰碰前端对自己蛮有帮助的。还年轻,趁找到自己专精的方向之前多多涉猎一些软件开发的各方各面吧。不是有句老话吗,

德智体美劳全面发展。——小学老师