但本文想要讨论的是 Tailwind CSS 这个框架,我用 Tailwind CSS 重写了整个博客的 UI, 减少了 90% 的 CSS 代码,开发时间加起来只有短短数小时,就完成了这个 Mobile First 的 Redesign (如果这也算 design 的话).
传统的 CSS 框架 —— 如 Bulma 之流,会预设很多组件样式,例如你只需给 <button>
一个 btn
的 class name, 你就能得到一个好看的 button. 但 Tailwind 不同,它没有提供任何的预设样式,
所以 Tailwind CSS 声称自己为:
A utility-first CSS framework for rapidly building custom designs.
Instead of opinionated predesigned components, Tailwind provides low-level utility classes that let you build completely custom designs without ever leaving your HTML.
—— Tailwind CSS 官网
可以看出,Tailwind CSS 的目的不是直接把设计过的东西给你,而是帮助你更快地实现你的设计。我想大家或多或少也有对流行 UI 框架审美疲劳的感受,Tailwind CSS 就是为此而设的。
为了解释 utility-first 的含义,我想了很久 utility 如何翻译比较信达雅,但我没有想到。所以下面我将通过亲身体验来解释 utility-first 这一词。
一直以来我很怕写 CSS, 一是我没有什么设计天赋,我只有审美天赋 —— 我知道什么是好看,但不知道怎么做才会好看。二是写 CSS 很无聊 —— 为了给一个元素定位,我需要给 HTML 元素命名,然后到样式文件写一堆无聊又重复的 CSS, 但又不想用现有框架写好的设计。 最怕的是写响应式的页面,一想到 media query 我就很头疼。
写自己博客的 CSS,时常遇到多个元素的样式有些交集:
<div class="foo">
字体颜色是黑色,需要加粗且文本居中
</div>
<div class="footer">
字体颜色是灰色,需要加粗且文本居中
</div>
遇到这种情况,我有以下选择:
.foo
.footer
都写上 font-weight: bold; text-align: center;
font-weight: bold; text-align: center;
font-weight: bold; text-align: center;
单独写成一个 class, 应用到两个 div 上这些选择我都不喜欢,我更喜欢像这样:
<style>
.text-center {
text-align: center
}
.font-bold {
font-weight: bold;
}
</style>
<div class="foo text-center font-bold">
字体颜色是黑色,需要加粗且文本居中
</div>
<div class="footer text-center font-bold">
字体颜色是灰色,需要加粗且文本居中
</div>
我喜欢像这样把一些常用的 CSS 原子化,这样可以直接通过 class name 复用到任何的元素。这些原子化的通用的 class 我们可以称为 utility. Tailwind CSS 提供的就是一些 utility, 这就是 utility-first 的含义。
在我博客首页有一个这样的导航:
HTML 结构如下:
<nav>
<div>
<div :key="navItem.title" v-for="navItem in $themeConfig.navs">
<a class="hover:text-gray-900 text-center" :href="navItem.url">
<div>{{ navItem.title }}</div>
<div>{{ navItem.alias }}</div>
</a>
</div>
</div>
</nav>
使用 Tailwind CSS, 我不必费神给几个 div 命名,也不用给 div 写一堆 flex 布局,Tailwind CSS 提供了 flex 而已要用到的预设:
<nav>
<div class="flex flex-col sm:flex-row w-full justify-center p-8">
<div :key="navItem.title" v-for="navItem in $themeConfig.navs" class="mt-6 sm:mt-0 sm:ml-6 sm:mr-6 text-gray-600">
<a class="hover:text-gray-900 text-center" :href="navItem.url">
<div>{{ navItem.title }}</div>
<div class="text-center text-sm font-serif">{{ navItem.alias.toUpperCase() }}</div>
</a>
</div>
</div>
</nav>
如果你没看过 Tailwind CSS 的文档,你可能对这些 class 比较模糊,在这里我按顺序稍作解释:
flex
: 声明这是一个 flexflex-col
: 声明 flex-direction
为 column
justify-center
: 声明 justify-content
为 center
p-8
: padding 为 8 个单位mt
: margint-top / ml
: margin-left / mr
: margin-right几个 class 就能完成 flex 布局。
你还会注意到有 hover:text-gray-900
, 这代表在 hover 的时候,color 为 gray.
这个导航在小屏幕时会变成竖向:
在上面的代码可以看到,这是通过 sm:flex-flow
实现的,意思是当屏幕大小超过 sm
时,就用 flex-flow
. 在 Tailwind CSS 的 Responsive utility 里,预设了 sm
, md
, lg
, xl
几个大小。这个 utility 减少了非常多的响应式设计代码量。
除了自己博客的例子,我特意到 dribbble 随便搜了一个设计来实现:
Codepen: https://codepen.io/djyde-1474473388/pen/RwwYPEv?editors=1000#0
See the Pen tailwind-signup-form by Randy (@djyde-1474473388) on CodePen.
Tailwind CSS 满足了我几点:
第二点很重要,也是为什么使用 Tailwind CSS 可以很容易做到好看的设计。读过 Refactoring UI 这本小书里面提到,Bad design 有时候是因为间距大小,字体大小,颜色的不统一导致的。如果没有一个固定的 Design system 规定了可以选用的这些参数,设计容易变得混乱。例如一个页面里面如果同时有 12px, 11px, 10px, 9px 大小的字,就会很难看。
Tailwind CSS 的 utility 对大小都有预设,像字体大小有 text-sm
, text-md
, text-lg
等等,颜色有 gray
, pink
, orange
等等(当然有可以自行扩展),这其实已经是一个很好的 Design system.
但 Tailwind CSS 毕竟不是一个组件框架,开发现代 Web App 的时候,只有 CSS 显然是不够的。如果选择 Tailwind CSS, 那就代表很有可能很多 (React, Vue) 组件需要自己动手实现。
另一个需要注意的地方是使用 Tailwind CSS 有一定的学习曲线,刚开始不可避免要不断翻文档,但是用她做一个页面之后基本就记住了,我的经验是用了一两天就不太需要看文档了。有点像学习 vim, 如果因为有一定的学习曲线所以错过这么好的东西,那未免太可惜了。
强烈推荐对 UI 设计感兴趣的朋友读一读 Refactoring UI, Refactoring UI 的两位作者,一个是 Tailwind CSS 的作者,一个是 Tailwind CSS Design system 的设计。
如果觉得这本书太贵,那至少读一读这篇 7 Practical Tips for Cheating at Design.
]]>以前喜欢到香港 hmv 买 CD. hmv 倒闭了,就去 CD warehouse.
无可否认,在串流音乐平台轻轻一点,连接蓝牙,是最方便的听歌方式,也是我日常主要的听歌方式。然而在闲暇时,选一只想听的碟,拆开硬盒,放进盘口,戴上耳机,闭上眼,不做任何事,却是我最享受的时刻。
因为身处异地工作,家里的 CD 机不方便运送,便有了买一台便携式 CD 机的念头。可惜现今已再没厂商愿意制造便携式 CD 机,只能在二手平台找找老古董,于是这台 SONY Discman D-9 就到了我手上了。
Discman D-9 是 1989 年发行的机器,距今已经有 30 年整了。收到这台机器时,成色十分漂亮,皮套也没有任何破损,实在难得。
打开皮套,便是方正的机体。外壳是冰冷的铁而不是塑料。
D9 是一台支持 MEGA BASS 的播放器,是 SONY 研发的低音补偿功能。在机器左侧有三段开关控制三等补偿强度。
播放器由两节 5 号电池驱动。在播放器前面有一块液晶屏幕。
插上耳机。
合盖。
按下播放键,就是另一个世界了。
]]>为什么你总是无法坚持写博客?和很多「为什么无法坚持做某事」的问题一样,我们得先弄清楚为什么要写博客。
我听过很多人想建博客的理由是想把博客当成笔记本,记录自己学到的东西、遇到的问题和解决方法。这是一个让博客最终走向荒废的理由,因为你在笔记本就可以做到类似的事,除非你记录的问题有你自己独到的思考,否则网友们更愿意去看 StackOverflow.
写博客的第一个意义是让陌生人通过博客了解到你是什么人、你在想什么、你做了什么。不妨试想一下,如果你正准备找工作,一位素不相识的 HR 如果要了解你,光是一份简历就能让 HR 知道你的价值吗?如果你有一个精彩的博客,HR 也许能通过你的博客了解你对技术的属于自己的思考、你在业余时间在想的事情是什么。这比简历本身更像一份立体的简历。
以下是一些你可以选择的题材:
这些题材的共同点是它们都是属于你的独特的内容。
当然,这一切的前提是你本身需要有一定的表达欲,你才会想不断地去写博客。博客只是其中一个表达渠道。我自己是一个很有表达欲的人,我乐意分享我的想法,我的生活。然而,试图通过建立博客去培养表达欲不是一个行之有效的方法,这就像很多本身不喜欢读书的人试图通过 Kindle 培养自己的读书习惯,大多数都是失败收场。你应该是因为喜欢读书所以买 Kindle, 正如你应该是想表达你的东西所以你写博客。
不是每个人都有表达欲,但很多有表达欲的人不敢表达,因为在担心一些不应该担心的问题。
除非你的博客的定位就是追求思想深度,否则不必苛求每篇文章都有所谓的深度,有时候即使是日常生活的小记也有其价值,读者能从中发现很多自己不知道的东西。例如 JustZht 的 一些生活小记 .
记得多年前我刚开始写博客的时候知道了罗磊和 罗磊的博客。虽然当时罗磊是一个前端工程师,但博客大多数文章都不是技术,而是一些数码产品和生活方式,例如 他跑马拉松的记录。
这些内容没有所谓的「深度」,但让我看到了不同的生活方式。每个人都有自己独特的生活方式,不妨把它分享出来让更多人从中获得借鉴。
并非写长文章才叫写博客,李如一的 一天世界 也不见得每篇文章的篇幅都长,但内容有其独特的思维方式,也能引发思考。这就够了。
写独立博客和写公众号不同,你不必追求「十万加」。你要追求的是一个陌生人只要进入一个网站就能知道你是什么人。你不靠写博客吃饭。人们总说 You are what you read, 当人们无法得知你在读什么的情况下,那么我更要说: You are what you write.
文章写完,分享给你喜欢的圈子,论坛,回复一些评论。慢慢地就会在你的圈子积累一些读者。
JerryQu 的博客 已经两年没有更新,但两年后的现在依然可以说这是一个很好的博客。
你的博客不是资讯网站,不必在意更新频率。
希望本文可以引导各位潜在的博客作者决心建立自己的独立博客。独立博客有免于献媚的自由,有排版自由,有修改删除自己说了算的自由。维护一个有意义的博客不是一件难事,用现成的平台或用框架,在网站主页写上你的个人简介,然后保有分享生活和想法的习惯,不限篇幅,不限频率(即使是一年一篇),按时续费域名,保持网站正常访问,
记住,一个博客的死掉不是不再更新,而是无法访问。
这是一封 2014 年我写给未来的自己的信。当时在上大一,是上职业规划课时老师布置给我们的一个作业,写完我们就交给老师保存,他会在我们大学毕业后返还。
老师通过微信联系我说有封信在他那里,并拍了这张照片(图为我用 Office Lens 处理后)。我早已忘掉我曾写过这封信,看完后我就全记起来了。
记起来刚上大学时对未知的憧憬,记起来在教室听无聊的网页设计课,记起来退学那天给宿舍阿姨还了钥匙后坐上中巴回广州市区的时候听的那首《一路向北》。
]]>本文主要记录了我如何从 UC News 运营后台孵化出这个内部框架,以及其背后的设计理念。虽然 Cans 没有开放源代码。但我认为相比这些工具的源码,那些我在开发这些工具背后的理念、思考,更有被分享的价值。也算作是对我在 UC 工作的一个总结。遂有此文。
我们在做 UC News 运营后台的时候,面临的最大问题是:当有新的业务需要我们团队支撑开发一个新运营后台的时候,我们应该怎么做?
这种情况在不断地发生。2017 年我们的 UC News 运营后台趋于成熟,业务需求响应的速度在不断提高。越来越多业务方问我们同一个问题:「我们 xxx 业务想有一个运营后台,我们可以快速接入吗?」
如何让 UC News 运营后台的开发经验可以被快速地复制出去,帮助更多业务?在回答这个问题之前,我想先说说我是怎么设计 UC News 运营后台的前端工程以提高需求响应速度的。
第一点是保证业务开发尽量不被构建环节干扰,只须关注业务逻辑本身。这一点很好保证,因为构建配置基本是不需要更改的。
第二点是降低新页面接入的成本。所谓的成本指的是:
我把这两点变成配置项,可以直接在项目里的 routes 文件配置。这样新页面接入只需配置菜单项的标题和路由,以及对应的页面组件即可。
第三点是规范项目目录结构。这一点我在 Egg 上受到很大的启发。在内部,我们的 Node.js 系统都使用 Egg. 我借鉴 Egg 把项目配置、路由配置、应用启动代码等作了规范:
要用这一套模式赋能更多不断冒出的新业务,首先要做的是统一。
在社区上做开源软件和做公司内部的软件不同,前者通常需要考虑兼容性。处理兼容性的代价是越来越复杂的配置项。但做内部软件,则可以通过「统一」简化配置(甚至无需配置)。例如,两个业务如果分别使用 less 和 sass, 那么在新建项目时,两个业务都要各自配置。相反,如果早已约定所有业务都使用 less, 那么 less 的配置可以固化到统一的工具里,两个业务都不需配置。
首先是技术栈的统一:
技术栈的选型背后没有什么高深的思量,不过是这一套在 UC News 运营后台经过了考验:Ant.Design 的组件覆盖了 90% 的需求;纯 EcmaScript 写运营后台这种复杂应用是灾难,尤其在新人接手的时候;
技术栈的统一带来的是构建配置可以全部收敛,当然除了技术相关的工具,还有业务层面的工具可以统一收敛:
做好了这些统一的准备,我开始开发一个叫 Cans 的前端解决方案,它的宗旨是快速搭建中后台类的项目。
「开箱即用,没有多余的东西」是我从 zeit.co 领会到的软件设计哲学。如果说一个框架设计得美,那么其实是说它用最简单且符合直觉的接口封装了最复杂的逻辑。
和 next.js 一样,我希望开发者只要用 cans start
这一条命令,就能开始开发一个页面。
在最基本的应用,cans start
的背后会做这样的工作:
也就是收集所有运行时将要用到的数据,然后生成一个入口 js 文件。
另外,在构建中还引入了 tree shaking, code splitting (with react-loadable) 等等优化手段。
构建时分析的应用数据,可以在运行时、页面中通过 import app 实例来获取:
这种注入机制是为了提供扩展性,为了通过这种扩展性建立「生态」,Cans 引入了 addon 机制,以一个打点 addon 为例:
开发者可以在 npm 发布以 cans-addon-
为前缀的库贡献生态。
Cans 自带了一些业务常用的 addon:
app.cookies.get
和 app.cookies.set
读写 cookiesapp.storage
做离线数据持久化(如本地数据缓存)虽然中后台的布局千篇一律,但为了尽量覆盖所有定制化的需求,Cans 也开放了 theme, 开发者可以定制自己的主题,用 app.theme()
引入。也可以在 npm 上贡献主题。像我们最常用的运营后台 theme:
而在业务层,尽可能将可复用的业务组件封装出去,让更多业务可以使用。如 antd-data-table 这个业务组件,就是从 UC News 运营后台独立出来的。
以上是 Cans 这个框架的大概模样, 或有其它细节,但不是本文的重点。
在设计这个框架的时候,我无时无刻在考虑的是如何使开发者要写的代码越来越少,同时开放的接口是要符合开发者直觉的。就像「建立一个页面就是创建一个页面文件然后运行 cans start
一样简单。
几年前我读了《乔布斯传》,使我对用户体验有了极致的追求,也把这种追求带到了软件开发。这种体验不是一句 Simple is better 就可以说明的。你在同时面对一堆电路板和一台 Macintosh 的时候可能才会体会到所谓的「科技与人文的十字路口」,但,软件设计同样有这样的十字路口,它可能出现在一份完整、漂亮的文档(像 Vue), 可能出现在一个屏蔽了所有复杂细节 (and it just works!) 的命令(像 next.js), 可能出现在它的小而美,可以接入到任何地方。
后来蚂蚁金服发布了 Umi, 比 Cans 做得更全面,但两者的思路都如出一辙。我想这证明了这套模式背后的价值所在。最近看到《Umi 架构、生态和未来》 ,让我更确信当初的想法和 Umi 在做的是契合的。推荐各位在下一个项目可以试试 Umi.
记得有一次面试的时候,我提到我主要是做一些前端基建的工作,面试官问我,觉得什么样的前端基建是做得好的基建?我回答说,如果我做的基建可以让同事少加班,那么我做的基建就是好的。
]]>几天前我购买了一块软木板挂在自己的房间,想到可以把一些有意思的读者来信打印出来钉到软木板上。于是在收件箱整理了 2014 年至今 5 年来收到的所有读者来信。
其中我发现一封 2015 年的已经被我淡忘的邮件:
这是一位在国外当老师的妈妈,特意来信想为她的儿子寻问关于编程的问题。当时我回信尽可能地写了很多浅薄的意见和经验分享,后来就再没有通信。这位母亲让我感动。
时隔几年再看来往的信件,无论是提到的这位母亲,还是其它特意发来感谢的读者,他们让我收获到的是,我写的文字确实帮助了一些人;五年来我坚持写独立博客是有意义的;这些来信让我意识到我活在世上还是有一点微不足道的贡献。所以我最开心的是看到来信里面提到的,我对读者的影响是什么。
对我来说,有没有活下去的勇气,取决于我在世界上有没有创造价值。
]]>所以这些人呢,不管是什么借口,最终选择了去做恶心的成年人社会中的一个恶心的人,最可气的是他们之后还产生了幻觉,说这就是“成熟”。于是又过来毒害年轻人,跟他们说,你看,我年轻的时候也像你这样,现在我这叫成熟,你这叫幼稚。
我现在看到很多我的同龄人变成这样,很多人都是我小时候的好朋友,也曾经充满了理想,充满了美好的这些东西,然后相信自己可以改变世界,最后却变成了这种恶心的人。
我回家吃饭心情好就敷衍一下,有时候心情不好就忍不住当场戳穿他们。我说:“你看你们这帮兔崽子,年轻时候我们在一块儿聊,都说要改变世界,现在你,你改变个屁了。”然后他们就有点不好意思,说:“哎呀,行了,老罗,咱们那时候不是幼稚吗?谁能改变世界?谁也改变不了世界。”我就跟他们说:“你别客气了,你已经改变这个世界了,因为你变成了一个恶心的人,这个世界多了一个恶心的人,因此它变得恶心了一点点。”
每一个生命来到世间,都注定改变世界,这是你的宿命,你别无选择。你要么把世界变得好一点,要么把世界变得坏一点。
你如果走进社会,为了生存或是为了什么不要脸的理由, 变成了一个恶心的成年人社会中的一员,那你就把这个世界变得恶心了一点点。 如果你一生耿直,刚正不阿,没做任何恶心的事情,没有做任何对别人造成伤害的事情,一辈子拼了老命勉强把老婆、孩子、老娘,把身边的这些人照顾好了,没有成名,没有发财,没有成就伟大的事业,一生正直,最后梗着脖子到了七八十岁死掉了,你这一生是不是没有改变世界?你还是改变世界了,你把这个世界变得美好了一点点。因为你,这个世界又多了一个好人。
—— 罗永浩
我很喜欢探讨这种两性哲学问题,因为我在看待两性关系中有些犬儒主义 —— 我不相信人性,不相信忠诚,世界上充满诱惑。更愿意相信大多数的婚姻是一场交易。
当两人初试开放性关系时,遇到一位专家在谈论「外遇」和「开放性关系」的成因,她说:
When I go to look for someone else, it’s not always because I wanna get away from you, it’s because I wanna get away from the person that I have myself become. And it’s not that I want to find somebody else, but I want to find another self.
当我开始想寻找另外一个人的时候,不是因为我想离开你,而是想逃离这个状态的我。我想寻找的不是另一个人,而是另一个我。
我想,这就是除了厌倦、诱惑之外,外遇的核心原因 —— 无法接受这个安定的自己。
有意思的是,在片中,男主角对开放性关系的底线,是只有自己可以令女主角高潮。所以每次女主角回家,他都会问女主角 Did you come (你高潮了吗)? 后来,女主角被一位成熟、事业有成的大叔深深吸引,她在他身上得到高潮后,男主角才感到他们的这段关系,已经彻底崩坏。
片尾,男女主角决定承受平淡、怨怼,回到对方身边。因为在各自经历过后,更加清楚彼此为何在一起。
*图片来自 https://movie.douban.com/photos/photo/2512608863/ *
或许最可靠稳固的关系就是双方愿意为对方失去那个原本的自己,而且是亲身经历告诉他们为何值得这样牺牲。
正如片中的两性专家所说,「开放性关系只是一个过程,千万不要把它作为你们的终点」。
]]>Think of it not as the destination, but the layover.
我在我所在的领域远算不上专家,只是一路走来有很多经验和自己独特的理解可以分享出去。以我这微不足道的「影响力」,我也不会因此获得多少钱。我希望读者可以把我创建的这个知识星球当作打赏的一个副加回报去看待。
我会在知识星球分享:
举个最常见的例子,对于用 JavaScript 来构建 UI, React 是声明式的。
// 普通的 DOM API 构建 UI
const div = document.createElement('div')
const p = document.createElement('p')
p.textContent = 'hello world'
const UI = div.append(p)
// React 构建 UI
const h = React.craeteElement
const UI = h('div', null, h('p', null, 'hello world'))
所有的 DSL (HTML, XML, SQL) 都是声明式的,你写出一条 SQL 语句,只是为了告诉数据库你要什么,然后数据库就会给你对应的数据,而不是通过数据库的 API 去取。
SELECT * FROM Products WHERE name='Alipay'
Apple 在今年 (2019 年) 也推出了 Swift UI, 在 WWDC 的 Swift UI 相关的 Session 里也多次提到声明式 UI 开发的威力。
声明式编程的潜力在于:
解放人力成本,你只要「声明」你要做什么,具体怎么做,由运行时解决。
函数式编程就是声明式编程的一种,在函数式编程里的尾递归性能,就取决于运行时,而不是靠程序员去手动优化。React 里你只要描述你的 UI, 接下来状态变化后 UI 如何更新,是 React 在运行时帮你处理的,而不是靠程序员优化 diff 算法。
我们可以认为 Serverless (尤其是函数计算) 在运维领域获得了声明式的好处 —— 我们定义好了函数,我们只要告诉平台我们需要调用这个函数,那么如何进行计算资源分配、如何对代码做分布式部署,都不需要程序员考虑。
运行时帮你完成工作,除了可以节省人力成本外,还降低了程序员出错的概率 —— 因为写的代码越少,出错的概率就越小。人是最不可靠的,我们应该尽量把工作交给计算机。
「声明」是「描述」而不是真正「执行」
在纯函数式编程语言里面,一切都是声明式的,是纯 (Pure) 的,没有副作用(Side Effect)的。
Haskell 是一个纯函数式的语言,像在控制台输出文本这种方法(putStrLn
)就是一种副作用。在 Haskell 里 putStrLn "Hello World"
本身不会真正地输出 “Hello World“, 而是返回一个 IO 类型,来说明他是一个副作用。但它如何被执行,取决于运行时。
Elm 和 Haskell 一样,副作用也只是返回一种类似 Haskell 中的 IO 类型。在 Elm 中叫做 Cmd.
以上说的这些,可能太过抽象。所以我用前端的同学们应该都知道的 redux-saga 对此作更具象的解释。也可以解答为什么我虽然不喜欢 Redux, 但认为 redux-saga 是一个的很不错的库。因为他利用 redux 的 middleware 机制和 generator 巧妙地实现了类似 Haskell 的 IO.
下面我将用 官方文档的例子 做解释。
比如,以下是一个有副作用的函数:
import { call } from 'redux-saga/effects'
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// ...
}
显然,Api.fetch()
是副作用,它会发送网络请求。但是,在 redux-saga 里面,你不应该直接执行这个函数,而是使用 call
告诉 redux-saga —— 你要执行 Api.fetch
, 参数为 /products
.
所以,事实上这个函数没有被命令式地被执行,而是由 redux-saga 决定如何执行。
如果你在外部直接调用 fetchProducts()
, 你会得到一个 Generator Iterator. 然后通过 next()
得到你 yield 的值。所以你可以这样去测试你的程序:
const iterator = fetchProducts()
// expects a call instruction
assert.deepEqual(
iterator.next().value,
call(Api.fetch, '/products'),
"fetchProducts should yield an Effect call(Api.fetch, './products')"
)
也就是说,你要测试的是「你有没有告诉程序你要执行的副作用,以及执行的参数是什么」。和命令式编程不同,因为命令式的程序在你执行函数时会真实地执行这个 Api.fetch
,你必须用测试框架里类似 mockFn
的手段去 mock 这个函数进行测试。
fetchProducts()
只有在 Redux 环境里,才会真正地执行副作用(在这里就是 Api.fetch 发送的网络请求)。
所以,声明式的编程是非常易于测试的。
可视化编程是一种声明式编程
我们探索可视化编程,是因为我们一直期望通过拖拽就能完成开发,其实就是期望我们完成任务仅仅需要通过声明,而不是写命令式的代码。当然这是一种理想的状态。
DSL 是最常见的声明式编程形式。我一直在布道 GraphQL, 因为它把网络请求变得声明式了:
query {
posts {
id, title, content
}
}
把网络请求变成声明式的好处有很多,其中一个就是它可以被放到各种各样的环境被执行。想象一下,我们可以打造一个可视化的应用搭建工具,在命令式编程的场景下,我们如果要做出如「点击按钮发送请求,得到响应后触发另一个 UI 更新」,就需要编写命令式的代码:
async function onClickButton() {
// 手动发送请求
const result = await fetch('/api')
// 手动更新 UI
table.dataSource = result
}
如果是 GraphQL, 我们可以把每一条 GraphQL 语句单独看作一个对象,他可以被任何组件触发,它的结果也可以被任何组件订阅。这样一来,在可视化的搭建工具里,程序员要做的是:
当然现实世界的应用不是那么简单,但已经是跨出了很大一步。
未来为什么属于声明式编程,因为我们在不断地努力提高开发效率,声明式编程显然是提效的最佳手段。React, Flutter, SwiftUI, GraphQL 的出现是最好的证明。最近听到内网太多人在提 Serverless, 我想说,提升开发效率,我们应该去想如何尽量让开发者声明式地编写代码,而不是只去想我们在 Serverless 上能做什么。
]]>我的童年除了阅读,大部分信息的获取来源于电视机。我从电视机看过的日本动画不少 —— 在 CCTV 少儿频道看《四驱兄弟》,在 TVS (南方卫视) 看《哆啦 A 梦》,在华娱卫视《通灵王》、《军曹》,在本港台看《游戏王》,在翡翠台又看了不计其数的,就不一一列举。
我从这些动画片里感受不出日本文化,一是因为都是中文的配音,二是基本上我看的都是虚构类作品,无法从中了解到日本的文化生活。只有翡翠台偶尔播出一些配音的日本真人电视节目,让我了解到日本的街道和风景是如此模样,但仅此而已。
不过我却很能理解我的那位朋友,因为我对另一片土地 —— 香港 —— 有一样的感受。
电视机把我和香港的距离拉得很近。白天有「卡通片」,傍晚有儿童节目,每晚六点半,可以看《六点半新闻报道》,知道香港、国际在发生的事。7点半后,有《东张西望》看娱乐新闻。紧接下来就是八点档和九点档的电视剧。
我是看香港电视剧和电影、听「广东歌」长大的。聊起亚视的「柒事」,我们一样会大笑。现在不看电视,但还有 YouTube. 走在香港街头,我一个广东人,和香港人又有什么太大的区别呢。
最大的区别可能是,我很羡慕香港人。香港人可以有不同的政见,可以自由表达自己的观点,有权发声争取自己(或别人)应有的权利,可以大声对别人讲:「香港系有言论自由咖」!不用担心「讲错嘢」。而我不能。
香港不能失去这些。我愿意在这样的地方生活。香港是我的精神家园。
撇开身份认同,在政治上,香港回归早已经是国际社会公认的。我自己作为中国人,一直为中国有这样的香港感到庆幸。因为这代表中国人还能有另一种作为中国人的方式。
而我,很惭愧,虽同讲广东话,但在内地,也只不过是一只知道自己是井底之蛙的井底之蛙罢了。
王小波说「井底之蛙也拥有一片天空」,对我来说,这片天空就是香港。
衷心祝福香港。
]]>我上大學時,有一次我的數學教授在課堂上講到:我現在所教的數學,你們也許一生都用不到,但我還要教,因為這些知識是好的,應該讓你們知道。這位老師的胸襟之高遠,使我終生佩服。我還要說,像這樣的胸襟,在中國人文知識分子中間很少見到。
把「知识」换成「普世价值」,我认为道理同样成立:
我们追求普世价值,是因为普世价值是好的,是对的,只要你是人类,你就应该拥有他。如果有人不幸失去,那么就应该帮助他。
令人难过的是,由于一系列客观条件,一部分人无法对此感同身受。普世价值和民族无关,和国籍无关,它属于全人类。一旦能意识到一点,那么这些人就很容易能识破一些延用了近半个世纪的可笑的借口和谎言,他们的被害妄想症也理应会被治愈。
很遗憾,我得不到它,我本应该能为此不断地写,不断地写,但我不能。这让我难过了很多年,一直到现在。
愿不在意的人能一直不在意,以免遭受这种难过。
]]>从那年的某天
我突然遇到你
但他们说
不要谈论你
因为她不喜欢你
所以你是禁忌 你是克星
你只会带来麻烦
他们请我不要谈论你
我说你没有错 喜欢你也没有错
错在她不喜欢你
他们劝我私奔吧
去一个大家都喜欢你的地方
但她是我妈妈
我如何离开她
他们劝我离开你吧
即使没有你 生活如常
多年后
我偶尔提起你
他们请我不要谈论你
]]>从上个月开始,博客的图片全部无法显示,原因是新浪微博开始防盗链了。
起初选择使用新浪微博的图床,一方面是因为不用花钱,另一方面是因为觉得自己的博客没有多少访问量,也就不折腾 CDN 了。然而这一两年我意识到,我认真写下的文字也被不少人在认真看待。趁着这个契机,就把博客的图片全部迁移到稳定的 CDN 上。
我选择了阿里云 OSS + CDN 的方案,我用 grep 把博客里所有的新浪图床图片找了出来,然后批量下载下来,上传到 OSS 上。
比较麻烦的是国内的 CDN 域名需要备案,除此之外,就是阿里云的一条龙服务 —— 域名可以绑定到 CDN,CDN 可以直接关联 OSS。体验还算不错。
Coding Page 的 Pages 服务在香港的腾讯云,抽风是家常便饭,无法忍受。于是接入了 Cloudflare, 现在你访问的这里就是经过 Cloudflare 加速的页面。
]]>这次入手 iPad Pro 完全是个意外,原本的计划是入手新款 iPad mini 更新一下我用了三年的 iPad mini 2. 后来想一想,不如用 24 期免息直接入手 iPad Pro.
之所以想换代,主要是这些原因:
恰逢这周末在深圳出差,我故意不带我的 15 寸 Macbook Pro, 只带了 iPad Pro. 然后又深度体验了一周。
虽然我也有一台 Kindle Paperwhite, 但是我更倾向于用 iPad 阅读。因为我的眼睛对屏幕不挑剔,使用 iPad 阅读不会像别人说的眼睛不舒服。而 iPad 能提供比 Kindle 大的阅读面积。
iPad mini 2 相对于 Kindle Paperwhite 来说已经大了不少,而在 iPad Pro 11 寸阅读给我的感受是读书就像是在读报纸。这次出差在飞机上总共读完了两本书,体验完胜 Kindle.
即使尺寸大,但握在手上也不会觉得笨重。
并不是说 iPad mini 2 不能做到,而是 mini 2 的性能让我没有欲望这样做。但是我很愿意在 iPad Pro 上 Google 一翻,刷刷 Twitter / 微博,收发 Email. 一切都很顺畅。
周末出差时,我的周报都是在 iPad Pro 上完成的。我用讯飞语音完成周报的文字录入,准确率比想象中高得多。讯飞可以满足大部分的文字录入需求,当然,有些场景不方便使用语音输入。这时有个键盘是更好的选择。迫于贫穷,我没有买 Smart Keyboard, 而是买了只要 178 元的罗技 k380.
K380 很适合对便携性有要求的用户,重量轻,支持 3 个设备一键切换。手感就是普通薄膜键盘的手感。唯一的缺点是和 iPad Pro 搭配使用的话,你要额外买一个支架架住 iPad.
这次到深圳出差顺便逛了一下海边,用 SONY RX100 拍了不少照片。当晚到顺电买了一个 MOMAX 的 Type-C HUB. 插到 iPad Pro 上,会自动打开系统照片 App 的导入页面,可以直接把 SD 卡里的照片导出。然后用 Snapseed 就能处理图片。
在没有 Macbook Pro 的情况下可以完成整个从照片导出到照片后期的过程,倒是令我惊喜的。
这是一张用 iPad Pro 后期的照片。
因为家里有 NAS, 64GB 倒不会不够用,照片导出和处理完,直接扔到 NAS 就完事了。
我本身就很喜欢写字,手写歌词,于是我也入了 Apple Pencil 2 以及笔记 App 「Goodnotes 5」.
刚开始在屏幕上写字有点不习惯它的手感 —— 太硬了。但是适应了一段时间后慢慢找到了点感觉。
这是在纸上写的字:
这是在 iPad 上写的同样的字:
因为配置高和屏幕大,iPad Pro 可以做的比原本 iPad mini 多得多 —— 处理照片、浏览网页、用 Apple Pencil 做笔记(当然现在新款 iPad mini 也支持 Apple Pencil 一代)。
作为程序员,在短暂出游时能不能以 iPad Pro 替代 Macbook, 我是持怀疑态度的。在编程领域,iPad Pro 离生产力工具还差得很远。但是 iPad Pro 能满足我除编程以外 99% 的需求。
另外一个矛盾的地方是,如果我带上键盘去用 iPad Pro, 那么我为什么不直接用 Macbook 呢?有人会说,iPad Pro 11 寸的重量只有 468 克,而我手上的 Macbook Pro 15 寸则有 1830 克,是几乎 5 台 iPad Pro 的重量。但说实话,背在背包里,我根本不介意这 1400 克的差异,所以于我而言,iPad Pro 太尴尬了,它可以做很多,但 Macbook Pro 可以做得更多。
对于程序员群体,iPad Pro 是一个可有可无的设备。而对于那些主要以文字录入、图片处理、手写笔记为主要任务的群体,那么 iPad Pro 还是很值得入手的。
我们总是在作自己未来的规划和目标的时候说,我想要什么,而忽略了问自己一个问题,「我愿意为此舍弃点什么」。我们逃避了残酷的事实 —— 我们每天只有 24 小时,我们什么都想要,但这是不可能的。
意识到这一点,有利于我们做很多的决策,而且对很多让你感到可惜的事都释怀了。
有人总是会在年初列下自己的读书清单,但直到年底,发现自己一本都没有读完。这是很多人懊恼和焦虑的来源 —— 为什么我总是无法履行自己的规划?
过去我总会把原因归咎于没有做好时间管理,但后来我顿悟了,在列下读书清单时,并没有问自己这样的问题 —— 你愿意为读书带来的精神收益,而舍弃点什么?
如果你在制定任何规划时都这样问自己,那么,你就会在每一次决策前明确这一点:这件事于你而言到底有多重要?如果阅读于你而言比看美剧重要,你就要作出取舍:把你看美剧的时间,花在阅读上。
如果你认为做得到,那么就让它正式成为你的规划。如果你做不到,那么就不要让它成为你的规划。何必为自己制定一个无法完成的计划呢?它除了为你带来无意义的焦虑,无一用处。
宁愿看美剧而放弃阅读并不可耻,活得明白是最重要的。只要这是你权衡后作出的取舍,那么你就不会为此而感到焦虑。
我在《健身一年》里提到,一年前我下定决心开始健身,当时我问自己:我能为健身舍弃什么?
健身需要耗费每周四天每次 2 小时的时间,这些时间我可以用来写代码,做音乐,阅读。我愿意舍弃他们吗?
经过权衡后,我决定舍弃他们。于是我获得了一些健身的成果,同时我再也不为放弃了大量的阅读、写代码、做音乐的时间而焦虑。因为我早就知道,如果我想要获得健身的成果,这些是必须放弃的。
不必为「我没有时间 xxx」而感到可惜,因为你没时间做的事,不过是在为你更想做的事让路而已。(除非你把时间都花在偷懒上)
每个人有不同的活法,对待事件的优先级也不尽相同。只要认识到「吾生也有涯」的局限,你就可以从每一年底的「责怪自己没有做到什么」转变成「为自己做到了什么而感到高兴」。人生因此也变得简单得多 —— 你只要保证自己不是在虚度光阴即可。
就像即使我现在惧怕婚姻,若是将来某一天也选择了婚姻,那一定是比起婚姻让我失去的,我更想得到婚姻给我带来的。
]]>但实际上,如果你理解 React 解决问题的方式的本质,你会发现 React 没有那么难。我写这本小书的主要目的,就是想让还觉得 React 难用的开发者们知道,React 很简单。
React 是一种构建 UI 的思想,关于 React 的思想,我觉得已经是老生常谈了。在所有从本质层面讲解 React 的文章或书里,总不免提到这样的公式:
UI = f(state)
意思就是,UI 是基于状态的一个函数返回值。这也是 Sebastian (React 设计者) 设想的理想状态。直到当我们真正拿 React 来写 UI 的时候,却发现我们很难遵循这个公式——组件的内部状态需要依靠 Class. 而写 Class 是导致 React 使用者困惑的重要原因。
用 Class 实现内部状态同时也带来了另一个问题——我们怎么复用这些逻辑?常见的做法就是使用 Function as Child Component.
先不说 Function as Child Component 容不容易被初学者理解, 光是它带来的嵌套问题,就已经足够我们烦恼了——可以想像我们只能用 callback 写 JavaScript 的时代。
const MyForm = () => (
<DataFetcher>
{data => (
<Actions>
{actions => (
<Translations>
{translations => (
<Styles>
{styles => (
<form styles={styles}>
<input type="text" value={data.value} />
<button onClick={actions.submit}>
{translations.submitText}
</button>
</form>
)}
</Styles>
)}
</Translations>
)}
</Actions>
)}
</DataFetcher>
)
还有,我们应该怎么解决组件之间的状态共享问题?Redux? MobX? 还是其它状态管理工具?
React 应该是简单直接的,但越来越多人「谈 React 色变」,正是由于以上的(或者以上没有提到的)问题,认为 React 复杂,难学。很多关于 React 的文章和书都花了不少篇幅来介绍这些解决问题的「设计模式」。
但随着 React 的不断迭代,有了 Context API, 有了 Hooks API, 一切都变得简单了。我们可以抛开种种「模式」,真正用「函数式」的思维去构建 UI. 这也是标题想表达的意思 —— 我们应该使用「现代」的 React, 去避免不必要的学习成本。
和著名的《设计模式》一样,很多「设计模式」是为了弥补面向对象的缺陷而出现的。React 通过自身 API 的完善,使我们能少写更多不必要的代码,少学习很多不必要的「模式」。
我的目的不是在教你怎么用各种 React 相关的库,而是想让读者知道,我们在解决什么样的问题?我们解决问题的方法是什么?别人的库是怎么解决的?
这个专栏会陆续发布循序渐进的文章。如果你完全没有接触过 React, 那么读完这个专栏后,你就完全能驾驭了 React 了,而且是用优雅的方式去驾驭他。你会发现,写 React 就是写函数那么简单,只不过这个函数的返回值是 Virtual DOM 罢了。
初学者们,请不必因为不懂所谓的「模式」而感到惭愧,尽情享受技术发展带来的红利吧。
这是我在小专栏付费连载的《Modern React》的前言,目前正在限时打折,有兴趣的朋友可以订阅。
之所以选择连载的方式,是因为我想要通过读者的反馈去决定我接下来连载的内容。欢迎读者们积极地来信反馈。
]]>本文不提供训练计划 本文不提供饮食计划
我在《2018 年终总结》里提到了我健身的成果,在这篇文章里,我将分享我健身的过程,以及身体上、心智上的一些成长。
说来惭愧,我第一次接触健身是 2015 年。当时刚出来工作,有点闲钱,看到镜子里的那只瘦猴,决定开始去健身房增肌。于是在知乎搜索了很多关于增肌的回答,选了住处附近的一家健身房,就开始瞎练了,没有计划,也不知道怎么做计划。
那时我身高 165, 体重 46kg. 想做卧推,空杆(20kg)都推不起;想做划船,动作做不好。这时候在健身房确实很大压力,什么都做不好,还怕占着别人地方。
这样坚持了 2 个月,没什么效果。自然就放弃了。过了一段时间,我决定花钱请一个私教,重新开始我的增肌计划。那时已经是 2016 年了,我买了 10 节私教课。商业健身房私教的惯用套路——先去跑步机热身,然后教我做些动作。
10 节课下来我没学到什么东西,因为教练只告诉你做什么动作,怎么做,有什么注意的,却没有讲为什么。除了上课,我也在 YouTube 看很多关于健身的视频。坚持了 3 个月左右,没太大的效果,又一次放弃了。
之后很长一段时间,偶尔 YouTube 会根据我的用户画像给我推荐一些健身类的视频,我每次看到都觉得很羞愧——我无法面对这个坚持不下去的自己。我那里常常想,如果我当初坚持下来了,那么我已经有很大变化了吧。
虽然我很矮,但是说实话,我很少因为我的身材自卑,除了限制了我追女生的范围以外,我的身高没有给我带来什么不好的事。但是我总是隐约地觉得自己很孱弱,我觉得我不能保护我喜欢的人,不能给人安全感。夏天穿衣服像一个毒友似的。这些倒是我比较在意的。
导火线是 2017 年年底,我在某个停车场被一个保安言语暴力(过程不表),我十分生气,当时我想:是不是因为我看上去太弱了,所以这个保安敢这么对我说话?如果我强壮一些,或许就不需要和傻x讲道理了。我自己都缺乏安全感,怎么让别人有安全感呢。
回到家我想了很久,决定认真对待健身这件事。**我问自己一个最重要的问题:我能为健身付出多少时间?**如果我每周训练 4 天,那么我将会大量减少自己业余的代码时间和玩音乐的时间,我是否能接受?
当然,我最后选择了接受,于是才有了今天的一点点成果。
和前两次不同的是,我不再选择商业健身房,而是去找一些更专业些的以授课为主的健身工作室。恰好当时看中的一家工作室在做活动,很优惠,就买了 10 节课,开始了我的第三次健身...
为了避免广告嫌疑,就不说是哪家工作室了。
以我的经历,工作室和商业健身房比,私教的价格没有相差太多,买的课少(10节以内)可能相差 100 左右。买得多一些,差距就明显了。但是我是宁愿多花 100 上 10 节我能学到东西的课,也不想便宜 100 块上 10 节没有意义的课。
严谨地说,在商业健身房遇到靠谱教练的概率比在工作室要低得多。「靠谱」体现在:
至于可不可以不请教练,我觉得天下间没有什么是不可以的。很多人一直自己练也练得很好。自己练的风险主要在于,你可能需要花更长的时间去探索正确的动作,也可能出现受伤的情况。所以我觉得请不请教练,是看你愿不愿意花钱规避这些风险罢了。
我自己主张的是有条件就尽量找教练(前提是靠谱的教练),因为我分析自己之前放弃的最大的原因就是不相信自己的训练,我不知道自己的动作到底有没有效。新手最大的问题是没有什么感受度——你根本感受不到自己的背部肌肉,怎么知道自己划船有没有划对呢。有教练的最大好处,就是他知道你有没有做对。这样就就不会怀疑自己是不是做了无用功了。
应该买多少节课呢。我当时买了 30 节课(做活动买的 10节 + 之后买的20节),后来我觉得 20 节课其实就足够了。我的训练频率是一周 4 练,上到 20 节课的时候,我基本已经可以自己独立训练了,也能感受得到自己的肌肉。最后 10 节课我都是专门让我教练辅助我练习我的弱项深蹲的。
最后关于跟着教练训练很重要的一点是,根据教练为你制定的训练计划去做没有问题,但你一定要自己理解这个训练计划,不要天天无脑地教练说做什么就跟着做,**多问教练为什么要这样安排?这样安排的好处是什么?同样是练这个肌群的动作,这个动作和另外一个动作有什么区别?**时刻提醒自己,这是你自己的训练,教练不是你的秘书(除非你这辈子都一直请教练),想想看,离开教练了以后,你能不能制定适合自己的计划?你从教练身上学到了什么东西?
我从我的教练身上学到的除了基本的动作模式以外,还学到的是,不要让客观条件限制了你的训练。训练动作不是死的,不是说没有平板凳、没有杆,就不能练胸了。多看看你现在有什么,能不能用现有的东西,去创造一个新的训练方式(即使效果不是最好的)。这是建立在你真正了解你的肌群是怎么工作的基础下才能做到的。所以我自己在业余的时间,也会去了解解剖学方面的知识。
当你在工作室上完课了,再转到费用更低的商业健身房也不迟。
「三分练,七分吃」,这句话都被说滥了。我个人觉得饮食比训练难太多了。特别是我这种所谓的「怎么吃都不胖」的瘦子。
其实根据观察,很多瘦子说的「怎么吃都吃不胖」,主要的原因还是吃太少,只是以为自己吃很多。像我,没健身的时候,对食物是很任性的——想吃就吃,不想吃就算了。我甚至认为人不应该浪费时间去吃饭,因为不吃也不会饿。吃饭的时候,一盘子的饭,我吃半盘就觉得太饱了,觉得自己吃了很多东西。但我后来观察那些稍胖的人吃饭,饭量起码是我的几倍。
健身以后,每天得强迫自己加餐,鸡蛋、坚果、鸡胸肉等等。刚开始训练的时候,我牛奶是买一升的那种,每天一升。
我不是专业的营养师,但我可以在这里科普一下,健身的时候应该怎么安排饮食。
不同的训练目的有不同的饮食方案:
这里所说的热量,指的是一个人正常活动时所消耗的热量(BMR)。所以从数学上来看,增肌和减脂都是很简单的——了解自己的 BMR, 然后针对需求调节自己的热量摄入。
如何控制呢?从控制提供热量的三个元素入手:脂肪、蛋白质、碳水化合物。
我不打算在这里大开篇幅去讲怎么去计算,因为 Google 一下你马上就知道怎么算了。我想说的是,一定要计算你的摄入量。例如增肌,如果你不去估算你自己今天吃有大概多少的热量,那么 99% 你是吃不够的。
肌肉合成需要蛋白质,所以增肌要保证足够的蛋白质摄入。关于摄入多少,众说纷云。我自己是按照自己体重(kg)乘以 2 换算成克 (g) 为单位的量。例如,体重是 50kg 时,每天至少需要摄入 50 x 2 = 100g 的蛋白质。然后用什么食物去满足这个量,就是你自己的决定了。
关于更详细的饮食计划,可以看看卓叔的 这篇文章。
能不能喝?有没有副作用?
能。没有(除非你的肾本来就有病变)。
喝多少?是不是每天都要喝?
上面已经提到了,你需要计算你每天要摄入多少热量。如果通过三餐或补餐你还是吃不够,那就用蛋白粉(或增肌粉)补够。蛋白粉不是药物,是补剂。这是很简单的数学问题。
我的训练计划是每周 4 练:腿(+胸)、胸(+背)、背(+胸)、肩。
我不打算在这里详细列出我正在使用的训练计划,也不会教你怎么制定计划(这应该是你教练的任务),而是分享我一直训练以来的一些小经验。
还有很重要的一点是:一定要对自己的训练做记录。 If you don't mesure it, how can you improve it? 记录你计划里的所有动作,重量、组数、做完后的感受(这很重要,下一次训练是否增加重量需要参考上一次的感受,比如,如果累的程度是 10,这一次训练完的程度是多少?)。如果没有训练记录,那么你到健身房可以做的动作只有——闲逛。
每一次踏入健身房,你的目标就是,能不能比上一次训练推更大的重量。如果不能,分析自己在哪个环节出了问题——吃不够?还是睡不够?然后去修正自己的问题——睡不够,那就早点睡;吃不够,就吃更多。
我很害怕健身房里其它人的目光,怎么办?
其实没有什么人在意你。大家的目光一般是在漂亮的女性,或练得很好的人身上的。我自己在健身房里见瘦子,我的脑回路是这样的:
一般来说,健身工作室的设备不会有太大的问题。如果你去找商业健身房,要考虑的因素有:
怎么去坚持健身?这是很多人问的问题。
首先是问自己,为什么开始健身?找到你的源动力。对我自己来说,源动力就是想变得强壮,遇到傻x的时候能自信地面对。
其次,一定要设立一个目标。而且必须是一个明确的目标。别说什么「我的目标是身体健康」,定一个可以量化的目标。我最初给自己的目标就是:2018 年底至少要体重要达到 60kg (当然我失败了)。如果没有一个可以量化的目标,在你没有爱上健身这个运动之前,你很容易会放弃的。
然后是不要和别人宣称你在健身,这对你的训练没有任何帮助,甚至会被嘲讽。更加不要傻到在朋友圈打卡——在你没有取得成果之前,没有人在意你的过程。打卡的唯一收获只会是更多人把你屏蔽了。
当你健身了一段时间,在吃、睡、练三件事都做好的基础上,第三个月绝对会有明显的效果,这是新手的福利期。很快会有人问你,咦?你是不是健身了,怎么壮了那么多(这是我健身 4 个月左右的时候听得最多的一句话)。这时你获得了成就感,享受了健身给你带来的改变,使你继续坚持下去。一直到坚持半年了,你会发现,健身渐渐地变成了你生活中的一部份。
当健身成为了你的一种生活方式时,哪有什么坚持不坚持。
我很多次洗澡脱光衣服看到自己身体的变化,都很感谢一年前的我坚持下来了。我再也不用对自己说「如果我当时...」
如果你说,「我很想健身,但是我没时间啊!」
据我观察,很多人所谓的没有时间,是不愿意花时间在健身上面而已。一天只有 24 个小时,除去上班的时间,我的时间很少,但为了健身,我减少了我写代码和玩音乐的时间,这是我付出的代价。如果你不愿意留你最多 2 小时的时间给健身,只能说明健身这件事在你的生活里优先级不够高罢了。
不愿意把时间花在健身上面并不可耻,这再正常不过了。每个人有每个人的生活方式。如果你用「没有时间」作为借口,那么你相当于在说,我们这些健身的,都是闲得慌的。
YouTube 地址:https://www.youtube.com/watch?v=4IwFVdxJywQ
]]>当然,不捐款你也不会失去什么。你还是可以免费读到我的所有博客内容。
感谢通过各种渠道捐赠过我的所有人。
]]>大人热爱数字。如果你跟他们说你认识了新朋友,他们从来不会问你重要的事情。他们从来不会说:“他的声音听起来怎么样?他最喜欢什么游戏?他收集蝴蝶吗?”
他们会问:“他多少岁?有多少个兄弟?他有多重?他父亲赚多少钱?”只有这样他们才会觉得他们了解了他。
如果你对大人说:“我看到一座漂亮红砖房,窗台上摆着几盆天竺葵,屋顶有许多鸽子……”那他们想象不出这座房子是什么样子的。你必须说:“我看到一座价值十万法郎的房子。”他们就会惊叫:“哇,多漂亮的房子啊!”
—— 《小王子》
2018 对我自己来说,是灰心的一年。这一年除了和往年一样的焦虑以外,也没有做成什么我认为了不起的事。很大的原因是我对技术失去了信心。
小时候,我最大的理想是「用技术改变世界」,但我早在我的 2017 年终总结 里提到:「技术并不是那么重要,它只不过是一种手段而已」。今年我更加确信这一点,我们做技术的人,永远只能间接地改变世界。一个可以改变人们生活方式的产品,技术虽是不可缺少的部份,但也不是起决定性作用的部分。
于我而言这是灾难性的信仰崩塌。
有时夜里我也会反思,是不是对自己的要求太高了,又或者是,我太想向别人证明自己,害怕自己变得平庸。而在今年,我意识到自己真的变得平庸了,所以我把他称为灰心的一年。
而今年最主要的心态变化,则是随着年龄的增长和财富的积累,我不免也会想到未来的现实生活。婚姻、家庭、置业,等等。我觉得这些东西不能带给我快乐,它们不是我想要的。但是为什么每一个人都要「善意」地提醒我,你应该这样,应该那样呢。我不买房,便错么?成年人的眼里,为什么都只有所谓的「保值」、「升值」呢。
我们总说大人们不关心孩子们快不快乐,只关心孩子们的成绩。原来大人们也不关心大人们快不快乐啊。
今年除了支撑了几个业务的后台管理系统以外,主要在思考的东西是如何利用 GraphQL 帮助开发者更轻松地应对中后台管理系统的开发。
在内部做了很多基建以后我有一个感悟,我们做基建的初衷是提高开发者的效率,但是实际上,影响开发者效率的因素,很大程度是开发者本身。基建能做的很有限。举例来说,Redux 这么一个「简单」的库,却有很多人用不好。在项目里,很多可以简单实现的地方,由于编程水平、经验、对库/框架的了解程度等等条件的不足,开发者就把简单的问题复杂地解决了。
即使给人一台最好的单反,也不一定拍得出最好的照片——因为重点还是镜头背后的那颗脑袋。
所以在新的一年,我的目标是多输出一些理论层面的东西。
2018 年依然是没有节制地消费,不过买的东西已经不多了(该买的都买过了)
(不得不提欧德堡全脂牛奶是大概一周一箱...)
今年尝试性地做了一集技术的短视频内容:《解读 The State of JavaScript 2018》
2018 年我做了决定,我的生活不再是 80% 的 coding time 了,我把更多的时间,花在了别的地方——音乐、健身、读书。这是一个巨大的变化,因为在以前,代码几乎是我生活的全部。我意识到,如果我稍微放下一点点的代码时间,生活里还可以拥有更多有趣的东西。
健身可以说是 2018 年唯一一件做成了的事情。我从 2018 年 1 月份开始健身,每周训练 3-4 天,控制饮食。直到现在刚好整整一年,达到了从 46kg 到 54 kg 的变化。
如果问我是怎么坚持下来的,我认为无论是坚持什么,都是:因为相信,所以咬着牙坚持试试,坚持了一段时间后,它就成了你的一种生活方式。当它成为了你的一种生活方式的时候,就无所谓坚持和不坚持了。就像你不会去问别人是怎么「坚持」看美剧的一样。
关于更多我健身的经验,我会在之后单独写一篇文章分享。
今年学会了德州扑克,和朋友打了很多场。我非常喜欢这个扑克游戏,他让我更了解我的牌友,更重要的是,在牌局里,我意识到了自己的缺点:冒进、喜欢承受高风险高回报、贪婪、充满侥幸心理。每次打完,通过回顾自己的打法,我更加了解我自己,我会反思:
德州扑克结合了运气、心理、概率、演技,它不仅是人与人之间的博弈,也能让你有不妥协于运气的机会——你拿一手烂牌,仍然能打好(当然,运气有时候也会打败你,本来在转牌时胜利在望,河牌却是别人的翻身之牌)。
我还转载排版了世界扑克巡回赛(WPT)总决赛冠军老邱的小传记 《赌士列传: 老邱传奇》。相信你可以通过这篇传记感受到德州扑克的魅力。
在这一年收到了挺多的读者来信,向我询问建议,我都尽量抽时间一一回复。还有一些文章收到的打赏留言里提到的因为我的文章而有所收获,让我知道我写的博客的确有他的价值所在。
希望把生活过得更有趣一些。
]]>「流行文化产品是时代气息的显影」,显然,当代粤语片区大多数人们变得不再需要粤语流行歌曲,他们比以前有更多选择,例如综艺、韩国流行文化等等。当然也和当代人接触信息的媒介有很大的关系,毕竟如果消费者可以满足于连「俗气芭乐」都不如的《学猫叫》,那么又何必费钱费心思去做更具音乐性的音乐作品呢。
当粤语片区的年轻一代人「抛弃」了电视机,加上国内串流音乐服务的版权问题,粤语流行文化在国内就很难传播进来了。如果不是主动接触它,关注它,粤语流行歌曲几乎没有机会进入你的耳朵。唯一的机会是香港艺人参加国内的音乐类综艺节目,但为了照顾观众,唱起了国语,发行的也是国语唱片。
不发国语唱片的新生代香港音乐人自然更难被发现,但港乐从旋律、编曲、填词都不输中国大陆(以及台湾地区)的流行音乐。下面是一个歌单,这些歌是我心目中 2018 年发行的粤语歌曲中最好的。听完这些歌,你就可以大概清楚目前港乐的发展状况。它是真的不如其它的流行音乐吗?
(如果你用 PC 访问这篇文章,在这里会有一个 Spotify 播放器,播放这一整个歌单)
《心之科学》 容祖儿 作曲:Howie@DearJane & 林家謙 / 填词:黃偉文
《小问题》 AGA 作曲:AGA / 填词:陳詠謙
《未来见》 RubberBand 作曲:RubberBand / 填词:Tim Lui
《恐怖情人》 许廷铿 作曲:雷頌德 / 填词:林夕
《无期》 AGA 作曲:AGA / 填词:林夕
《哪儿》 小尘埃
《仙乐处处飘》 小尘埃
《挥挥手》 JW 作曲:Eye Fung / 填词:陳詠謙
《荣辱战争》 林奕匡 作曲:林奕匡 / 填词:陳詠謙
《人妻的伪术》 谢安琪 作曲:雷頌德 / 填词:林夕
《睡前服》 小肥 作曲:小肥 / 填词:Tim Lui
《天才儿童 1985》 张敬轩 作曲:伍樂城 / 填词:黃偉文
《重阳》 邓小巧 作曲:林家謙、謝國維 / 填词:黃偉文
《百年树木》 张敬轩 作曲:伍卓賢 / 填词:林若寧
《风尘三侠》 小肥/侧田/6号@RubberBand 作曲:李偉@RubberBand / 填词:林寶
《如何从夏天活过来》 黄妍 作曲:黄妍 / 填词:黄妍
]]>因为我的经历特殊,有人也会对我开玩笑说真不知道读书有什么用,还不如早点出来工作。我知道以我朋友的受教育程度来说,这肯定只是玩笑,他们不可能是「读书无用论」者,所以我也会附和着笑。
虽然这只是句玩笑话,但是确实令我回顾了一下我从我的在校生涯中得到了什么东西。
我首先想到的是英语。我的小学是从一年级开始就有英语的课程,所以我接触英语的时间很早。我自认小学四年级以前我还是个很乖的学生,上课认真听讲,作业按时完成。所以我的语数英成绩都很不错。从小培养了「语感」,使得我一直以来可以低障碍地读写英文。这对我的编程学习、职业生涯都有很大的意义。懂英文让我可以亲自融入整个开源社区、技术社区,可以直接在 YouTube 听别人在 Conference 的 Talks, 吸取别人的思想。而不是等别人翻译过来。我认为能低障碍地读写英语是技术人成长很重要的条件。
尽管我对现阶段的英语教育方式仍然存疑,我还是认为在基础教育里,英语是必不可少的。
我的价值观和人生观,是我于在校生涯之中建立的。不过不要误会,这些都不是学校的课程直接赋予我的,而是学校这个「象牙塔」,让我可以有充分的时间去读书、学习和思考。我虽然在班上是「差生」,但我敢说我是最爱学习和读书的。这句话要是被我当年的老师看到,估计他要笑个半死,但这是事实,不可否认。我在学校不用供车供房工作赚钱,我可以把一整天的时间花在读书上,我在我读的所有书里懂得了我应该成为一个什么样的人、我应该努力让自己拥有什么样的品格才能算是一个「好人」。
另外,我很多技术书籍都是在学校读的,比如《JavaScript 高级程序设计》,我读了很多遍。不是我自己想读那么多遍,而是当时我带回学校的就只有这一本书,我一个月才回家一次。那一个月我只能天天都读他。这本书我每读一遍都有新的发现。
出来工作以后我还能像这样把一本好书读这么多遍又读这么认真吗?可以,但是很难了。
我高中的时候,最喜欢上语文课。语文课本选的文章其实挺好的,比如说有《棋王》,以及很多《古文观止》里面的文言散文。偶尔发个新试卷,我也很开心,因为一张语文试卷有两篇阅读理解。我高中读的很多书,都是在试卷里和练习册里发现的,新的练习册一发下来,我就把所有的阅读理解都看一遍,看到哪篇我觉得不错的,我就看是节选哪本书,然后就去图书馆借回来。另外,写作文大概是我在学校里唯一的思考输出的方式了,因此我很珍惜每次写作文的机会,我把自己的所思所想全写进文章,以至于每次都很低分,因为离题万里。我的语文老师对我很包容,他知道我想什么,我很感谢他。
有人会说,你把学校说得那么好,你自己怎么就辍学了呢。
我认为,一个人在结束学校生涯以前,必须先弄清楚两个问题。一个是,**在离开学校以后,你在社会上是以一个什么样的角色存在?**另一个是,**你是否已经为迎接这个角色做好了充分准备?**学校的意义就是给你时间想清楚问题一,然后给你条件完成问题二。
我在学校里,早已弄明白了这两个终极问题。我在社会上,将会以软件工程师的角色存在,并且我已经有能力胜任这个职位。往小的说,我能为我所在的公司创造价值,往大的说,我要为整个社区作出贡献。所以(在一所不怎么样的)大学对我来说已经不那么重要了,我希望早点投身社会创造价值。
很多人就是因为在学校的时候从来不思考这两个问题,所以是「光着身」离开学校的。你是想创造价值,还是想做着不喜欢的工作然后满足于 奶头乐 ,其实都由你自己决定。
以上,就是我在学校学到的东西。
延伸阅读
]]>从第一个 commit 读源码的好处是:
在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。
真的需要状态管理库吗?在之前,我没有信心回答这个问题。但在使用了 GraphQL (Apollo) 后,我确信,在大多数场景,你不再需要状态管理。
这篇文章的目标就是让你认识 GraphQL / Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。
Keep frontend code lean and straight. —— Randy Lu
本文的前端代码在 CodeSandbox https://codesandbox.io/s/pwmrnjz2km
本文使用大量 ES6+ 特性,请在阅读本文前熟悉 ES6+ 语法。
GraphQL 是一个查询语言,和 SQL 是同等概念的。
举个例子,在 RESTful 的场景里,我们查询一个资源是通过命令式地进行网络请求:
const posts = await fetch('/api/v1/posts')
而使用 GraphQL, 是声明式地查询:
query {
posts {
title, body, id
}
}
写数据时,命令式地 POST:
const response = await fetch('/api/v1/posts', { method: 'POST', body: { title: "foo", body: "content" } } )
使用 GraphQL, 声明式地触发 mutation:
mutation {
createPost(post: { title: "foo", body: "content" })
}
你也许会疑惑,这些 GraphQL 语句怎么执行?其实这些语句需要被转换,而转换的工具就是接下来要介绍的 Apollo.
Apollo 是一系列的 GraphQL 工具链,从客户端(不同的前端框架)到服务器端都提供了使用和搭建 GraphQL 的工具。
下面会通过一个简单的例子,让你从前端到服务器端对 GraphQL 有个初步的了解。
想象有这样一个需求:用表格展示一组数据。
后端告诉你,有如下接口:
这个接口可以获取所有 Post
, 返回的格式如下:
interface Post {
userId: number,
id: number,
title: string,
body: string
}
第一步我们需要搭建一个 GraphQL 服务器。
搭建一个 GraphQL 服务器不难,Apollo Server 对主流的 Node.js Web 框架都有封装,本文不赘述如何搭建一个 GraphQL 服务器,只介绍 GraphQL 后端编写的一些概念。
用 Apollo Server 编写 GraphQL 服务器有两个主要概念,typeDefs
和 resolvers
.
typeDefs
指的是类型定义。GraphQL 是一个有类型系统的查询语言,因此在编写 GraphQL 服务时,要先对查询的数据类型进行定义。
我们已经知道 Post
的数据类型是怎样的,就可以编写 Post
的类型定义:
import gql from 'graphql-tag'
const typeDefs = gql`
type Post {
userId: Int!
id: Int!
title: String!
body: String!
}
`
另外,我们需要对 Query
进行定义,来定义有哪些查询操作:
import gql from 'graphql-tag'
const typeDefs = gql`
type Post {
userId: Int!
id: Int!
title: String!
body: String!
}
+ type Query {
+ posts: [Post]
+ }
`
在 官方文档 详细了解 GraphQL 的类型系统。
这样一来,外界就可以通过
query {
posts {
id, title
}
}
这样的查询语句查询到 posts
了。
光是类型定义还不够,因为服务器还不知道「查询 posts」这个操作到底应该做什么。这里就是 resolvers
要做的事了。在 resolvers
里定义查询的实际行为:
const resolvers = {
Query: {
async posts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
return res.json()
}
}
}
在 官方文档 详细了解
resolvers
的用法。
最后,通过 Apollo Server 把 typeDefs
和 resolvers
连起来,一个 GraphQL 服务器就成功搭起来了。
const server = new ApolloServer({ typeDefs, resolvers })
server.listen().then(({ url }) => {
console.log(`Ready at ${url}`)
})
我在本文用到的 GraphQL 服务器源码在 https://github.com/djyde/graphql-jsonplaceholder , 通过 https://graphql-jsonplaceholder.now.sh 可以访问 Playground.
你也可以通过 Apollo Launchpad 在线上快速搭建一个测试用的 GraphQL 服务.
有了 GraphQL 服务后,我们开始编写前端组件。首先要创建一个 ApolloClient
实例。最简单的方法是通过 apollo-boost
:
import ApolloClient from "apollo-boost";
const apolloClient = new ApolloClient({
// GraphQL 服务器地址
uri: "https://graphql-jsonplaceholder.now.sh"
});
ApolloClient
可以命令式地进行查询:
const result = await apolloClient.query({
query: gql`
query {
posts {
id, title, body
}
}
`
})
不过,更高效的做法是用 <Query />
和 <Mutation />
组件进行声明式的查询。因为它们用了 Function as Child Components
的模式,把 loading
状态,返回的数据 data
都通过参数传递。你不需要手动去管理请求的状态。
import { Query, ApolloProvider } from 'react-apollo'
import gql from 'graphql-tag'
import { Table } from 'antd'
const GET_POSTS = gql`
query GetPosts {
posts {
id, title
}
}
`
const App = () => {
return (
<Query
query={GET_POSTS}
>
{({ loading, data }) => {
const columns = [
{
title: "ID",
dataIndex: "id"
},
{ title: "Title", dataIndex: "title" }
]
const dataSource = data.posts || []
return (
<Table
size="small"
loading={loading}
dataSource={dataSource}
columns={columns}
/>
);
}}
</Query>
)
}
export default () => {
return (
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
)
}
<ApolloProvider />
的作用是向所有子组件里的<Query />
和<Mutation />
传递ApolloClient
实例.
我们希望通过一个下拉框 <Select />
选择需要获取的 Post 数量:
我们可以让 posts
查询接受一个 limit
参数:
import gql from 'graphql-tag'
const typeDefs = gql`
type Post {
userId: Int!
id: Int!
title: String!
body: String!
}
type Query {
+ posts(limit: Int): [Post]
}
`
然后在 resolvers
里拿到参数,进行处理:
const resolvers = {
Query: {
async posts(root, args) {
// 每个 resolver 的第二个参数就是查询参数
const { limit } = args
const res = await axios.get('https://jsonplaceholder.typicode.com/posts', {
params: {
_limit: limit
}
})
return res.json()
}
}
}
在前端,<Query />
的 variables
props 可以传递参数:
import * as React from "react";
import { Table, Select } from "antd";
import { Query } from "react-apollo";
import gql from "graphql-tag";
const GET_POSTS = gql`
query GetPosts($limit: Int) {
posts(limit: $limit) {
id, title
}
}
`
export default class Limit extends React.Component {
state = {
limit: 5
};
onChangeLimit = limit => {
this.setState({ limit });
};
render() {
return (
<div style={{ padding: "2rem" }}>
<Query
query={GET_POSTS}
variables={{ limit: this.state.limit }}
>
{({ loading, data }) => {
const columns = [
{
title: "ID",
dataIndex: "id"
},
{ title: "Title", dataIndex: "title" }
];
const dataSource = data.posts || [];
return (
<React.Fragment>
<div style={{ marginBottom: "12px" }}>
<Select
onChange={this.onChangeLimit}
value={this.state.limit}
style={{ width: "100px" }}
>
<Select.Option value={5}>5</Select.Option>
<Select.Option value={10}>10</Select.Option>
<Select.Option value={15}>15</Select.Option>
</Select>
</div>
<Table
rowKey={record => record.id}
size="small"
loading={loading}
dataSource={dataSource}
columns={columns}
/>
</React.Fragment>
);
}}
</Query>
</div>
);
}
}
在 官方文档 详细了解 GraphQL 查询变量定义
接下来实现创建一篇 Post:
当我们需要操作数据的时候,就要用到 Mutation
. 还用到一个特殊的数据类型 Input. 通常用来在 Mutation
的参数里传一整个对象。
const typeDefs = gql`
input CreatePostInput {
title: String!
body: String!
}
Mutation {
createPost(post: CreatePostInput!): Post!
}
`
然后在为 createPost
这个 mutation
创建一个 resolver
:
const resolvers = {
Mutation: {
async createPost(root, args) {
const {
post
} = args
const res = await http.post('/posts', {
data: post
})
const now = Date.now()
const id = Number(now.toString().slice(8, 13))
return {
...res.data.data,
id,
userId: 12
}
}
}
}
前端结合 Ant.Design 的 <Modal />
, <Form />
组件和 react-apollo
提供的 <Mutation />
组件,就可以完成整个「新建 Post」动作:
const GET_POSTS = gql`
query GetPost($limit: Int) {
posts(limit: $limit) {
id, title
}
}
`;
// 「新建 Post」 的 Muation
const CREATE_POST = gql`
mutation CreatePost($post: CreatePostInput!) {
createPost(post: $post) {
id, title
}
}
`
class CreatePost extends React.Component {
state = {
modalVisible: false
};
showModal = () => {
this.setState({ modalVisible: true });
};
closeModal = () => {
this.setState({ modalVisible: false });
};
// Modal 的 onOk 事件
onCreatePost = createPost => {
const { form } = this.props;
form.validateFields(async (err, values) => {
if (!err) {
// `createPost` 是 `<Mutation />` 组件传给 children 的 mutation 方法
await createPost({ variables: { post: values } });
this.closeModal();
form.resetFields();
}
});
};
render() {
const { form } = this.props;
return (
<div style={{ padding: "2rem" }}>
<Query query={GET_POSTS} variables={{ limit: 5 }}>
{({ loading, data }) => {
const columns = [
{
title: "ID",
dataIndex: "id"
},
{ title: "Title", dataIndex: "title" }
];
const dataSource = data.posts || [];
return (
<React.Fragment>
<Mutation mutation={CREATE_POST}>
{(createPost, { loading, data }) => {
return (
<Modal
onOk={e => this.onCreatePost(createPost)}
onCancel={this.closeModal}
title="Create Post"
confirmLoading={loading}
visible={this.state.modalVisible}
>
<Form>
<Form.Item label="Title">
{form.getFieldDecorator("title", {
rules: [{ required: true }]
})(<Input />)}
</Form.Item>
<Form.Item label="Body">
{form.getFieldDecorator("body", {
rules: [{ required: true }]
})(<Input.TextArea />)}
</Form.Item>
</Form>
</Modal>
);
}}
</Mutation>
<div style={{ marginBottom: "12px" }}>
<Button onClick={this.showModal} type="primary">
New Post
</Button>
</div>
<Table
rowKey={record => record.id}
size="small"
loading={loading}
dataSource={dataSource}
columns={columns}
/>
</React.Fragment>
);
}}
</Query>
</div>
);
}
}
export default Form.create()(CreatePost);
和 <Query />
一样,<Mutation />
把请求状态都传递给了 children.
在 官方文档 详细了解
<Mutation />
的用法
成功「新建 Post」以后,通常我们会更新数据列表。react-apollo
有两种方法实现。
<Mutation />
有 update
这个 props. 在 mutation
执行成功后回调,并且带有 cache
和 mutation
的响应数据。我们可以通过更新 cache
来实现更新数据列表。
例如,在获取数据列表的 <Query />
中,是通过 GET_POSTS
来查询的:
query={GET_POSTS} variables={{ limit: 5 }}
那么,在 update
回调里,我们可以得到 GET_POSTS
对应的 cache, 然后更新这个 cache. 更新 cache 后,通过 GET_POSTS
(以及相同的 variables
) 查询的组件,会自动 rerender:
const update = (cache, { data: { createPost } }) => {
// 取得 `GET_POSTS` 对应的 cache
// 注意要和你要更新的组件的 query 和 variables 都要一致
const { posts } = cache.readQuery({ query: GET_POSTS, variables: { limit: 5 } })
// 用 mutation 的响应数据更新 cache
// 同样,query 和 variables 都要一致
cache.writeQuery({
query, GET_POSTS,
variables: { limit: 5 },
data: { posts: [createPost].concat(posts) }
})
}
有时我们想要直接重新请求数据列表而不是手动更新 cache. 我们可以使用 refetchQueries
返回一个你要重新查询的查询数组:
const refetch = () => {
return [
{ query: GET_POSTS }
]
}
这样,所有 query 是 GET_POSTS
的组件都会重新执行查询并 rerender.
Ant.Design 的 Table
组件可以通过 Pagination
很容易地实现分页异步加载.
首先先让 GraphQL 接口支持分页:
const typeDefs = gql`
type Post {
userId: Int!
id: Int!
title: String!
body: String!
}
+ type Meta {
+ total: Int!
+ }
+ type PostResultWithMeta {
+ metadata: Meta!
+ data: [Post]!
+ }
type Query {
posts(page: Int, limit: Int): [Post]
+ postsWithMeta(page: Int, limit: Int!): PostResultWithMeta!
}
`
const resolvers = {
Query: {
async postsWithMeta(root, args) {
const {
page, limit
} = args
const res = await http.get('/posts', {
params: {
+ _page: page,
_limit: limit
}
})
return {
+ metadata: {
+ total: res.headers['x-total-count']
+ },
+ data: res.data
}
}
},
}
前端就可以传 limit
和 page
实现分页:
const GET_POSTS = gql`
query GetPosts($limit: Int!, $page: Int) {
postsWithMeta(limit: $limit, page: $page) {
metadata {
total
},
data {
id, title
}
}
}
`;
export default class Pagination extends React.Component {
// 传给 Ant.Design Table 的 pagination 信息
state = {
pagination: {
pageSize: 10,
current: 1,
total: 0
}
};
// Query 完成后,给 pagination 设置数据总数
onCompleteQuery = ({
postsWithMeta: {
metadata: { total }
}
}) => {
const pagination = { ...this.state.pagination };
pagination.total = total;
this.setState({ pagination });
};
handleTableChange = pagination => {
const pager = { ...pagination };
pager.current = pagination.current;
this.setState({ pagination });
};
render() {
return (
<div style={{ padding: "2rem" }}>
<Query
onCompleted={this.onCompleteQuery}
query={GET_POSTS}
variables={{
// 在 pagination 信息中得到 `limit` 和 `page`
limit: this.state.pagination.pageSize,
page: this.state.pagination.current
}}
>
{({ loading, data }) => {
const columns = [
{
title: "ID",
dataIndex: "id"
},
{ title: "Title", dataIndex: "title" }
];
const dataSource = data.postsWithMeta ? data.postsWithMeta.data : [];
return (
<Table
pagination={this.state.pagination}
onChange={this.handleTableChange}
rowKey={record => record.id}
size="small"
loading={loading}
dataSource={dataSource}
columns={columns}
/>
);
}}
</Query>
</div>
);
}
}
GraphQL 比 RESTful 的优势在于,GraphQL 让你专注于你想做什么,想获取什么。「查询语言」是声明式的,而「HTTP 请求」是命令式的。声明式可以让复杂度转移给运行时,就像 GraphQL 语句最终执行的 HTTP 请求可以交给像 Apollo 这样的封装去处理。
当你不再需要自己管理这么多 HTTP 请求的状态时,你就要仔细考虑你的应用到底需不需要状态管理工具了。尤其在开发中后台类的管理系统应用时,往往不会涉及复杂的数据流。Local state is fine.
]]>副标题: 《可能是最适合 Egg 的 React Serverside-rendering 方案》
上一周周末我花了些时间来完成了一个 React serverside-rendering 框架——Serlina. 在此想通过这篇文章讲讲 Serlina 框架本身,以及我为什么要开发她。
(下文中 React Serverside-rendering 均简称为 "SSR")
最直接的起因是我们在内部有一个 React base 的项目的首页希望做服务器渲染,我参考了一些方案,如 Next.js, Fusion.js 等等。我很喜欢 Next.js, 我从他刚发布的时候就在持续关注,我认为他已经是最完美的 SSR 方案。
但是当我试图把 Next.js 接入到我们的服务器端 (Egg.js base) 时,我发现 由于 Next.js 需要控制 http context, 导致无法兼容 Egg 程序。
我认为 Next.js 的核心应该可以脱离 http context. 只需要完成构建配置、renderToString 这些脏活,然后把渲染后的 HTML String 返回即可。于是我浏览了 Next.js 的代码,试图寻找类似 nextjs/core
的东西,然而并没有。Next.js 是一个完整的 Web Framework.
于是我开始设计一个理念是脱离服务器实现的 SSR 框架,并取名为 Serlina. 她和 Next.js 拥有同样友好的开发体验,唯一不同之处是,她不关心服务器实现。
安装依赖
npm i serlina react react-dom --save
创建一个应用目录
├── index.js
├── page
│ └── page1.js
编写一个 React 页面
// page/page1.js
export default () => {
return <div>Hello Serlina!</div>
}
最后是服务器的实现
// index.js
const { Serlina } = require('serlina')
const path = require('path')
const http = require('http')
// 初始化 Serlina
const serlina = new Serlina({
baseDir: path.resolve(__dirname, './')
})
serlina.prepare()
.then(() => {
http.createServer(async (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' })
if (req.url === '/page1') {
// 渲染页面
const rendered = await serlina.render('page1')
res.write(rendered.string)
} else {
res.write('works!')
}
res.end()
}).listen(8090)
})
.catch(console.error)
通过以上的例子,Serlina 有两个最主要的 API:
prepare()
用于做构建准备render()
用于渲染 React 页面, 得到 HTML string.以上的例子也表达了 Serlina 的核心思想——她处理了 React 服务器渲染的一切脏活,然后把处理好的东西交给你自己去渲染到客户端。
这就是「渐进式」的意思:你可以在某些地方用她,也可以在某些地方不用她。你可以只在某个路由里面使用 serlina.render() 去渲染。这有点像是一个模板引擎。
我之所以认为 Serlina 是最适合 Egg 的 SSR 方案,是因为我认为 Next.js 是最好的 SSR 方案。而 Serlina 把 Next.js 的体验带到了 Egg, 那么她应该就是最适合 Egg 的 SSR 方案。
以下内容非 Egg 用户可以跳过。
npm i egg-serlina react react-dom --save
exports.serlina = {
map: {
'/page1': 'page1'
}
}
配置了用 Serlina 渲染的页面后,页面会在 getInitialProps
里得到 egg 的 ctx:
// {app_root}/client/page/page1.js
export default class Page1 extends React.Component {
static async getInitialProps({ ctx }) {
// ctx is egg `ctx`
return {
data: await ctx.service.getData()
}
}
render () {
return (
<div>{this.props.data}</div>
)
}
}
关于这个问题,上文已经说得很清楚了。另外,Serlina 并不是要取代 Next.js, 而是希望在某些场景,能成为一种合适的选择。
]]>前端有很多不同的框架和库。 我上大一的时候,有一门课叫网页设计,前几个课时教 HTML, 又教一个课时 CSS, 再教一个课时 JavaScript. 然后是 jQuery. 当时我很气愤,学生连 DOM 是什么都还没搞清楚,就要他们用 jQuery,这不是误人子弟吗。我后来想明白,学校大概已经把 jQuery 看作是饭碗了。
把库和框架看作饭碗是无可厚非的, 我们不能苛刻地要求所有程序员都对软件开发抱有发自内心的热爱。但是急功近利地学习框架,绝对是举步维艰的。胡适说「多研究些问题,少谈些主义」,在编程上,我认为需要多研究些问题,少谈些框架。
框架是我们达到目的的便捷手段,我们使用 React, 更要知道为什么使用它?它的本质是什么?它解决了什么问题?所谓「格物致知」,不「格物」,如何「致知」呢?
]]>我的 2017 过得很平淡,可能是因为出来工作已经是第二年了。
今年比较特别的是身边的朋友都大四了,参加了一些毕业拍照。大家都陆陆续续地开始找工作,面试。偶尔在朋友圈看到朋友拿到 offer, 也会替他们感到开心。
有很多朋友开始请教我一些出来工作的经验,面试的技巧、offer 怎么选择、租房的经验等等。被问到这些问题的时候我也会很开心。因为以前上学的时候,学业成绩不好,我在班上除了搞搞笑,对身边的同学来说并没有多大的「用处」。
4 月份的时候上台拿了 16 年的优秀新人奖,但是 17 年整一年没有做出很出色的成绩。慢慢失落地发现在很多事情上,技术并不是那么重要,它只不过是一种手段而已。
在年底,和 @EGOIST 一起创办了 StickerOverflow. 成功地让国内的开发者能买到高质量的技术贴纸。
今年想得比较多的是应该如何用自己的能力去帮助更多的人。想过去做培训,想过回大学给以前班的人开个交流会,想过写书。最后都搁置了,因为我后来发现,「助人」有时是一件很一厢情愿的事。
除了技术外,这一年有很多新的尝试,上台唱歌纪念张国荣,参加了唱歌的比赛,做了一期音乐电台节目。在新的一年,还想尝试去经营一个音乐博客,让更多人发现被忽略的优秀粤语流行音乐和歌手。
今年博客的 PV 和 UV 都小幅度降低了,原因是今年下半年文章写得很少。因为花了不少时间在比写文章更有趣的事情上。
博客没有广告,但还是会有人因为读文章而受到启发给我赞赏,今年一共收到 ¥136 的赞赏。更开心的是收到了一些真诚的交流邮件。
PV: 33,247 (去年 49,406), UV: 12,172 (去年 15,800)
把很喜欢的 iPhone SE 退役了,因为跑 iOS11 耗电快,性能不够。
为了方便写数码产品评测,入手了黑卡 1 代。
上半年用 DS216j,因为性能满足不了,年底换成了 DS218+. 关于 NAS 的使用,可以参考这篇文章.
因为要学习 Keyboard,入手了 Yamaha P115 电钢来入门。P115 是全配重的,手感很好,但是重量大,不便携。为了可以携带出去户外表演,于是又入了轻一半重量的 61 键的 NP12。
买了 Switch, 圆了掌机梦,玩了《塞尔达传说》和《奥德赛》。因为便携,吃灰率要比 PS4 低。
2018 年的计划是多写写代码,多玩玩音乐,读更多书,换一间大些的房子,买一辆车。
有次和朋友聊天,她说,你知道自己想要做什么,又能把它做到,还能靠它养活自己。你千万不能变得世俗啊,如果你也变得世俗,我会觉得这个世界,真的就是那样了。
]]>这份刊物叫《新视觉》。当年要做这份刊物,是因为那段时间,我读了很多民国时期的文学作品,还读了李开复的那本《世界因你而不同》,使我开始对政治、教育有了不一样的看法。
当时的我认为文字只有背负了政治的责任,才能称得上文学。所以看不起很多「当代文学」。我对当代文学的定义是 —— 1990 年以后出现的文学。
班里一个女性朋友很喜欢郭敬明,我却对此嗤之以鼻。于是我给我朋友传教似地讲了很多诸如为什么我们需要关心政治、我们应该读些什么书、我们需要什么样的教育等等这样的话题。但是她不以为然,认为我越俎代疱。
我想,我应该做一份刊物,来传达我的理念。因为「不识字的上人的当,识字的的上印刷品的当」。
就这样,我拿了一沓我们学校标准的信签纸,开始做了起来。我把这份刊物叫做《新视觉》,是源于白话文运动时,陈独秀所创的《新青年》。我认为这份刊物的目标,是为了提倡新思想,以摘抄严肃文学作品的形式,启发班上的同学,应该从现在起,去寻找自己的目标,自己想要的是什么,未来想做的是什么。而不是把所有时间,都花在试卷和娱乐上。
为了传达这份理念,我在首期的《新视觉》的第一页,认真地誊写了 Robert Frost 那首诗 —— The Road Not Taken.
我现在已经记不清当时摘抄了哪些具体的文字了,只记得抄过王小波、李敖、韩寒、胡适等等。
这份全手写的刊物开始在班上互相传阅,并不是因为内容深刻,而是因为形式新鲜罢了。我为了这份刊物可以维持下去,「聘请」了上文提到的女同学作为编辑,也接受所有同学的来稿。
接受同学的来稿后,刊物渐渐成了班级的一部分,甚至每一期都会有人催我「出版」。当时的「出版」形式,则是收集来稿,用钉书机装钉成册。
同学的来稿很有意思,有人开始做起了小说连载。而我还是做一些摘抄,顺便构思一些虚构类的讽刺文章。同时 Bunny 开始把手写的刊物改革成了真正的印刷品——每周回家录入然后打印出来。
因为刊物带有媒体属性,班级上一些事件也会被刊登到里面去。例如校运会,我们做了一份校运会特刊,报道了校运会的情况、现场照片。
虽然刊物在班上火了,但我并不是太开心,因为这本刊物慢慢偏离了我的初衷。我向 Bunny 提出要做一本《新视觉》的子刊物,就叫做「16 度」(我们是 16 班),把小说连载、读者来信这些内容都放到里面,《新视觉》只做严肃文学。
于是 Bunny 开始打造《16 度》,而我继续负责《新视觉》。发展到后来,《16 度》其实有很多地方和《最小说》变得很相似,是同学们饭后的话题、减缓考试压力的地方。很多人在上面连载小说。不生产的内容的,也会来留言,说自己对某篇文章的读后感。而这份带有意识形态的《新视觉》很快就没有再连续出版下去了。
以上是我对这份刊物仅剩的几乎所有回忆,手写的稿早就在初中一次搬课室的过程中丢失了。谁会想到这些东西未来会被这样津津乐道呢。这确实是一个遗憾。
从手写改革到印刷的头几期
校运会特刊的「发布会」
校运会,「小编」们的一次合照
]]>传闻说猫的智商相当于 2~3 岁的小孩,所以我会把养她当作是在养我的小孩,每个月要花钱换猫砂,去超市要顺便买些罐头,偶尔也想去宠物店看看有什么好玩的玩具。养猫和养小孩的区别,我想大概就在于前者的过程中不需要担负教育的责任。教育很难。
我外甥女今年刚好是 3 岁,有时候我也觉得和猫很像:见到什么都要摸摸看,甚至打翻。
对于猫打翻东西,一般来说,主人都会生气。但是我想,3 岁的小孩打翻了东西,其实是监护人的责任。既然家里多了这个不懂事的小孩,易打翻的东西就不该让她碰到。出现事故,自己要先反思。
她不会完全信任我,她吃东西,我路过,她有时还是会立刻跑开。帮她滴清耳液的时候,她看到我拿着瓶子也会跑开。但有时候我觉得她也很信任我,每次回家,不管她的时候她都要一直叫,非要爬过来腿上让我摸才停。养猫有趣的地方就是这里,你要向一种时刻保持警惕的生物身上获得信任。
第一次带她去医院打疫苗,在医院里我和几个素味谋面的猫主人狗主人竟然可以自然地打成一片,互相交流育宠经验,然后让自己的宠物见一见对方的宠物。大家都像妈妈们互相交流育儿经验似的。
有时候在路上看到走过的流浪猫,会想要不要把它养了,让她有个伴。但是想想,我确实还没有做好二胎的准备。
前段时间发烧,在搞保险报销的时候,我想如果可以给宠物也买保险,我也会给她买一份。
养一只猫不容易,何况养一个人。所以养猫之后,有时我会留意街上怀孕的路人,想她们是不是做好了准备,去承担这份责任呢。这需要一份勇气和担当,还有深思熟虑。
鲁迅在《热风》里谈到孩子,他说,「中国的孩子,只要生,不管他好不好,只要多,不管他才不才。生他的人,不负教他的责任。虽然“人口众多”这一句话,很可以闭了眼睛自负,然而这许多人口,便只在尘土中辗转,小的时候,不把他当人,大了以后,也做不了人。」
因此我很怕,我没有准备好,没有想好要怎么做教育。我要先把猫照顾好,才有信心去养一个人。
以上。
]]>当初考虑使用 NAS,是因为使用了小米盒子后,开始追求离线下载的体验。我平时追美剧,理想中的场景是,每到美剧资源更新的时候,就可以下载到家里的硬盘中,再通过小米盒子观看硬盘中的视频。这样就能实现上班后到家打开电视直接看剧的需求。
半年以来,这台 NAS 成了我的数据中心,影视、照片、音乐、TimeMachine 都存放在两块 2TB 西数红盘上。
DS216j 有两个盘位。起初我在犹豫到底做 RAID0 还是 RAID1, 后来想想,我的硬盘里大部分是影音,做 RAID1 未免太浪费,倒不如做 RAID0, 然后定期把最重要的数据同步到另一块移动硬盘里。
在首次安装、启动、配置后,我把它放在家中的一个角落,之后就再也没有管它了,它就角落里安静地服役。即使它摆在你的房间,你也完全不会感受到他的存在。当然,如果你嫌 LED 灯太刺眼,可以在设置里关闭它。
NAS 的本质只是一台低功率、持续运行的计算机。理论上自己也可以 DIY 一台 NAS,有很多人也这么做。但是为什么很多人还会选择购买群晖的解决方案,一个很重要的原因是群晖的软件生态——DSM.
DSM 是群晖 NAS 中运行的「操作系统」。这个打引号的「操作系统」指的不是我们常说的操作系统,而是一个用于组织管理 NAS 上的文件的程序。DSM 提供了一个 Web 界面,一切操作都可以在这个界面中进行。
除了 Web 界面,群晖在移动平台提供一系列的 APP,读取和操作不同类型的资源。
在 Web 界面中,我一般会通过 File Station 管理我的文件,类似于 Finder.
DSM 的生态在于套件中心,但实际上在我半年里的使用来看,我真正用到的套件只有那么几个。
作为多媒体中心,有两个最重要的要素,分别是下载资源的体验,和观看资源的体验。
先说下载资源的体验。DSM 本身提供 Download Station 这个套件,可以在上面添加管理下载任务,也提供电驴和迅雷离线下载的入口。
但是使用多次后,我毅然放弃了 Download Station, 原因有几个:
Download Station 唯一比较有用的功能是提供从 RSS Feed 中下载资源的功能,比如我只要提供一个持续更新资源地址的美剧 RSS,通过过滤器,可以自动的根据正则表达式进行对应的行为。例如当 RSS 更新有「闪电侠」三个关键字的时候自动下载。
但是影响下载速度和稳定性的因素是资源的质量,如果单纯使用 http 下载,有些资源恐怕从我上班到下班回家都不一定下载完。所以需要放弃 Download Station, 另辟蹊径。
通过 ssh,我在 NAS 里跑了 Aria2, 配合像 BaiduExporter (百度盘导出任务到 Aria2) 这种插件,可以畅快地建立下载任务,临睡前把下载任务开始,一晚上能下载很多资源。
但是因为没有外网 IP 的原因,在外面我就无法直接访问 Aria2 的 Web UI 添加任务,所以我需要另一种下载方式进行互补。
我在 NAS 又跑了一个 Xware, 通过 Xware 可以关联迅雷远程下载和 NAS. 这样既能够在外面管理下载任务,又能享受迅雷的高速通道和离线下载资源。
至于观看,DSM 的 Video Station 会对文件夹中的视频进行分析,自动从多个影视 API 获取相关信息,然后整理。
然而我只会在使用移动设备进行观看的时候才会使用 Video Station. 因为 Web 版的 Video Station 用的是普通的 <video>
标签,支持的视频格式不多(DS216j 也没有转码功能),所以我一般是通过 Finder 连接 NAS,之后用本地的播放器进行播放。
因为我有一台小米电视,所以主要的影视播放都在电视上。我一般会通过两种方式在电视上播放 NAS 的视频。
第一种,通过小米电视自带的「高清播放器」应用,可以连接远程的服务器,只要填写 NAS 的地址和用户名密码,就能访问里面的文件夹。
我一般是从文件夹里找到我要看的视频。小米电视也会自动识别出文件的影视信息,但是识别不是特别准确。
第二种是通过 Airplay/DLNA,移动设备上用 DS Video 选好要看的视频,然后直接 Airplay 到电视上。
至于管理音乐的 Audio Station,对我这个音乐发烧友来说也显得尴尬。因为听音乐我更倾向于串流服务,比如 Spotify 和 网易云音乐,很少存放音乐文件,一般存放音乐文件到本地只是为了满足我对音乐的收藏欲。
Photo Station 做得很好,在买了 Dji Spark 以后,对图片视频存放的需求也就有所增多,因为 TF 卡的容量也有限。手机上我会常用 DS Photo 同步我比较喜欢比较重要的照片到 NAS 上。但是我没有使用增量同步的功能,因为我订阅了 iCloud 50GB 的空间,目前手机 7000 多张照片,只占用了 iCloud 26GB 多一些。
macOS 的 TimeMachine 原生支持网络设备,所以只要在 TimeMachine 里把备份位置设定到 NAS 的文件夹后,每当我回家插上电源,就会自动备份到 NAS 上。
NAS 极大的改善了我观看影视的体验,同时还成为了我的数据中心。以往 TimeMachine 要定期插一个移动硬盘进行备份,现在只要接上电源,无需操心。
DS216j 的价格很适合作为 NAS 的入门之选。然而入门 NAS 的花费不仅在于机器本身,还有硬盘的花费。同时你可能还需要把你的百兆路由扔掉,重新买一个千兆路由。
然而 DS216j 的配置十分尴尬,32 位的 CPU 导致你无法使用 docker 这个利器。 我安装 Aria2 和 xware 的时候,只能费尽力气找对应的编译好的包来安装的。所以如果有充足的预算,建议直接买 DS216 II+ 吧。
]]>很多人会问我,你是怎么会想到要编程的。其实是因为当时班上有一个和我很要好的同学,他在写 PHP。我小学上的是私立学校,每个教室讲台都配了一台用于播放教学 PPT 的电脑。有一天他告诉我他写了一个博客,用的是 PHP。然后在讲台的电脑打开了他博客的后台,改了几句 CSS, 博客的颜色就变了。我当时很震惊,觉得我自己也要学会。
但是我没有去学 PHP,原因是 PHP 的书太贵了。我拿着我交学杂费的 20 块找零,到购书中心买技术书,最终找到了一本 19 块的《C 语言程序设计》,不是谭浩强的,是一本中专教材。
当时的我对编程没有什么概念,对语言当然也没有概念。回到家,跟着书上的步骤,安装了 Turbo C, 写一些四则运算。
入门编程最难的其实就是这个阶段,面对白字黑底的命令行,写的是代码,做的是数学题(算水仙化数,算 Fibonacci)。
我是个数学不好的人,小学教到了除法以后,我的数学成绩就没上过 85 分。因为我花了很长的时间理解除法,到我理解了以后,教学进度已经很往后了。数学对程序员来说重要吗?我觉得很重要,因为数学决定了一个工程师的上限。但我实在太笨,无论如何也学不好。
于是我对 C 语言开始失去兴趣,开始搞 GUI 了。
我是 2009 年开始搞 GUI 的,那年刚上初一,读了李兴平的经历,备受鼓舞。李兴平是 hao123 的作者,2004 年的时候被百度收购了。我决心也要做一个网址导航。
因为这个网址导航的目标,我正式接触了前端开发。从一本教 Dreamweaver 的书里面学了 HTML, 在 w3school 学了 DIV + CSS 布局。很快我就写出了第一个网址导航的 demo, 但是是静态的页面,里面所有的网址栏位都是自己手动编辑的。
这段经历对我来说收获很大,因为通过编写一个网址导航,掌握了很多 HTML, CSS 的技巧。这些技巧是无法通过书本系统习得的。
做技术不是照本宣科,因为语法是有限的,但是想象力和智慧是无限的。举个例子,你可能从文档和书本上了解了 float 属性,但是只有当你开始写一个横向导航的时候,才会思考到,可以用 float 把 ul
中的纵向的 li
变成横向。
另外一个例子是 TJ 的 co
. 可能大多数的人都知道 Generator, 但是 TJ 想到了基于 Generator 把异步写得像同步。这是一种想象力。所以为什么要读源码,因为语法是有限的,想象力是无限的。
这使得我多年来一直保持一种习惯,来保持我的想象力。就是每当我看到一些有趣的网页效果的时候,都会先思考,如果我来写,我会怎么写。最后审查元素,看看我想的和具体实现是不是不同?如果不同,是不是别人的实现比我更好?是不是用到了开源库?如果没有,那么我是不是可以把这种效果封装起来?
文档只会告诉你 border-radius 的特性,但是人们的想象力可以用其特性实现一个三角形。在人类的进化当中,是想象力使智人到达了食物链的顶端。在软件开发上,我认为是智慧和想象力使工程师达到卓越。
在掌握了基本的网页开发技能以后,我的重心转移到了桌面软件的开发上。因为当时我错误判断了桌面软件的前景,认为桌面软件才是未来。所以我尝试了 Delphi, 因为 Delphi 而学习了 Pascal.
其中一个很有趣的经历是我学过 Delphi 以后意外地得知了易语言。易语言号称中文开发,所有语句都是中文的。易语言内置的控件库非常丰富,是当时我被吸引的最重要的原因。
学习了一段时间后,我开发了第一个上线到软件站的软件,叫做「网帝系统清理助手 2009」,至今还能 Google 出来。
我那时非常中二,成立了自己的虚拟软件工作室,取名「网帝」。这是一个充满抱负的名字,因为他的含义是「互联网帝王」。
软件上线了以后,在各个软件站的下载量很不错,于是我又马不停蹄地开发了第二个软件,叫「网帝 QQ2009 去广告精灵」,用来去 QQ2009 里的广告的。
我那时候开始泡易语言的论坛,结识了很多论坛里的大牛,那时候称「大虾」。我记得有一个网名叫「凝逸」的版主,用易语言开发了一个叫「凝逸反病毒」的杀毒软件。我很震惊,因为我非常好奇杀毒软件的工作原理。于是我在他的帖子里询问,但是他没有回过我一次。
我又通过自己的摸索,做了一个可以根据病毒库查找病毒、保护 IE 主页的杀毒软件,「极光反病毒」。
以上三个软件是我做桌面软件时的产出,当时还在读初一。做完极光反病毒的第一版后,我觉得这种反病毒太傻,要依赖特征库去匹配病毒。病毒稍微变种,就查不出来了。于是我想要做一个防御式的反病毒工具,根据文件的行为,来判断它是不是病毒。比如如果这个文件在不断复制自己,那么它肯定是病毒。
易语言没有文件监控的 API, 我只好学习一个微软的亲生儿子,C#.NET 来做这件事。不过后来学到一半,由于学业压力,没有继续再做下去。
2010 年年底有一部电影对我影响很大,讲 Zuckerberg 创立 Facebook 的经历,叫《社交网络》。
这部电影我反复看了 7, 8 遍,我觉得用技术做出有很多人喜欢的产品太酷了,我很想成为这样的人,应该是我终身奋斗的目标。这部电影,直接导致我放弃了桌面软件开发,正式转向了 Web 开发。
我的 PHP 就是这段时期学的,因为 Facebook 是用 PHP 写的。我买了一本蓝色封面的书,《PHP + MySQL 程序设计》。这本书的内容讲得头头是道,但是我一点都看不懂。我对 Web 开发除了网页设计以外一窍不通。结果我又去买了一本国产的书,《PHP 兄弟连》。
这本书的作者是专门搞 PHP 培训的,讲得比较浅显易懂,成了我的救命宝典。从那本书里,我对 PHP Web 开发有了一个大概的了解,知道 LAMP 架构,知道一个完整的 Web 系统应该有哪些基本的部分,知道什么是模板引擎,更重要的是,开始接触 Linux。读完读那本《PHP + MySQL 程序设计》,犹如打通了任督二脉。
后来我用 PHP 做了一个我们校内的非常精简的社交平台,不是说我那时就开始崇尚极简,而是我的能力就到这了。于是脑抽风去找学校的副校长谈,结果副校长说这个不错,但是我们不主张校内建的讨论区。
我深受挫败,然后就学 Python 去了。
学 Python 是因为一个 Python Web 框架 Tornado. 看多了 PHP, 会觉得 Python 非常美。Tornado 本身也很美,尤其是路由的设计,和 Web.py 一样美。我从此再也没写过 PHP。
我没有用 Python 写过什么有效的产出,但是从学习 Tornado 的过程中,我了解了异步的概念。Tornado 是个异步 IO 的 Web 框架,这对我之后学习 Node.js 十分有帮助。
整个初中大概就是这么过去的,初三那段时间是一段技术真空期,因为有升学压力,并且开始沉迷文学作品。
升上高中以后是我技术上一个很重要的转折点,因为我鬼使神差地从一个成绩优异的学生,变成了一个老师眼中的差生。
这对我来说意义非凡,因为我由此决定放弃学业,专攻编程。那时候 2011 年年底,刚好是 Node.js 最火的那一年。
我是在那年开始读《JavaScript 高级程序设计》的。我并非毫无基础地读这本书,我在写网址导航的时候,已经读过几遍《JavaScript DOM 编程艺术》了。
所以很多人到后来会问我,我想学前端,有什么书推荐?我都会给他推荐这两本书。先读完《JavaScript DOM 编程艺术》,再反复读《JavaScript 高级程序设计》。为什么是强调反复读,因为就我来说,这本书,我每读一遍,都有新的收获。甚至工作以后遇到问题,我也会翻一翻这本书,重新复习里面的原理。
和我接触过的人,不多不少都知道我是一个相信社区的人,因为我算是见证着 JavaScript 在社区发展的,一群人是怎么样通过社区创造生态的。没有社区,一切都不可能。
所以我也积极参与到社区里面去。很多人害怕社区,认为自己的水平不够,其实是种错误的想法。参与社区不仅仅代表贡献代码。阅读源码,发表源码分析的文章,这是参与社区;阅读代码,动手修改 bug, pull request, 这是参与社区;不读代码,帮忙解决 issue, 这是参与社区;不读代码,关注一个项目的进展、规划、出谋划策、提意见,这也是参与社区。这样的水平要求高吗?
学 Node.js, 我用 Express, MongoDB 写了一个可以提交 Chrome 插件的插件商城。这个项目对我自己的意义在于,整个产品都由我自己一手包办,部署、项目架构、数据库设计、文案,等等。一手包办的意义在于,我知道链条中的每一部分是怎么工作的,前端和后端是如何配合的,这使得我往后在团队合作的时候,和别人的沟通十分流畅。
在高中的最后一个阶段,我学习了 Angular, 双向绑定那时在前端是一个新鲜事,颠覆了以往以手动操作 DOM 节点为核心的开发模式。那段时间印象最深刻的是,我花了大量时间,来把自己的思维从 MVC 的模式,转换到 MVVM 的模式。
高考完的暑假,我入手了人生第一台 Android 手机 Nexus 5, 我开始学 Android 开发。在学 Android 开发以前,我是一个 Java 无脑黑。认为 Java 罗嗦,严格类型。但是从 Android 开发的经历,我对 Java 逐渐改观。很多在我看来的无意义的、罗嗦的代码里,换来的是工程上的健壮性,可维护性。我会思考,如果可以选,我愿意用 JavaScript 去写 Android 的 Application 层吗?我不愿意,因为 JavaScript 不适合。
虽然我后来也上架了些 Android App, 但是学会辩证地看待每一门语言却是我学习 Android 最宝贵的财富。
上完大一我就退学了,原因简单来说就是旷课太多。整个大一我进步很大,因为 Android 开发和前端开发我是并行学习的。退学以后,就开始了职业生涯。
其实我没有想过我会以前端工程师的身份进入这个行业,因为我高考后是立志成为一名优秀的 Android 工程师的。前端于我而言,只是一个基本技能。但是找工作迫在眉睫,又不能拿我半桶水的水平去应聘 Android 开发。
所以我的意思是,无论我现在的职位方向是什么,比如我现在是前端工程师,但我首先是一个软件工程师,其次才是前端工程师。语言只不过是你的工具而已。
如果要我说我学习编程有什么技巧,我认为是首先要清楚,你想做的是什么。再去想,你用什么去做。
最后再分享一些一路以来的点滴:
我家人都是很传统的一代,不理解我学习编程最终能做出什么,他们主张先完成学业,再做这件事。但是我很清楚我自己的智商,不足以多线程处理不同的大领域,所以我顶着压力,努力地做出来他们能看出来的「成绩」,才能换来他们的理解。所以,如果你通过你的理性分析,坚持认为某件事情是对的,就努力的去做,不要放弃了以后看到另一个人做了你曾经想做的东西然后感慨当初应该怎么样怎么样。我尤其感谢我的父母,没有因为自己的不理解而采取强制措施阻止我沉迷计算机,例如拔网线。
第二件事是我初中的时候读的是私立封闭式的学校,两周才能回家一趟。我当时是怎么学编程的呢?每次回到家,我都把要读的教学,存到我的 U 盘里。回到学校,趁老师不在的时候,就偷偷用讲台上读。在家里的时候,玩电脑时间被限制在 2 小时,我会用这两小时,把要读的教学,排版成 txt,然后放在 U 盘,插到电视上(当时的电脑可以播 txt),在电视上读 txt. 我的 Pascal 就是这么学的。
相对于我那时的情况,其实大家能学习的时间和机会都要多得多,如果你真正热爱这一件事,我想,无论什么样的情况,你都能抽得出精力和时间,去使你自己变得更好。
]]>假定你已经有一台可用的,可联网的机器
这篇文章将介绍如何使用自己的机器来搭建用于 Gitlab CI 的 runner. 在搭建自己的 CI Runner 之前,需要先明确一些概念:
CI 的全称是 Continuous Integration (持续集成),是 extreme programming (极限编程) 的一部分。我们常用 CI 来做一些自动化工作,这种自动化工作会运行在一台集中的机器上,比如程序的打包,单元测试,部署等。这种构建方式避免了了打包环境差异引动的错误,并且通过 Gitlab 的 hook, 在代码提交的各个环节自动地完成一系列的构建工作。
和第三方的 Travis CI, CircleCI 不同,Gitlab 本身并不提供机器,只提供一个注册机器的接口。这些机器用于运行构建逻辑,在 Gitlab 中被称为 Runner.
在这里直接使用 Gitlab Runner 的官方 docker image:
curl -sSL https://get.daocloud.io/docker | sh
因为众所周知的原因,国内 pull docker 镜像非常不稳定,所以在这里用 Daocloud 提供的镜像:
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://718dbf2d.m.daocloud.io
sudo service docker restart
拉取镜像:
sudo docker pull gitlab/gitlab-runner:latest
sudo docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
虽然 Gitlab 支持多种 runner 运行方式,但本文建议使用 docker,因为使用 docker 较为灵活,一台机器可以创建多个 docker images 分别为不同的项目进行 CI, 但仍能保持环境隔离。
配置 Docker image 最简单的方式是写 Dockerfile
, 比如可以用 Node.js 官方的 Docker image:
# Dockerfile
FROM node:7.9.0
由于每个业务总会有各自的环境要求,比如应用依赖底层的库。这时可以通过 Dockerfile
配置:
# Dockerfile
FROM node:7.9.0
RUN apt-get update && apt-get install -y \
package-foo
package-bar
写好 Dockerfile
后,需要把它构建成 Image:
ls
# Dockerfile
docker build -t IMAGE_NAME .
Build 完后,通过 sudo docker image ls
查看 image 状态。
接下来就可以注册 Runner:
sudo docker exec -it gitlab-runner gitlab-ci-multi-runner register
程序会要求你填写相关的信息,这些信息可以从 Gitlab 项目的 Settings -> Runners
页面中找到:
Please enter the gitlab-ci coordinator URL:
# http://gitlab.alibaba-inc.com/ci
Please enter the gitlab-ci token for this runner:
# 项目的 token
Please enter the gitlab-ci description for this runner:
# Runner 的 description
Please enter the gitlab-ci tags for this runner (comma separated):
# Runner 的 tag
Whether to run untagged builds [true/false]:
# true
Please enter the executor: docker, parallels, shell, kubernetes, docker-ssh, ssh, virtualbox, docker+machine, docker-ssh+machine:
# docker
Please enter the default Docker image (e.g. ruby:2.1):
# 填入构建 Docker image 时填写的 image 名称
这时 runner 就会出现在 runners
页面:
ERROR: Job failed: API error (404): repository xxx not found: does not exist or no pull access
这是由于 Gitlab 会默认从远程拉取 image,而我们的 image 是在本地构建的,所以需要对 gitlab-runner 进行配置,把 pull_policy
设置为 if-not-present
或 never
.
# 进入 gitlab-runner 的 bash 环境
sudo docker exec -it gitlab-runner bash
# 编辑 config.toml
nano /etc/gitlab-runner/config.toml
编辑 config.toml
中对应的 runner:
[[runners]]
name = ""
url = ""
token = ""
executor = "docker"
[runners.docker]
tls_verify = false
image = "nb-node"
privileged = false
disable_cache = false
volumes = ["/cache"]
+ pull_policy = "if-not-present"
[runners.cache]
「大神」这类的称呼一开口,会把两个人的距离感拉得特别长,就像闰土叫了迅哥儿一声「老爷」。我没有觉得自己很厉害,反而是越学越自卑了。我总结了这些视我为「大神」的网友,不过是经验比我少一些, 或者思考的东西比我少一些而已。
于是我回想自己刚接触编程的时候,也很喜欢逛 BBS, 看一些版块上有些许知名度的网友,发自己的新作品,心里很是羡慕,觉得他们技术很强,是无法触及的。我只能静静地看着,不断地思考他们做出来的功能是怎么实现的,然后翻书找。实在找不到,也会回回贴,发发站内信,请教一下实现的思路,不过基本是没有人会回复我的,因为那时年轻,提问的方式很蠢。渐渐地,我的经验也因为自己做的 side project 日积月累了出来,慢慢地也成了一些人口中的「大神」。
如果说这其中的成长有什么秘诀,我认为是自信:遇到自己不懂的问题时,永远相信自己可以通过不断的学习和查阅资料得到解答,并使解决问题的经验成为自己的一部份。
妄自菲薄会让人变懒,认为自己永远达不到别人的高度而放弃努力。很多人因为不自信,开始依赖别人,把自己活成配角,又苦恼自己不成长。我通常会建议他读一读《提问的智慧》,学会自己先思考问题。我从前也很害怕问比我富有经验的开发者问题,因为我害怕自己的提问很蠢。因此每次提问前,会先梳理好问题梗概,我做了什么去解决这个问题,最后才小心翼翼地把问题发出去。其实往往在梳理问题的过程中,自己就把问题想通了。
所以做技术要自信,不要做伸手党,要多思考。开源不是所谓的「大神」才能参与的,你也可以参与,从读代码开始。每一个你眼中的「大神」都不是生下来就会编程,他们只是善于自己解决问题,并坚信自己能解决问题,最后成功解决问题。
]]>我想,我要花很长时间来练习我的键盘指法,但是作为一个程序员,我对电脑键盘的键位了如指掌。于是我开始考虑把钢琴键盘移植到电脑键盘上。
如果你 Google 一下 online piano 的关键词,你会搜出很多在 PC 上模拟钢琴键盘的 demo. 但是这些 demo 的出发点都是把钢琴键盘上每一个黑键白键都序列化地映射在电脑键盘上。这种程序演奏一下单音,弹首小星星还行,但是真的要在上面弹一首流行伴奏,几乎不可能。
首先和弦是由几个不同音高的音组合成的,如果单纯把音序列化地映射到每个按键上,即使是最简单的三和弦,你都没有办法准确地在电脑键盘上同时按下 1 3 5 来弹出一个 C chord. 借鉴五笔输入法的设计,我设计出了通过按键映射和弦(chord),以及对应的分解和弦(broken chord)的键盘布局方案,来降低演奏时对指法的要求,简化流行钢琴伴奏。
利用这个周末,我已经把这个想法初步实现了出来,在这个初步版本里,已经实现了例如右手柱式和弦的几种转位、自动弹奏左手降两个八度的根音、分解和弦在键盘上的映射等等这些基本能力,通过这些能力已经可以玩一些简单的伴奏 pattern,比如常说的 151, 1531 等等。
在浏览器上面,我写了一个钢琴键盘排列,来展示当前所按到的音。
为了证明这个东西真的能弹出伴奏,我特意录了一个用它来弹唱的视频,唱的是薛之谦的《绅士》:
流行钢琴伴奏和古典钢琴不同,流行钢琴伴奏的重点在于节拍的掌握以及对和弦进行丰富。事实上许多流行歌曲都围绕一个相同的和弦进程(chord progression)来创作旋律,所以**一首伴奏好不好听,很大程度上取决于怎样用不同的 pattern 来丰富你的和弦进程而已。**除了一首歌的 Intro 外,基本可以用和弦去完成一首歌曲的伴奏。在这个前提下,用电脑键盘来弹奏流行钢琴也不是不可能的事。因为我可以通过按键映射和弦以及分解和弦。
下图表示了初期版本的键位映射关系:
C
, 按 A 则会弹奏 Am
.从技术上来说,我把 Piano Keyboard 转化成了程序里的数据结构,通过计算得出相应和弦的转位。
电脑键盘比不上真钢琴或者 MIDI 键盘在于延音和力度都没有办法控制。脚踏板是钢琴的灵魂,因此用电脑键盘演奏有时会觉得一些位置非常单调、生硬,不够丰富,但又无能为力。
代码开源在 https://github.com/djyde/Pciano
用电脑访问 https://djyde.github.io/Pciano/ 即可
]]>If using React makes u confused, it's time to think about what React actually is. It's a great DOM diff algorithm with component system.
— Randy (@randyloop) December 30, 2016此前我在 Twitter 上这样表达过对 React 的理解,但是 Twitter 篇幅有限,所以在这篇文章里,我要做更详尽的阐述。
我从前不喜欢 React, 是因为写 React 的 render function 不像写 template 一样方便,尤其是存在复杂的判断渲染的时候,Vue 的 template 一个 v-if 就搞定了。而在 React 里写,需要把这个判断写成 function, 然后条件判断 return 哪一个 view. 这是我最初对 React 的偏见所在之一。
然而经过自己的实践和思考,加上阅读一些文章,我发现以前的想法是错的。我在使用 React 的时候,没有做到 Thinking in React. 从而对 React 产生了不解和困惑。
有很多人把 React 当成框架来用,这是用不好 React 的根本原因。很少人认真思考 A JavaScript library for building User Interface 背后的含义,把 React 用得一团糟。
何谓 For Building User Interface? 意思就是,这个库仅仅是用于构建 UI 的,这是 React 本质要解决的问题。我甚至和很多人说,事实上 React 本身是不是 React 已经不重要了,重要的是我们写 UI 的思维。React 这个 library 本身仅仅是用来实现这个思维的手段。React 提供的,是优秀的 DOM diff 算法,和一套 Component system。换成代码来说,也就是:
(state) => View
这是 React building UI 的核心思想,所有的组件,就是接受 state, 返回一个 View. 这样看上去比较抽象,比如我们有一个 Clock 组件:
const Clock = (time) => `
<div id='clock'>
<span>It's now: </span>
<span>${time}</span>
</div>
`
Clock 是一个 function, 接受一个 time 参数,返回的是一串 HTML String. 在程序里,我们可以给一个 Interval, 每秒传一个当前的 time, 得到一个新的 HTML String, 然后 apply 到某个 DOM 上。
const $app = document.getElementById('app')
setInterval(() => {
$app.innerHTMl = Clock(Date.now())
}, 1000)
这样的实现是能达到目的的,但是问题在于,每次 innerHTML
时,整个 #app
的 DOM 树会被重新渲染。
我们都知道,DOM 更新的花费是昂贵的。整个 DOM 树,实际上只是一个 span
在不断变化,所以我们需要 DOM diff 算法来得知到底哪一个 DOM 节点才需要被更新,从而节省开销:
const Clock = ({time}) => (
<div id='clock'>
<span>It's now: </span>
<span>{time}</span>
</div>
)
const $app = document.getElementById('app')
setInterval(() => {
ReactDOM.render(<Clock time={Date.now()} />, $app)
}, 1000)
在 React 里,把 props 传入,返回一个类似 HTML 的结构,然后 render 到指定的 DOM 节点上。这里 React 会算出哪个节点应该被更新:
我们这样手动去 setInterval 然后 render 未免有点傻,我们可以更改 state (也就是通常用到的 setState
) 自动地让 React 随着 state 的改变而重新 render. 这里的 time 就是一个 state. 这叫做 Reactive.
Functional Programming 里有 Pure Function 的概念。Pure Function 之所以 Pure, 是因为不存在 side effect. 举个例子,我们写一个求和 function:
function add (a, b) {
return a + b
}
这个求和函数就是一个 pure function. 因为函数内部没有对 input 做任何改变,并且返回一个新的值。我传 1 和 1,得到的永远是 2.
Pure Function 的好处是利于维护和测试。要测试一个 Pure Function, 仅仅是传不同的值,预言对应的返回值。
现在回头看 React 的 Component, 也可以算是一个 Pure Function——接收不同的 props, 然后 render 对应的 View. 上面 Clock 的例子,props 和返回的 View 是映射关系。
光是 state => View
还不够,在构建 UI 的时候,我们希望 state 改变的时候,立即 rerender 整个 View, 也就是我们经常用到的 setState()
.
这样就很容易理解为什么我说 React 仅仅是实现构建 UI 思想的手段,因为构建 UI 的思想总结起来就是:
setState
)无论是 React 还是 Vue, 大抵都是这样的思想。Vue 1 还不完全是,Vue 2 就更接近了,只是后者写法既可以写得像 template, 又可以写直接写 vdom.
而开发者常常感到困难的地方实际上是上面的第 4 点——管理 state. 写 React 写得痛苦,大部分原因是用把 library 当成 framework 去用,把处理 state 的逻辑瞎写到 View 层中去,也就是所谓的 Dump Component.
改变 state 是 side effect, 我们应该把它从 View 层中分离出去。我多次提到,View 层真正要做的,仅仅是根据 state 返回对应的 View. state 的变化逻辑,应该在给 state manager 库去做,例如 Redux, Mobx. 下面我用 Mobx 作为例子:
https://jsbin.com/fumerup/edit?js,output
如果没有接触过 Mobx 不用慌张,只需要知道,Mobx 的 Observable 变化时,被 observer 包装的 React 组件会重新渲染。使用 state manager, 明显地分离了 View 和 side-effect:
测试这样的程序,首先为 side effect 的逻辑做测试,再为 View 做测试。View 的测试在这里就十分简单了,给他传一个 store 实例,借助 enzyme 之类的 testing utilities 预言不同的 action 得到的返回 View.
React 是 Reactive Programming 在 Web User Interface 上实现的手段,它只不过是一个库,提供了reactive render, component system 和降低开销的 DOM diff 算法. 把 React 换掉,只要不是手动操作 DOM, 其它的框架也不过大同小异。重要的是理解它背后的思想。说到底,前端开发在解决什么问题,用什么样的方式解决问题,在使用任何框架和库之前先把这两个问题思考明白,就不会认为前端难学了。
]]>开篇讲讲这个博客这一年的「成绩」:
因为今年年中进行过博客迁移,从 github pages 迁到了阿里云,还换了整个 blog platform. 所以新旧博客的统计要结合起来看。
旧博客统计:
新博客统计:
总计: PV 49,406,UV 15,800
iPhone 用户仍然排第一,第二是小米。
上半年一个 Vue 和 Webpack 系列访问量比较大。下半年给自己的博客定位不是单纯的技术博客,访问量比较大的是关于退学的两篇文章。
坚持做独立博客很难,坚持写博客更难。我没有开公众号,因为开公众号会让我感到有压力,更会让我「为了推送而写文章」。也是因为赞同陈皓的观点。
今年因为工作原因,把大部分的精力花在了 React 上,Vue 反倒没什么机会写了。写 React 的过程中对 Thinking in React 有了不同的看法。我从前不喜欢 JSX, 不喜欢 setState
, 但是慢慢地开始思考 Functional Programming, Reactive Programming. 后来发现,React 是不是 React 已经不重要了,因为 React 只是实现它思想的手段,更重要的是在其背后的,UI Development 的观念 —— (state) => View
。无论如何争论 Functional Programming,它的的确确改变了我对程序开发的想法,我开始追求 Pure Function,开始讲究函数的 side-effect. 我想下一年我还会对 FRP 做更深入的研究。关于这方面的感悟,我会单独写成文章。
比较快乐的是在一些项目里使用了 TypeScript. 自己也写了文章,录了视频, 来表达我对这个语言的看法。
这一年参加了两个 Talk, 一个是珠三角技术沙龙,讲的是 Vue 和 Native. 另一个是 Node 地下铁,讲了 TypeScript. 关于技术分享,我在知乎有一篇回答 讲了我对国内技术分享会议的看法。希望我自己在接下来的一年能有更大的长进,然后用自己的行动去告诉大家技术分享应该怎么做。
比较遗憾的是工作之后减少了写开源项目的时间,今年对开源社区最大的贡献就只有给一本TypeScript 书 贡献了些内容。
从 3 月份入职,完成了一个项目的重构,帮助了一些还有些迷茫的朋友,用自己的热情感染了团队的技术氛围,是在这一年工作上让我自己感到满足的事情。
Just for Fun: The Story of an Accidental Revolutionary
Linux 创始人 Linus 的自传,记录了 Linus 的少年时期和 Linux 的诞生,之中还夹杂一些对开放源代码的观念。读完以后很受鼓舞,能像 Linus 一样是我做软件开发的终极目标。
Soft Skills: The software developer's life manual
中文名叫《软技能:代码之外的生存指南》。我很不喜欢这类教别人做人的书,但是受人推荐,还是读完了。
书里有几个章节我印象比较深刻。比如谈到大公司、中等规模公司、创业公司之前的区别:
在大公司工作令人沮丧,因为他们感到他们个人的贡献无足轻重
为大公司工作的一个显而易见的事情就是成长机会
结合自身条件和自己喜欢的工作环境进行职业选择,是需要深思熟虑的事情。
书中还谈了个人品牌的打造,学历问题等等。都值得一读。
中文名是《UNIX 编程艺术》。万幸我用 macOS 也算是 UNIX 环境的重度用户,所以在读这本书的时候不会感到吃力。这本书实际上和 UNIX 源代码没有什么关系,讲的是 UNIX 下的程序(比如 grep),这些程序的设计哲学让我对软件有了新的思考。
编写复杂软件又不至于把自己搞混乱的方法是降低软件整体的复杂度。软件本身的复杂度不会因为实现方式和代码组织的优秀而降低,但是这能使整体复杂度降低。降低整体复杂度的方法是用清晰的接口把复杂的软件分解成若干个简单的模块。
每把剃刀都自有其哲学,更何况是软件开发呢。即使是开发一个小函数,它的输入和输出也是需要讲究的。
我没有读《乔布斯传》,而是选了这本《成为乔布斯》,是听说这本传记记录的乔布斯要更真实。事实上通过很多途径都已经宏观上对乔布斯有了很大程度上的了解,所以读传记的时候已经没有对某些事件产生触动。反而触动我的是一些小细节上,比如书中提到乔布斯父亲的话:
对于一个橱柜来说,别人看不到的底面与表面的抛光一样重要;对于一辆雪佛兰汽车来说,别人看不到的刹车片和汽车的油漆一样重要。
我求学时期读了很多民国作家的书,唯独胡适先生的书读得不多。今年读了这本文集,意犹未尽,还想读他的《中国的哲学》,但看来要等下一年了。
硅谷「钢铁侠」Elon Musk 的传记。十分羡慕财富自由又有想法的人。
推荐这本有趣的《人类简史》,看人类是如何从原始人进化过来的。
今年买了 PS4, 人生第一台游戏机。玩了几款大作,《最后的生还者》、《神秘海域》、《GTA5》、《看门狗》。印象较深的是《最后的生还者》,没有 PS4 的也建议视频通关。
下半年换了 Macbook Pro 也入了 Steam 的坑,Steam 的游戏和 PS4 比简直就是白菜价。沉迷了一段时间 Don't Starve. 通关了 Firewatch.
这一年的成长比较横向,不仅仅在技术,还学习了音乐,健身等等。遗憾是改不掉一直以来喜欢「急」的缺点,急于完成事情,不多加考虑。甚至容易使自己在快要完成一件事情的时候,容易由于太急,最后烂尾,比如
]]>今年断断续续从 3 月到 11 月我都在牙诊接受牙齿修复,直到今天,修复已经完全做好了。在这个过程中我对牙科有了全新的认识,于是我希望通过分享我这一段经历,让更多人重视牙齿健康。
如果不想看长文,可以直接跳到本文末尾的 TLDR.
我的牙齿属于牙釉质发育不全,虽然从刚换牙的时候我就发现我的牙和别人不一样,但还是在今年 3 月初才准确知道的。那时我刚好智齿在发炎,去医院打算拔掉,一个医生看了我的牙,说你的牙齿烂得很厉害,釉质基本没了,要做牙冠保护,不然再过几年牙就没了。
听了以后我心情复杂,我说我还这么年轻啊。医生说,你的牙用 1 年等于正常人用 5 年的啊。
做牙冠的费用不少,我就回家跟家人说了一下情况,然后又去附近一家牙诊所咨询意见。牙诊所的牙医一看,问我是哪里人,因为他不确定这是釉质发育不全还是氟斑牙,而广东这边的水质一般不会出现氟斑牙的情况。我说我是广东中山的,于是他就基本确定是釉质发育不全了。
他再仔细看我牙齿的情况,最后说了一句:你还是去省口腔医院吧,你这些牙的磨损程度太高,咬合又低,我们这里的设备没有办法做到很高,你给钱我们也不做。
我开始感到情况似乎越来越悲观,后来我姐姐给我推荐了广州某口腔诊所的熊医生,于是我到了广州找他。
熊医生看了我的牙以后,说磨损的确挺严重,但是牙冠还是能做,只不过要做很多颗而已。他和另外几位医生会给我做一个方案。
到底什么是釉质发育不全,首先要清楚一只牙齿的结构:
这是我随手画的一张牙齿抽象图。一颗牙的最外层就是所说的釉质(也称「珐琅质」),而黄色这一层就是牙本质。牙釉质是牙齿最坚硬的部分,也是哺乳动物体内最坚硬的组织,用于咬碎食物,以及保护牙本质。他是不可再生的,所以蛀掉的牙一定不会自愈。牙本质是黄色的,而釉质是白色半透明的,因此正常人最自然的牙齿是偏黄色的。而当釉质被磨损越严重时,牙本质几乎被暴露,才会越来越显非正常的黄。
这是我在后来修复过程中做的下排牙模,可以明显看出,发育不全的釉质比较脆弱,两边的后牙磨损严重,甚至快要磨到底了。在咬合的时候,基本只能靠磨损出来的几个尖尖的点来咬食物。
熊医生给我的方案是,8 颗后牙做根管治疗然后做牙冠,顺便在做牙冠的过程中重整咬合。
为什么需要重整咬合呢?因为我的后牙几乎完全没有咬合。
这是修复前和修复后的侧面对比,可以看出修复前的后牙基本没咬合。
于是整个修复流程开始。而根管治疗是整个过程中最漫长和痛苦的步骤。即使在手术过程中会注射麻药,疼痛还是存在。
上面维基百科上的根管治疗流程图。简单来说,就是从牙齿中间钻开,然后人为把牙神经杀死。牙神经被杀死后,牙齿就不会再有感觉。一般来说,根管治疗都是用于治疗牙髓坏死或牙根感染。而我的情况是做牙冠需要磨一部分牙,如果不把牙神经杀死,磨牙磨到牙本质的疼痛是无法忍受的。
杀死牙神经后,还需要等待一个星期,然后填充药物,最后再补牙。
经过几乎两个月的时间,两边的根管治疗完全做好了。由于经济问题,我拖了很长时间才开始做牙冠。
做牙冠首先要把牙齿磨小一圈,留位置套上新牙冠。磨小以后,咬硅胶、做牙模,然后发给牙冠制造厂家做。这个过程大概需要一周,也就是说,我两边分开做,起码有两周时间需要单边咀嚼。
上面分别是修复前、过程中、修复后的状态。
在修复右边时,有一段小插曲。我的右下智齿阻生并且已经萌出,影响到做牙冠的位置,所以要先把智齿拔掉。拔掉智齿的过程用了两分钟。
在做牙齿修复的这段时间,我向牙医学习了很多关于牙齿的常识。虽然我的牙齿问题并非由于后天影响,但偶尔会看见一些人来做牙齿治疗,有很多都是没有太把问题当一回事,到问题严重才会来看牙医。都以为牙疼忍忍就过去了,然而等到再疼下去,等到把牙髓坏死的时候才来找医生,也就只能做根管治疗了。
也有很多人,蛀了牙也不补,最后疼了,有的人也不管,眼看整个牙都要蛀掉了,来看牙医,也就只能种牙了。
大多数人都不知道,补牙能够解决问题是有多幸福,然而大多数人都只会拖着。没有人想要花钱买难受,做根管治疗又贵又疼,真是心疼肉也疼。有好几次我在做根管的过程中都要疼得叫出来,但是又有什么办法呢,这是发育不良,没有办法改变。但是在读这篇文章的各位,你们都要比我幸运得多,你们天生就有一排好牙,希望你们不要浪费。在这里我有一些微小的建议:
我的意见向来是都是劝回学校好好读书,好好拿个学位证,反对一切没有铺好后路的退学行为。直到越来越多这样的人来找我问这样的意见,我意识到我应该认真的写一写我对退学的看法。
选择退学代表放弃学位(也就是所谓的文凭),这是一个沉重的代价,因为学历很重要。
学历像是人的脸。对于样貌,大家都说,「长得好看没有用,内在美最重要」。乍听上去是这么一回事,但是如果长得不好看,茫茫人海里,会有多大的概率有人主动了解你的内在呢。不是说内在没有样貌有用,而是让人愿意了解内在的成本太高了,比有样貌的成本高得多,要付出的努力也要多得多。
很多人看不起别人整容,然而读书(Schooling)其实就是一种整容。谁都会有这种感觉:这个人读重点大学,实际上也没比我能力高多少,但是他处处受欢迎,拿到的 offer 也多。你觉得这样不公平,其实这公平得很。别人为了这个学历付出了多少的努力(不管这种努力在你眼里看来有没有「意义」),而你又为了做好不拿文凭的准备做过多少努力呢?
我不知道别的行业对文凭的重视程度,所以我拿我的本行——软件开发来说。如果打算放弃学位而提前从事软件开发行业,请先问自己一个问题:
你用什么来让别人相信你会写代码?
如果你没有学历,你就需要靠经验来证明自己的能力。
然而大多数来邮件求建议的朋友,大多没有什么经验,却又声称「和你的经历相似」、「和你一样」。其实我们完全不一样,我在退学前就已经写了 7 年代码。
不是说一定要有这么长时间的经验才能离开学校,而是 一定要已经拥有解决问题的能力的时候,才有资格考虑退学这件事。我常常拿我朋友 Drakeet 给身边的人做例子,他是大二退学的,退学时虽然写代码才两年,但是他当时的能力就已经足以独当一面了。
曾经有一位朋友的朋友问我关于退学的意见,他说觉得学历不重要。我问他你会做什么,他说「虽然我现在能力很弱,但是我学习能力强,想进一些大公司跟着大牛学习,等技术可以了,就可以弥补学历的缺陷。」到底是哪来的勇气让他认为大公司的大牛会因为「学习能力强」愿意让他跟着学习。
我不知道为什么那些还没有什么能力的人会认为学校不能教会他们什么,我想大多是人云亦云。他们往往把我退学的原因归咎于「大学教育的落后」(当然这确实是其中一个原因),然后拿我的例子来证明他们的观点,却没有认真思考过我为什么认为(我所在的)大学落后,那是因为学校在教的大多数内容我在好几年前已经滚瓜烂熟了。如果一个人连这些学校在教的基础知识都不会,又何来的底气声称「学校不能教会他们什么」。
当你有信心对老师说「你下来吧,让我来教」的时候,你才需要考虑退学这件事。
大学是一个很好的避风港,能有如此长的一段时间可以没有顾虑地学习自己想学的知识在一生中难能可贵。想研究 V8 内核就去研究,想了解机器学习就找机器学习相关的书慢慢读。
我知道很多打算退学的人心里都盘算着退学后在家自学一两年,然后出去找工作。我不看好这种规划,因为风险大,并且在家自学不见得比在学校自学好到哪里去。除非在这一两年里能在开源社区带来一些什么,否则到最后不会是你理想中的结果。
马克吐温说过一句话:"I have never let my schooling interfere with my education." 在上大学之前,我学习编程的时间很少。每周只能回家一次,用电脑的时间又不多。拿学杂费的找零买些书,上课看,晚修看,把草稿纸当成编辑器,周末回家用电脑实践。我想所有人的大学比那时的我自学条件要好得多,又有什么理由放弃这种条件呢。
希望所有打算退学的朋友能深思熟虑,退学不是一件好事也不是一件坏事,它只是一种选择,取决于哪种选择更适合自己的处境。
虽然我从来没有后悔自己做出的选择,但是我总会羡慕那些有机会读好学校、受好教育的朋友。我是个很爱学习的人,我也想在大学学编译原理,线性代数,概率论,但我这个人比较蠢,没有办法同时兼顾我喜欢的和我不喜欢的课程,又疲于应付功课,所以才会选择退出,然后付出比别人更大的努力,跑在别人的前面。
比我聪明的人很多,而这些人,根本用不着退学。
]]>number
类型,但是客户端需要的是字符串,导致一条数据在没有办法在客户端正常显示。 我年轻的时候曾经是一位动态类型的忠实拥趸,因为动态类型方便、自由。假设在我需要编写一段为我服务的小程序时,如果 runtime 强制需要我去做一些向上向下转型的工作,恐怕我会崩溃。
但是,在享受自由的同时,我们却很容易写出这样的程序:
// config.js
export const appId = 123456
// utils.js
import { appId } from './config.js'
export function generateSignString (salt) {
return appId + salt
}
这是一个用于生成签名字符串的函数,从逻辑上看,这段代码没有问题。但是,当另一个对这个函数内部实现不了解的开发人员使用它时,会返回意想不到的结果:
import { generateSignString } from './utils'
const salt = new Date().getTime()
const signStr = generateSignString(salt)
使用者希望把 timestamp
用作 salt
,但是 signStr
并不是意料中的由 timestamp 和 appId 拼接而成的 signString, 而是 appId
和 timestamp
相加的运算结果。而这种错误是在编译时无法感知的。
然而,如果我们用静态类型的 TypeScript 做同样的事情:
const APPID = 123456
function generateSignString (salt: string): string {
return APPID + salt
}
const salt = new Date().getTime()
const signStr = generateSignString(salt)
// Error: [ts] Argument of type 'number' is not assignable to parameter of type 'string'.
在编译时,就会抛出类型错误的提示,这时就会知道,你的 salt
应该用一个 toString()
来转换成字符串。
我认为不是。起码在构建大规模的应用程序时,静态类型会帮助你避免大多数的运行时错误。
而 code base 并不是衡量应用程序规模的唯一指标。只要符合以下某种情况,就认为这个程序是大规模的:
在以上这些情况下,类型声明变得尤为重要。你可能认为你能记住变量是什么类型,但在程序的世界里生存的不仅仅是字面量。
在团队共同维护代码的时候,如果没有类型声明,你绝对有可能传入一个非预期的参数。所以,类型声明是开发者与开发者之间的传达信息的过程——我要的是什么类型,你就只能给我什么类型。
类型声明也是开发者给编辑器传达信息的手段。只有显示的类型声明,编辑器和 IDE 才能判断函数中的形参应该有怎样的行为,才能给开发者正确的代码提示,才能安全地为你进行 Refact.
JavaScript 虽然是一门面向对象的语言,但相对于 C++, Java 这类语言来说,它的抽象能力很弱。在构建大规模的 JavaScript 应用程序时,我常常怀念写 Java (Android) 时可以写 interface
, 可以写 abstract
. 在编写 JavaScript 程序中,很难舒服地运用一些设计模式。
We designed TypeScript to meet the needs of the JavaScript programming teams that build and maintain large JavaScript programs. —— TypeScript Language Specification
TypeScript 引入了 Interface
, Enum
, Generic
, abstract class
等等,这些表达能力正是在构建大规模 JavaScript 程序时所缺失的。不是说没有 TypeScript, ECMAScript 就不能实现这些,而是 TypeScript 在代码层面赋予了这些约定。
比如,用 TypeScript 实现 Singleton:
理论上,被编译出来的 ECMAScript 代码照样可以顺利地 new Person()
, 但 TypeScript 会在编译时提醒你,这个类不应该被实例化。
我不打算在本文对 TypeScript 作出详细的指南,我只希望大家可以认识到,严谨的类型检查和面向对象表达能力对代码的健壮性和可维护性有很大的正面作用。ECMAScript 是一门自由灵活的语言,但绝不代表我们就应该为这种灵活承担过高的出错概率。况且,TypeScript 在严格类型检查的同时,又保留了 ECMAScript 的自由性(你甚至可以用 any
类型来规避类型检查)。
我永远认为使用没有类型验证的 JavaScript 会让我快乐,无论她有多大的坑。我仍然可以用她愉快地写脚本抢这个抢那个,用她来写各种各样的 bot,无须考虑类型转换。但当我用她来写一些不是为了让我快乐的程序的时候,我希望她在运行时不要跑偏,还希望她长得像真正的 OOP 。
静态类型的 JavaScript 就像开始做手机后的罗永浩,你能看出他们的妥协,他们本不是这样。但他们是真正要上场合了,才不得不这样,即使他们根本不是你喜欢的样子。
]]>我是 Randy,一名 95 后程序员,在一家创业公司做前端工程师。热衷于技术和各种科技、数码产品。
11 寸 Macbook Air ( i5 4GB 128GB ) 是我的主力机,是 2014 Early 的版本。目前来看写 Android 写到项目中期的时候会有明显的卡顿。写前端项目对性能要求没有 Android 高,所以目前还没有必须升级的需求。11 寸的确比较小,但是平时工作都是接显示器的,就不在意了,尤其是当你背着它到处抱的时候,就觉得为了便携性,屏幕小也是值得的。
iPod Touch 4
这台 iPod Touch 4 主要用来听 Podcast,我最常听的节目是 『内核恐慌』,『IT 公论』
性价比较高的显示器,色彩满意,外观时尚,是低价位显示器的好选择。
IKBC Poker 2
我用的机械键盘是 Poker 2 红轴。Poker 2 和 Mac 配合使用没有什么问题,只是电脑休眠后要重新插拔。由于是第一个机械键盘,在轴的选择没有经验,选择了相对保守的红轴,使用了半年的感受是,红轴直上直下的感觉并不太适合我个人,我比较喜欢有段落感的轴,所以如果要买新的键盘,我可能就不会选红轴了。
一加手机 1
现在的主力手机从 Nexus 5 换到了一加(Babyskin),唯一不太满意的地方是屏幕太大,单手操作很吃力。
Pebble 是比较小众的智能手表,用的是 E-ink,续航一周,单纯的用来看天气、时间和手机推送,非常棒。不过外观不太符合大众的审美。
我希望在一个宽阔的空间里工作,加上足够大的桌子和显示器,舒适的人体工程学椅。光线要好,窗外的景色能让人感到活力。
阅读。读资讯,读书等等。平时我经常刷 Github, hackernews, V2EX 这些社区。 V2EX 的创意板块是获取灵感的好地方。
我推荐乌克丽丽。因为 ukulele 是一件学习门槛不高的乐器,它的音色很欢快,学习也很容易,学几个和弦指法就能弹唱一首歌。平时累了就弹一首,等待编译的时候也可以舒缓一下工作压力。
本文参与了「利器社群计划」,发现更多创造者和他们的工具:http://liqi.io/community/
]]>写这篇文章是想说说退学一年后的一些想法。想看技术总结请移步。如果不想看这么多的文字,可以直接拉到最下面,看写在最后的内容。
一年可以发生很多事。
回想起来,大一的一整年我旷了很多课,待在宿舍看书和写代码,周末总是往广州市区跑,参加各种各样的社区活动和聚会。学院规定旷课 20 课时以上就要处分,我很不出意外地被处分了。
签处分的时候,辅导员告诉我,如果这个处分不撤销,是拿不到学位证的。如果想要撤销,要保证在大二整个学年不旷课,也不挂科。我说,那我是完全没戏了。
辅导员让我好好考虑一下。我表达出了离开学校的意愿,她说好好想想,暑假和家人商量出一个结果,开学再找她。
其实我已经想了很久。在学校的一年里,我过得极其难受。我必须浪费很多很多的时间,去上一些只是听上去很有意义的课程,和那些在课室里只会玩手机和睡觉的同学处在同一个课室里,连续待一个半小时。 而我最这个人痛恨的事情,就是别人浪费我的时间。
我父母是很传统和保守的人,我不敢在他们面前提关于离开学校的事情。于是在那个暑假,我度过了人生中最抑郁的两个月。
大二开学,辅导员知道我并没有和家人商量,就打给了我家人。本以为上了大学,就不会再有「打电话通知家人」的破事。
家里人和我想象中不一样,他们很冷静。周末回家,我说出了我的意愿,他们表示让我自己做决定。他们说,你是个成年人了,读大学是自己的事情,如果你要读,我们愿意继续花钱供你读,如果不想读,你自己要为一切后果负责。
于是,在一年前的今天,我回到了学校,找辅导员签下了休学一年的协议。辅导员问我会不会后悔,我说不会。她说那你要加油,希望你能成功。我很开心,因为她是笑着说的。我辅导员笑的时候很漂亮。
和学校没有任何关系以后,我租了一个算是能住的房子,正式开始了职业生涯。
我的第一份工作入职的是一家创业公司 (Kiwi Inc.),是暑假的时候就已经入职的。暑假前我在某群表示想在广州找一份能在暑期做的工作,李秉骏 很快就跟我联系。我从学校坐了趟车到他的公司,一番愉快地交流之后,工作就落定了。
我在 Kiwi Inc. 负责的是前端开发。因为整个开发团队只有我一个前端,而公司主要的产品又有很大的比重在前端,所以进去以后,我过得很充实,因为自己 do the things that matter. 团队因为人不多,大家都很融洽。
我后来听很多人对应届生的「忠告」,都说千万不要去创业公司,大多数创业公司都是坑人的。有条件的,都争取去去大厂,不要在创业公司浪费时间。
对此我保持不同的意见。我非常庆幸自己在创业公司待过(当然也是因为我待的公司不坑人)。在创业公司,我们可以自己选择用最好的工具,可以用 Slack, Trello 以及第三方的 PaaS 等等等等。**正因为我们能自由地选择最好的工具,我们可以亲身感受和思考这些产品本身。这些产品优秀的地方会渐渐地成为自己做产品时对产品的品位和追求。**并且在创业公司,我们没有无意义的会议,可以把时间用在干实事本身。关键是,我们每个人在公司里,都能感受到自己是极其重要的一员。
在 Kiwi Inc. 写前端的过程,是我自己建立前端工程概念的过程。从开发到测试再到部署,从代码规范到构建,都希望把软件工程的思想带到前端中去。虽然现在看来习以为常,但是对于当时只做过自己的 side project 的我来说,这是极大的经验和收获。
在 Kiwi Inc. 待了半年后,出于对自己更高的要求,我离开了这个靠谱的创业团队。
崇尚工程师文化的我一直认为自己会和国内的大厂无缘,大厂不会选择我,我也不会选择大厂。然而我却在农历新年后通过了阿里巴巴移动事业群 (也就是 UC) 的面试。在 2016 年的 3 月加入了阿里。
大公司和创业公司不同,一个简单的问题会牵扯到不同的组,比如一个字段出了问题,要去追踪到底是架构组的问题还是算法组的问题。在大公司,每一个需求要有排期,版本上线要提测,要面对大量用户的反馈。
其实离开了学校开始工作,自己的时间就很少了。一本不厚的《设计模式》,原本可以花两三天粗略地读完,现在会有不知道什么时候才能读完的感觉。这种情况下,很容易使自己在选择读一本书前出现「读这本书对我的工作有实际帮助吗」这种错误的考量。
**不够用的时间常常使我处于不安、烦躁和轻度抑郁中,因为我害怕被别人追上。**每个认识我的人无论出于真心还是客套都会说我「年轻有为」,但是没有人知道这是一场看不到终点的长跑,我的特立独行让我必须跑得比别人快得多,才能弥补我们之间的差距(学历、智商等等)。这使我感到害怕,尤其是同龄人渐渐都出来实习工作的时候。但又没有任何办法。
工作以后有了稳定并且还不算少的收入,我能用来买自己喜欢的数码产品,也花了不少钱在自己的身体上 (比如健身和吃)。
我不存钱,我希望在我还年轻的时候,在我不需要供这个供那个的年龄,能没有顾虑地花钱让自己快乐。快乐太难又太容易错过了。就像小时候想有一台 Gameboy 但不得愿,现在即使可以没有压力地买下一台 PS4, 也不见得有多快乐。
我有很多还在读书的朋友,我们出去聚会聊天的时候,我都会看得出他们的迷茫和无奈。这些迷茫和无奈可能很大程度上来源于教育体制的缺陷,但是,在这个时代,学习早已不仅仅只局限于学校,它在任何一个你可以掌控的角落。它在互联网、在每一本有价值的书籍上。如果学校没有满足你,就去这些地方找。珍惜时间,总有一天会找到它,并能让你过上你希望过上的日子。如果迷茫是出于你的懒惰,就请不要埋怨教育,也不要埋怨任何体制、任何人。
我还有很多已经在工作的朋友,尤其是和我一样在写着代码的朋友。我没有特别要说的,我想说,嘿,我来了。
]]>主力浏览器
我在 macOS 上主要的科学上网工具。
窗口管理工具,可以自定义窗口布局,通过快捷键快速定位当前窗口。
macOS 上最好用的 Terminal Emulator. 如果说系统自带的 Terminal 是 IE, 那 iTerm 就是 Google Chrome.
我喜欢把 iTerm 固定在整个屏幕的上半部分,并设置透明度(可通过 command + u
进行 toggle),通过 command + i
(自定义的 hotkey) 快速显示和隐藏。这样无论我当前正在处理任何事情,我都能快速和终端交流。
我最喜爱的「代码编辑器」。
我最喜爱的「代码浏览器」。因为有了 Visual Studio Code, 我用 Sublime 的场景更多是快速浏览个项目的代码。
在 Menubar 中显示更多有用的数据,比如万年历、电池状态、上传下载速度等。
密码管理工具。我的每一个账户密码都由 1Password 随机生成和储存,登录网站时只需要 command + \
自动填充。既安全又节省时间和记忆负担。
macOS 自带 zsh, 但是 zsh 需要做很多配置才能用得顺手,这时就需要 oh-my-zsh 帮你做好这些。一条命令安装好后,再通过 ~/.zshrc 添加自己的配置。
homebrew 是 macOS 上的 apt-get. 安装命令行程序只需要 $ brew install
proxychains 是给命令行使用代理的工具,支持 socks5.
$ brew install proxychains-ng
z 是类似 autojump 的目录跳转工具,它会根据你的历史路径,在你只输入目录名的情况下,自动分析你要进入的目录路径。
$ brew install z
以上这些工具保证了我最基本的生产效率。
]]>这位原名李峻、从 1999 年开始做填词学院的作曲及填词人,虽然没有两位『伟文』家喻户晓,但却写过非常多好旋律、好词。当初(2001 年)受陈辉阳赏识的他初来香港,用了半年,包办词曲,凭借一首《烂泥》,直接拿下当年十大劲歌金曲最佳填词,亦间接助许志安拿下最受欢迎男歌星。词里这句:
别轻视我 纵是这种烂泥 能滋润你 耗尽每分让你艳压一切
颇有李清照『落红不是无情物,化作春泥更护花』的意味。只是李峻一把它变得非常『自虐』,但又比不上林夕的『能得到的安慰是失恋者得救后 很感激忠诚的狗』。
很多人认为《烂泥》是为『苦男人』许志安度身订做的,但是后来李峻一在接受采访时,说写这首歌的时候,并没有想过写给谁唱。而陈辉阳觉得适合许志安,就给许志安唱了。这首歌就这样一炮而红。
《烂泥》的爆红令李峻一的客户名单多了陈奕迅这个名字。同样是他包办词曲,他们合作出了《Lonely Christmas》。可能对于李峻一来讲,无奈的感觉就是这首词里**『人浪中想真心告白 但你只想听听笑话』**的感觉吧。
李峻一说自己比较满意的歌是邓丽欣的《电灯胆》,一首描写女生喜欢上有了已有对象的男性朋友,只能在旁做他们的电灯胆,又想他们总有一天分手后会轮到她上位的这种三位一体的唏嘘处境。而我最爱这句**『善良人埋藏着最坏的心眼 妄想一天你们会散』**,算得上整首歌的升华。
虽然李峻一认为自己在《电灯胆》上把女性的心态情感写得非常到肉。但我却觉得略早于《电灯胆》的关心妍的《放生》才是。也许每一对情侣,都曾经历过由热恋到平淡,再到**『拍拖都变义务』**的阶段,最后女方无奈洒脱地将贪玩爱自由的男方『放生』。毕竟:
互缠着到老 不死都疲劳 还是跟你痛快结束
但是他又隐秘地在词里抛出了一个问题:『自由来换失恋那代价,你真的相信值得吗?』
李峻一是偏爱填词多于作曲的。但偏有一首歌——《幸运水晶》,我认为作曲比填词要好得多。整首词没有特别这点,但旋律却使最初听这首歌时只有不到 10 岁的我一直记在了心里。
其实提到李俊一,又不得不提到郑中基的《无赖》了。如果说《烂泥》天生属于苦男人许志安,那《无赖》天生就属于『贱男』郑中基。这首浪子自道的作品,赢得了听众的青睐,在当年赢得了四台冠军歌。
我相信无论每一个男人如何『无赖』,都总会有一个**『跟你笑着挨』**的女人。
和郑中基合作还有一首歌叫《美女与野兽》,也是一首忏悔歌。讲这个男人虽然贪新,却忘不了那个真正爱他的女人:
完场就远走 骗心的野兽
为何离别你 比死更是难受
当天我背叛你 时刻都会内疚
仅有一次真爱 没法保留
在歌的结尾,只得无奈地说**『喜欢的不配拥有』**。
随后又与邓丽欣再度合作出《分手的礼貌》,描写分手后不舍又要假装豁达的女人。里面一句**『无法笑着原谅仍能沉默退场,别回望痛哭交低最好印象』,让我想起叮当唱《我爱她》里面的那句『若那一刻重來,我不哭,让他知道我可以很好』**。一段感情里,最后最伤的似乎往往都是女方。
他还给张智霖写过《你太善良》,终于是一首写悲情男的歌。男方认为女方的对象不是好人,是个感情骗子,而女方却又偏偏不醒。所以歌中最后才会说**『我也想骗你,这句才算真相』**。
其实我最想推荐的是李峻一和陈辉阳合作的歌,是写给陈冠希的《单恋高校》。这首充满青葱校园气息的歌,非常适合陈冠希。
李峻一写情爱的歌很出色,但对于非情爱的作品,却稍欠火候。可能这也是他和两位『伟文』最不一样的地方吧。
文章最后,我想把这个位置留给一位叫陈僖仪的歌手,她非常优秀,却在 2013 年很遗憾地意外身亡。李峻一曾为她写过一首《忘川》,由陈僖仪自己作曲。希望她在另一个地方,可以知道我们从未忘记过她。
]]>事实上很早以前我就想搬离 Github Pages, 因为国内访问很慢很不稳定,加上不能上 https, 但是一直考虑到我的博客几乎都是写给程序员的,就无所谓了。
然而最近有了一些变化,我喜欢写起非技术文章了。技术文章的沉淀周期很长,更新频率都是以『月』作单位的。而我又喜欢写东西,也喜欢分享生活。所以事实上从 2015 年 9 月的 《我是 Randy, 这是我的利器》 开始,我写的文章都是关于生活方式和 LifeHacker
类的,还有我以前最讨厌写的游记。所以博客的 subtitle 变成了『代码、科技、艺术和生活』,我希望分享这些我所爱的、有价值的东西,这些东西都围绕『创造』。创造对于我而言是一种与生俱来的生理冲动。
从我刚开始能勉强读懂语文课文开始,我就有提笔写点东西的冲动。这种冲动很单纯,因为我连要写什么也不知道,只是看到空白的单行本,我就想要在上面写些什么。但是以当时的能力,实在没有东西可写,我就拿书柜的书来抄写段落。这也是我童年时期写完作业后的乐趣之一。
我初一那段时间沉迷一个叫『易语言』的东西,宣称中文编程,可以很快搭出一个图形界面应用程序(类似 MFC)。我泡了很久的易语言 bbs,加了几个 QQ 群,认识了一帮朋友(这是我最早混的程序员圈子),他们有做外挂,有做杀毒软件的等等。虽然现在看来这个所谓的『语言』只是一个 C++ 的壳,设计上也不尽合理,却让我充分体验到了创造的乐趣。
比如我用易语言写的第一个成品:
简单来说就是用来清缓存的,改编自曾经很火的『删除系统垃圾的批处理』。至于为什么叫网帝... It's a long story. 以后有机会可以专门说说这段故事。
我还用过易语言写杀毒软件:
原理是我在各种病毒交流群拿了很多病毒样本,然后写了一个小程序把所有样本的 md5 提取出来做成病毒库,扫描的时候匹配 md5。
后来我觉得这样做病毒库很蠢,于是想办法做一种根据文件行为判断可疑文件的安全软件。这种系统级监听,易语言很难做到,结果我就入了 C#/.NET 的坑。不过这都是后话了。
这些很蠢的作品带给我创造的乐趣,它们和写作一样,只不过形式变了。很多人觉得自己不知道自己到底想要做什么,我想大概就是因为没有体验过创造的乐趣。我会因为写出了这些看上去不怎么样的软件而激动得睡不着觉,在那一刻我就知道我喜欢的,我想要做的事情就是编程。
因此,要找到自己喜欢的事情,只要找到做什么事情能让你体验到创造的乐趣就是。假使没有,那就读读书。
]]>考虑到移动设备用户,文中图片都经过七牛 50% 质量压缩。非广角照片拍摄设备均为 iPhone SE.
另外,很多图中我都在看手机,是因为运动相机成像要在手机上看。
最近入手了小蚁相机(一代),需要找个地方测试一下相机的体验和成像效果,恰逢即将高考,于是决定回高中一趟,找找以前的老师,看看学校的变化。
我买了从广州南到南朗早上 10 点 38 分的动车票,但是由于时间估计错误,我没有赶上这趟车,只好改签成 12 点多。这很遗憾,因为我原本的计划是拍学生冲饭堂,然后自己去吃粗菜馆的。
坐上动车以后天色就开始变,我开始感觉这次的行程很亏。在我的想象中,我应该在一个风和日丽的白天架着三脚架拍拍阳光下的凤凰花。
到达南朗轻轨站后就开始下雨,本来想刚好试试中山刚开通的 Uber, 奈何附近没有 Uber 车。眼见雨越下越大,只好上了一辆私家黑车,大概是 Uber XL 的级别(这在南朗是司空见惯的)。
路上开始慢慢从大雨变成了暴雨。我问司机,南朗打 Uber 的人多不多?司机说南朗还没有 Uber,中山暂时只开通了石岐、小榄和古镇。我问那打滴滴的多不多?他说偶尔,但是在这个地方一般都是短途,平台分成又多,做了反而赚得不多,所以也很少做。
到了校门口依然暴雨,只好躲到『翠影』。『翠影』一家人没有变,老板娘在包云吞。我问她现在还有东西吃吗?她说现在没有啊。我看了看表,下午 1 点多,时间的确是很尴尬。于是我买了一瓶果粒橙,又去了隔壁的『成伯伯拉布粉』。
雨小了就进学校,撑着伞把每周一演的舞台拍了。
没过多久雨就停了,但天还是灰的。我决定到兰溪湖拍水鸭,但是当我走到亭里的时候,水鸭都逃走了。
我觉得鸭子这样很不礼貌,所以很快就离开了兰溪湖,走到了纪中里我最喜欢拍照的地方——图书馆最顶楼。
之所以喜欢这里是因为这里几乎是整个学校的最高处(除了后山),这个角度看兰溪湖和天空非常漂亮。
我特意在这里用小蚁拍了一段延时摄影:
接下来去教学楼找语文老师。这个时间点刚好是午休完开始上课,不出意料,有很多学生迟到被抓了。抓人的还是我以前的年级主任。她看到我,说,卢涛南,怎么选这么好的日子过来啊。
见到语文老师后闲聊了几句,中途又见到了物理老师,但是显然她已经把我忘了(很正常,只教了我们一年)。我说到我没有上大学了,现在在阿里巴巴。物理老师问,是自己开网店吗?
闲聊了十多分钟,散了以后,逛了高三楼,面基了 Lucy。高三到这个时候,基本就是放羊状态了,很快他们都要『解放』了。我想,这种所谓的解放,虽然只是假象,但起码能给你一个瞬间的快感。有人告诉你,熬到什么地方,你就可以疯狂了。然而出来工作以后,没有人告诉你,到哪个点,你就解放了,你就自由了。没有,并没有尽头,连转折点都看不到。
傍晚天气很好,还有晚霞:
南朗的变化很大,我高一那年刚进来的时候,南朗的第一家 KFC 还在装修。现在有 KTV、有正规的电影院、有网咖。
到轻轨站的时候是日落最后的阶段,这时晚霞更加鲜艳。
旅行就这样结束。我一个朋友问我,你觉得你有多爱你的高中?我觉得我并没有『爱』这个高中,我相信任何人留恋一个地方,都是因为在那里发生过的事、遇到过的人而已。而我的这部分记忆又恰好是在中山纪念中学留下的罢了。
]]>于是这个理想再也没有跟任何人提起。但是我很俗套地感谢那位老师,因为从那以后我决定改变我的理想,成为一名科学家。
之后我读过很多科普书籍,奇怪的是,这种给青少年看的科普书,大部分的内容都是有关化学的。以至于我有很长一段时间都在意淫我成为化学家的情景。
六年级转学后,我遇到一位朋友,他给我展示了他自己写的一个博客网站。当时我很吃惊,于是和他一起自学编程。我没有零花钱,只能用学杂费的一些找零买了一本 20 块钱的 中专教材《c语言程序设计》。
和老罗一样,从那时起我就有种被上帝选中的感觉,我觉得我这辈子就是搞这个的。我从意淫自己成为化学家,变成了意淫自己天天写代码。
在这个过程中,我遭受了很多不被人理解和看好的压力,甚至被嘲笑。念高中的时候,由于在一篇周记里表达了自己的理想主义,晚修就被班主任拖出去批判了一番。
直到这一年,经历了足足 8 年,我离开了学校,真正成为了一名软件工程师,曾经意淫的场景变成了现实,和『科学家』这个理想也算占了点边。理想主义万岁。
21 岁,我希望我可以继续写代码,离自己的终极目标更加接近。而那些曾经看不起我和嘲笑过我的人,我只想说,你们这帮孙子,我去你妈的。
]]>自从入手了 iPad mini 之后,我剁手入了很多游戏。有些游戏太贵,希望等降价或者限免才入。于是我开始找一些提供降价限免资讯的网站。
找到比较靠谱的是 少数派 和 Appshopper ,区别是前者是编辑筛选,后者是程序监听价格变动。Appshopper 还能把 APP 加入 wishlist,当价格出现变动的时候会发邮件到你的邮箱。
Appshopper 在功能上无可挑剔,但是十分蛋疼的是搜索引擎不认中文:
于是,我有了自己写一个类似的程序的想法。
整个程序的思路是:
我把这个程序起名叫 Catchem, 是 Catch them 的意思。因为目标是 selft-host, 不必太在意数据库的性能,这种情况用 serverless 的 database 是最方便的,serverless 首选 SQLite. 逻辑用 JavaScript 写,Express 暴露一些 API (比如添加应用) 给前端调用。前端选 Vue.js。
首先我们应该怎么通过 APP 的 URL 获取 APP 的信息,比如 Shadowmatic 的 URL 是 https://itunes.apple.com/cn/app/shadowmatic/id775888026?mt=8 。在我观察页面 DOM 结构的时候,我发现这种 APP 页面是有 semantic schema 的。并且提供的内容非常多,包括应用的名字、截屏、评分、价格等等。
于是二话不说写了个 解析 APP 页面信息的方法,这个方法解析后的整合到的信息是:
{
url: 'https://itunes.apple.com/cn/app/shadowmatic/id775888026?mt=8',
name: [ 'Shadowmatic', 'TRIADA Studio LLC' ],
screenshot:
[ 'http://a3.mzstatic.com/us/r30/Purple69/v4/81/bc/33/81bc33fd-5597-8b5e-fed7-bf99927e29f9/screen640x640.jpeg',
'http://a4.mzstatic.com/us/r30/Purple69/v4/d7/8a/2a/d78a2a8f-7367-e8b5-fb03-22de6523d996/screen640x640.jpeg',
'http://a3.mzstatic.com/us/r30/Purple49/v4/62/45/0e/62450e8d-193b-6942-7750-a64e86b5c102/screen640x640.jpeg',
'http://a3.mzstatic.com/us/r30/Purple69/v4/d9/a5/ce/d9a5ce19-e751-083e-d7d5-17fc552b0b10/screen640x640.jpeg',
'http://a1.mzstatic.com/us/r30/Purple69/v4/89/67/32/896732ba-19d0-d8f1-ed8a-aedd4ca02e61/screen640x640.jpeg',
'http://a5.mzstatic.com/us/r30/Purple49/v4/e7/3f/25/e73f252d-b0c0-bb00-8dc9-4b67d5b6ff67/screen480x480.jpeg',
'http://a4.mzstatic.com/us/r30/Purple49/v4/b7/49/97/b74997a5-127b-e1e3-76ab-c8681217f244/screen480x480.jpeg',
'http://a1.mzstatic.com/us/r30/Purple69/v4/42/6e/e7/426ee72d-7b55-b5ad-cba6-b0b1b79f67f2/screen480x480.jpeg',
'http://a1.mzstatic.com/us/r30/Purple69/v4/e7/ac/2f/e7ac2f8c-067a-6b28-0cf4-c3621470d6e8/screen480x480.jpeg',
'http://a1.mzstatic.com/us/r30/Purple49/v4/6e/e1/9f/6ee19f6d-457a-a0ef-f9f5-e3b465b85877/screen480x480.jpeg' ],
textDescription: '** 2015年Apple Design Award得主 **** App Store. iPhone 年度最佳游戏 2015 **** App Store. iPad 年度创新游戏 2015 **Shadowmatic是一款能够激发想象力的谜题游戏,游戏过程中,你将在聚光灯下旋转抽象物体,在墙上找出可辨认的投影。这款游戏融合了精彩的视觉效果和既轻松又令人爱不释手的游戏玩法。在探索的旅程中,您将惊喜地发现很多超乎想象、变幻无穷的投影。游戏有12个房间,每个房间都有独特的概念设计、环境氛围以及音乐效果。提示。游戏中包含一系列的提示。为能够充分享受游戏的乐趣,我们建议您仅在个别情况下进行求助。音乐。每个房间都有自己独特的音乐编排,形成了独特的氛围和与众不同的感受。佩戴耳机可获得最佳音乐效果,同时,这些音乐还可在iTunes单独购买。-- 12个独特环境中特设的100多个关卡-- 炫酷的画面-- 次级目标-- 非线性关卡进度-- 3D视差效果-- 街机模式** Shadowmatic要求设备为iPhone 3GS及更高版本。-----------------------------------------------------Triada Studio是一个拥有20多年行业经验的计算机图形及动画工作室。Shadowmatic是该公司的首个项目,该项目结合了其丰富的计算机图形经验以及实验性的内部3D引擎。',
image: 'http://a5.mzstatic.com/us/r30/Purple49/v4/63/2f/f1/632ff1ab-4019-48d1-fdbd-b3b9e1e50e43/icon175x175.png',
offers: '¥18.00',
price: '¥18.00',
applicationCategory: '游戏',
datePublished: '2016年02月03日',
softwareVersion: '1.9',
author: 'TRIADA Studio LLC',
operatingSystem: '需要 iOS 6.0 或更高版本。与 iPhone、iPad、iPod touch 兼容。',
aggregateRating: '4.88354 395 份评分\n',
ratingValue: '4.88354',
reviewCount: '395 份评分'
}
// https://github.com/djyde/Catchem/blob/master/models%2Fapp.js#L141-L147
function cronJob(){
check().then(() => console.log('Checking finsihed'))
}
cronJob()
let job = schedule.scheduleJob('0 */2 * * *', () => cronJob())
监听任务本质上就是一个 cron job, 定时执行获取所有 APPS 的最新信息并对照原有价格。
对比得出变动后,我希望远不止发送 email。所以我加入了 Integration, 使 Catchem 可以对价格变动作出更灵活的反应。
目前只有 WebHook 这个 Integration, 当检测出价格变动,Catchem 会自动触发这个 hook, 把相关数据 POST 给所提供的 URL, 这样一来,价格变动后的动作取决于 hook 而不是 Catchem 本身。由此你可以写各种 hook,比如用来发送 Slack 信息的 hook,当你想要的 APP 降价后会给你的 Slack 发送信息。
由于我经常用微信,如果 Catchem 可以及时把降价信息推送到我微信的话那是极好的。于是我写了一个给自己发送微信消息的 hook,这个 hook 使用测试订阅号的 token。
hook 相关的代码段:https://github.com/djyde/lean-hook/blob/master/routes%2Fwechat.js#L31-L54
我伪造了一个价格来测试:
于是乎,现在只要我心仪的 APP 降价或限免,我的微信就能收到降价信息。
整个项目开源在 https://github.com/djyde/Catchem ,大家可以拿来自己用,前提是遵循 MIT License。
会编程非常酷,对吧。
]]>我第一次接触五笔是五年级的电脑课,那时鼠标还是有滚球的。当时电脑老师知道我很喜欢电脑,就告诉我学校准备搞一个电脑打字比赛,希望我学五笔去参赛。我那时只会打全拼,压根没有想过学五笔,但是老师告诉我,如果参加,午睡的时间可以到机房练习。听到这个『福利』之后我立马答应参赛。
之后的一个星期的午睡时间我都去机房练打字,不过我的电脑老师并没有手把手教学,而是帮我装了个叫 "wt" 的软件,是个 DOS 程序,专门用来练五笔的。(我一直没搞懂它为什么叫做 wt,最近又搜了一下,这货中文名叫『明伦五笔』)电脑老师就让我按照这个软件的进度去练。然后又给我一张字根口诀表,什么『王旁青头戈五一』,让我背一下。
刚开始我练的是很认真的,看着一排字,对照着键码提示敲。老师也没管我,自己在讲台玩电脑。于是几天以后我也就没怎么练五笔了,开始玩金山打字通,玩打字游戏,打英文。
这就是我第一次接触五笔,没有背字根,只勉强记住了一些一级简码,能打出来的词语只有『中国人』(k l w)。这段时间最大的收获其实是学会了盲打,靠金山打字通掌握了正确的打字指法。
第二次接触是我姐买了一台步步高电子词典,这部词典相当牛逼,可以用数据线连到电脑,然后安装很多游戏,我经常上厕所的时候拿去玩。后来我姐就生气了,一怒之下把游戏全删了。当时我很失落,但是我还是得上厕所,还是得拿电子词典去玩。我把所有功能都按了一遍,发现没什么好玩的,唯一算得上具有人机交互功能的程序就只有一个练五笔程序。
我就开始玩这个,但是我几乎什么字都不会,我选了四级全码的练习。四级全码的意思是同一个字母打四次就会出现的字。程序是这样的:右上角出现一个字,我得输入四个字母然后按确认看对不对。我一开始什么都不会,只会一级简码。所以我就开始一直蒙,蒙到最后我就把所有全码几乎都记住了。我现在还记得我记住的第一个全码是『火』,四个 o。
第三次是六年级毕业后的暑假,那时我的上网时间是被限制的。那段时间几乎每天在我爸上班的地方,有一台没有联网的电脑,除了 Microsoft Office, 就只有金山打字通。那时拼音输入法没有现在智能,会五笔仍然是招聘的一个加分项,所以我爸妈都希望我们学五笔。我姐是打五笔的,打字非常快,虽然和我当时打拼音的『敲打速度』差不多,但是五笔重码率非常低,不需要像拼音那样,打出来,还得看看是哪个选项。加上那时我也闷得慌,就天天用金山打字通练五笔。
很多人说学五笔得背字根表,记口诀。我从小就讨厌背诵,所以一直没有去背那个鬼东西,导致我一直非常容易忘。后来我『牺牲』了自己一些有限的上网时间,搜了一下字根排布有没有什么规律。
然后我发现果然是有的,五笔字根在键位上的排布是有规律的。
首先来看一下整个字根的分布图:
对于新手来说肯定是非常吓人的,这也是很少人学五笔的原因。但是其实它是有规律的。
第一是整体上的划分,整个布局总共只有五个区,分别是撇(QWERT)、点(YUIOP)、横(ASDFG)、竖(HJKL)和折勾(XCVBNM)。这也许就五笔叫做五笔的原因。只要记住这五个区的分布就十分容易定位某个字根所在的键位了。比如你忘了『蝗』字的『虫』字旁在哪个键位,但是你知道它肯定是在 HJKL 其中一个键上。
再来深入地看某一个区中的规律。例如『刘』立刀旁,他是竖的,它肯定在 HJKL 其中一个键位上,那到底是哪一个键呢?如果你仔细看一下,你会发现,H 是一条竖,J 是两条竖,K 是三条竖,L 是四条竖。立刀旁是两竖,那它就是在 J 上了。
当然,还有很多其它的字根并不遵循这些规律,到底是为什么,我也不知道,你得问王永民先生。打多几次,就能记住。
记着这两个规律,自己再读一遍字根图,摸索一下,再打个一星期,正常人来说,是肯定能学会的。但是熟练还是得靠多打。五笔是十分靠肌肉记忆的,打到熟练的时候,你已经不会再去这个字应该怎么拆的。比如我就不会去想『应该』这两个字怎么拆,但是我知道它肯定打 YIYY
。
现在社区上一般讨论的是五笔快还是拼音快。常年打拼音的人肯定会说现在的拼音输入法已经相当智能,不怎么需要去考虑重码。事实确实是这样的,以前智能 ABC 的时代,五笔是完全可以虐拼音的。但现在,起码在这个点上,五笔的优势已经不再明显了。也就是说,五笔最能体现出优势是在脱离词库的场景。这有点像 vim 和 sublime 的比较,离开了 GUI,vim 的优势就迅速体现出来了。
根据我多年的经验,实际上,拼音不是最快的,五笔也不是,最快的输入方式是拼音五笔混输。说实话,打了这么多年五笔,我仍然不清楚『尴尬』两个字怎么拆,当我打到这两个字的时候,我只要输入"ganga",就立刻打出来,这是最好的,非常舒服和自然。这一段文字,除了尴尬两个字,其它全是五笔打出来的,难拆的字一点也没影响到我的打字速度。所以,还是那句话,最快的输入方式是拼音和五笔混输。
无论在什么情况,能学习的东西,学了肯定是好的,不管它是不是『有用』的。就像 Steve Jobs 说的,connecting the dot. 有没有必要学,花多少时间去学,那是你自己要考虑的事情,作为旁人,没有办法给意见。只能说的是,学了以后不会在短时间内给你带来什么,不学你也不会失去什么。
人越长大,就越少机会做些能让自己感到开心的『无用』的事情了。
关于输入法,我在 Mac 上用的是 QQ 五笔,基础词库很足,再加上流行语词库,就十分够用了。打五笔,词库只有嫌多,没有嫌少的。至于手机,我在手机是不打五笔的,因为没有手感。之前已经说了,打五笔到后期基本是靠肌肉记忆的,也就是说,离开了实体键盘,很难有同样流畅的手感。我在手机上比较奇葩,用的是笔划输入,是学我姐的。最近也在玩 Google 的划动输入,所以偶尔也打打拼音。
]]>最近在把 SISE Game(我们学校的校内游戏直播网站) 从原本的 Ruby on Rails 彻底用 Node.js 重写, 经过一些考虑,决定用 Vue.js 和 Express.js 实现前后端分离的架构,在这几天的重写过程中,积累了对 Webpack 和 Vue.js 的一些新的看法。
Vue 是个很年轻的 MVVM Library,常常有很多人用 Angular 和 Vue 比较,因为两者都是 MVVM,但实际上,前者是 Framework,而后者是 Library。前者有很陡峭的学习曲线,后者可以很快地掌握运用到项目中去。
Vue 的官方是用 a library for building modern web interfaces 来描述自己的。Vue 适合和 React 对比,因为在使用 Vue 的 Components System 开发比较大型的 Single Page Application 的时候,我发现它和 React 有一些相似的地方。如果你赞同 React 的思想,但又不想写 JSX,那么,你就可能需要试试 Vue 了。
一个用 Vue 实现 Data binding 的 Demo:
<!-- index.html -->
<div id="#app">
<input v-model="msg" />
<p>{{ msg }}</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
msg: 'hello Vue.js'
}
})
</script>
前端开发发展到现在,我们做过的很多努力,都是在尝试把开发者从繁琐的 DOM 操作和管理 DOM state 中解放出去,我们希望只需要通过描述数据和行为,DOM 自己就可以发生对应的变化,React 在 View 这一层实现了这一目标,而 MVVM 则是通过 ViewModel 的 Data Binding。React 宣称自己是 View,那么在我看来, Vue 则是 View + ViewModel,并且 Vue 更加 lightweight 和 flexible。
Vue 最让我喜欢的是它的 Components System,利用它可以构建组件化的中大型应用。React 当然也是组件化的,但是 Virtual DOM (JSX) 在一些场景让我很不满意。比如有一次,我用一个使用 React 的项目中,想要在一 <video>
里使用 webkit-playsinline
这个 attribute,但是 React 不支持,渲染的时候直接被 ignored,我必须手动地操作 DOM 给 <video>
setAttribute。相反,Vue 的 Components System 当中,写的是真正的 DOM,不需要担心不支持不兼容的各种情况。
容易被用作对比的是 Angular。我第一次听说 Vue 的时候,也是把它当作一个 lightweight 的 Angular alternative. 但是当真正实践使用 Vue 的时候,才发现它和 Angular 有着很大的不同。Angular 是一个 Framework,一旦你使用它,就必须按照它的一套去组织你的项目。以前写 Angular 项目的过程和经历对我个人来说都不太愉快,我更加倾向于 Vue 这种更灵活的方案。
关于 Vue 和其它库和框架的对比,官方也有作者更详细的 解答(中文版本)
SISE Game 并不算一个大型的 Web APP,但也规范地使用组件化的开发,整个项目的结构大致如下:
├── app
│ ├── app.js #entry
│ ├── app.vue
│ ├── config.js
│ ├── filters # 自定义的一些 filters
│ ├── components #各种组件
│ ├── models
│ ├── utils
│ └── views #各种页面的 views
│ ├── home.vue
│ ├── room.vue
│ ├── signin.vue
│ ├── signup.vue
│ └── user.vue
├── bower.json
├── build
├── gulpfile.js
├── index.html
├── node_modules
├── package.json
├── static #静态文件
│ ├── images
│ ├── styles
│ └── swfs
└── webpack.config.js
Vue 通过自己的 .vue
文件来定义 components,.vue
文件里包含组件的模板、逻辑和样式,从而实现组件和组件之间的分治,非常易于维护。
<!-- components/user.vue -->
<template>
<p>Hello {{ name }}</p>
<button v-on="click: alertName()">alert!</button>
</template>
<script>
module.exports = {
data: function(){
return {
name: 'Randy'
}
},
methods: {
alertName: function(){
alert(this.name);
}
}
}
</script>
<style>
p{
color: #69C;
}
</style>
以上就是一个简单的 component 实现,借助 webpack,甚至可以直接在 component 里写 es6、scss 和 jade。
路由对于 Single Page Application 来说应该算是不可少的东西,Vue 作为一个 Library,它本身并不提供这些组件。目前官方的 vue-router 仍处于 technical preview 的状态,官方也建议可以使用 component 和 Director.js 实现路由,比如:
<div id="app">
<component is="{{ currentView }}"></component>
</div>
Vue.component('home', { /* ... */ })
Vue.component('page1', { /* ... */ })
var app = new Vue({
el: '#app',
data: {
currentView: 'home'
}
})
// Switching pages in your route handler:
app.currentView = 'page1'
这样你只需要操作 app.currentView
的值就可以实现视图的切换,这一步通常会配合 Director.js 这类的 hash router.
与其费周章说明 Webpack 是什么东西,倒不如先说说不用 Webpack 以前的一些现实。
我们在写前端 JavaScript 的时候,通常是写在多个 .js
文件里,通过闭包避免全局变量污染,然后一股脑地用 <script>
引入。
<body>
...
<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
</body>
出于性能上的追求,我们会应该把 a.js
b.js
c.js
合并为同一个文件 bundle.js
来减少请求数量,变成:
<body>
...
<script src="bundle.js"></script>
</body>
使用 Gulp/Grunt 等自动化构建工具很容易可以实现这样的 concat,但是很快我们就会发现,单纯的 concat 并不是一个好的方案,因为代码文件之间的依赖关系不明确,这样一来,有时不得不花一些时间去组织 concat 的顺序。我们很希望像写 Node 一样模块化地去写前端 JavaScript。
又有些时候,在两个不同的页面当中我们常常会共用一些代码,单纯的 concat 会增加很多不必要的体积。
所以 ,我们理想的情况是,可以在前端优雅地写符合模块规范(AMD, UMD, CommonJS)的代码并且自动打包,最好还能自动把重用的文件分离出来。
嘿,Webpack 就很擅长做这种事。
Webpack 兼容所有模块规范(如果你不知道到底用哪一种,就用 CommonJS)。
配置 webpack 比较简单,你需要定义入口文件和 bundle 输出的目录:
// webpack.config.js
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
}
}
这样,你就能在前端这样去写 JavaScript:
// /app.js
var Vue = require('vue');
var app = new Vue({/*...*/})
这是 CommonJS 的写法,如果你写过 Node.js,应该对这种写法相当熟悉。这时运行 $ webpack
,webpack 会自动根据入口文件 app.js
中的依赖关系来打包成单个 js 文件,输出到配置文件中指定的 output path 中。
webpack 也可以通过 plugin 自动分析重用的模块并且打包成单独的文件:
// webpack.config.js
var webpack = require('webpack'),
CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
},
plugins: [
new CommonsChunkPlugin('vendor.js')
]
}
webpack 的一个特色是可以指定多个入口文件,最后打包成多个 bundle。比如说 Timeline page 和 Profile page 是不同的页面,我们不希望两个页面的 js 被打包在一起,这时我们就可以为 timeline 和 profile 两个页面定义不同的入口:
// webpack.config.js
module.exports = {
entry: {
timeline: './timeline.js',
profile: './profile.js'
},
output: {
path: './build',
filename: '[name].bundle.js'
},
plugins: [
new CommonsChunkPlugin('vendor.js')
]
}
最后会被分别打包成 timeline.bundle.js
和 profile.bundle.js
。
webpack 神奇的地方在于,任何的文件都能被 require()
。依靠各种 loader,使你可以直接 require()
样式、图片等静态文件。这些静态文件最后都会被处理(比如 scss pre-process 和图片的压缩)和打包在配置好的 output path 中。
#container{
background-image: url(require('.https://gbstatic.djyde.com/blog/background.png'));
p{
color: #69C;
}
}
// app.js
require('./styles/app.scss')
// blablabla....
你可以像上面这样在 JavaScript 中引入 scss (和在样式中引入图片),只要你配置好处理 scss 的 loader:
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.(css|scss)$/,
loader: ExtractTextPlugin.extract('style','css!sass')
},
{
test: /\.(png|jpg)$/,
loader: 'url?limit=8192' // 图片低于 8MB 时转换成 inline base64,非常神奇!
}
]
}
}
css 默认被编译到 JavaScript 中成为字符串后再被插入到 <style>
中,我个人建议使用 ExtractTextPlugin
这个插件把 css 分离出去。
在以往的一些小型的前端项目中,我习惯把逻辑(scripts
)、视图(views
)和样式(styles
)分开在独立的目录当中,保证三者不耦合在一起。但是随着项目越来越大,这样的结构会让开发越来越痛苦,比如要增加或修改某个 view
的时候,就要在 scripts
和 sytles
里找到对应这个 view
的逻辑和样式进行修改。
为了避免这样随着项目增大带来的难于维护,我开始尝试前端组件化,把 views
拆分成不同的组件(component),为单个组件编写对应的逻辑和样式:
app/components
├── Chat
│ ├── Chat.jade
│ ├── Chat.js
│ └── Chat.scss
└── Video
├── Video.jade
├── Video.js
└── Video.scss
这样的开发模式,不仅提高代码的可维护性和可重用性,还有利于团队之间的协作,一个组件由一个人去维护,更好地实现分治。幸运的是,随着 React 越来越火,组件化的开发模式也就越来越被接受。
在 Vue 中,可以利用一个 .vue
文件实现组件化,而不需要对每个组件分别建立 style, scripts 和 view。这样做的好处是使组件能更加直观,而坏处是目前有些 editor 对 .vue
的语法支持还是不太好。我用 Atom 写 .vue
的时候,<style>
的那一块并不能自动补全。不过我个人不依赖 css 的补全,所以没有太大的影响。如果你比较依赖这个,建议你还是把这些代码分离出来。
一个简单的 Vue Component:
<!-- components/sample.vue -->
<template lang="jade">
.test
h1 hello {{msg}}
</template>
<script>
module.exports = {
el: '#app',
data: {
msg: 'world'
}
}
</script>
<style lang="sass">
.test{
h1{
text-align: center;
}
}
</style>
我们使用 Webpack 就可以自动将 .vue
文件编译成正常的 JavaScript 代码,只需要在 Webpack 中配置好 vue-loader
即可:
// webpack.config.js
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.vue$/, loader: 'vue-loader'
}
]
}
}
这样,就可以正常地在文件中 require()
所有 .vue
文件:
module.exports = {
el: '#app',
data: {/* ... */},
components: {
'sample': require('./components/sample.vue')
}
}
vue-loader
使用 style-loader
把 component 当中的样式编译成字符串后插入到 <head>
中去。但我们希望把 css 文件独立出去,那么可以使用上一篇文章提到的 ExtractTextPlugin
插件,配合 vue-loader
的 withLoaders()
方法实现生成独立样式文件:
// webpack.config.js
var vue = require('vue-loader')
, ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.vue$/, loader: vue.withLoaders({
sass: ExtractTextPlugin.extract("css!sass") // 编译 Sass
})
}
]
},
plugins: [
new ExtractTextPlugin('app.css') // 输出到 output path 下的 app.css 文件
]
}
webpack 是一个十分好用的模块打包工具,使用它更加利于实现前端开发工程化。
很多人认为 webpack 可以取代 Gulp/Grunt 等构建工具,其实不然。Webpack 仅仅是顺便替构建工具分担了一些预编译预处理的工作,而构建工作不仅仅只有预编译啊。
Vue.js 和 Webpack 的结合使用方法写到这里就已经算是写完了,当然,还有很多其它的实践方法,都要靠读者自己去摸索了,这个系列仅仅是想给没有使用过 Vue.js 或者 Webpack 的读者一个大概的认识。
最后趁这个机会感慨一下,前端开发是让人感到兴奋的,我以前也写很多有关前端的东西,但从来不愿意称自己为『前端开发者』,是由于自己对前端开发的各种浅见,认为前端开发低端、repetitive、不能成大事。但是经过更加深入的实践,才慢慢发现前端也是工程化的、有学问的、有活力的。我很高兴可以作为一名『前端开发者』,在这里感受日新月异的氛围的技术浪潮。
编程是非常有趣的一件事情,学会编程就像成为魔术师,你可以创造任何你想创造的东西。在我出生到现在的这 20 年里,除了文学,没有比编程更吸引我的活动。就像 Zed Shaw 所说的,『编程作为一项智力活动,是唯一一种能让你创建交互式艺术的艺术形式。你可以创建项目让别人使用,而且你可以间接地和使用者沟通。没有其他的艺术形式能做到如此程度的交互性。电影领着观众走向一个方向,绘画是不会动的。而代码却是双向互动的。』
但是当你开始接触(我所在的)大学的计算机相关课程,你会发现,所谓的编程学习,无非是老师教一章内容,要求你用学到的语法去解一些数学题。于是很多人选择放弃,认为编程乏味无趣,用很长的课时学习,却只能写出简单的四则运算,或者解数学题。
然而这并非编程的全部,因为这恰恰能生动地表明编程语言只不过是一种工具,你可以用它来解数学题,也能做其它更有趣、更实用的程序。
在(我所在的)大学,计算机相关课程教 C/C++。有一次,一位大三的朋友问我说,『我学 C 学了一段时间,为什么我还是感觉很没用?』我是这样回答他的:
你学 C 学了一段时间感觉没有用处并非你个人的问题,我无法跟你解释为什么,不过我可以告诉你的是,Linus Torvalds 用 C 写了 Linux 和 Git; Antirez 用 C 写了数据库 Redis. 你可能连 Git 是什么都不知道,如何用 C 写出这种水平的程序呢?
所以,当你学习 C/C++ 基础后进入困惑期时,不要因为用它写不出实用的程序而止步不前,而应该开始接触其它的编程语言。当你所学和所做的东西越来越多的时候,你慢慢就会发现,编程语言只不过是一种工具,重要的在于你想做什么,用什么语言合适去做(华软的校内游戏直播网站 SISE Game 就是例子,网站的后端用 Ruby on Rails 写,但实时聊天是用 Node.js (JavaScript) ,因为相对于 Ruby, Node.js 更加适合处理 WebSocket)。 你之所以感觉 C/C++ 没用,是因为你暂时还没有足够的眼界和能力去使用他去进行创造。
应该去学什么?我的建议是一门严谨的强类型语言(如 Java)和一门愉快的脚本语言(如 JavaScript, Ruby, Python)。当然了,如果你想学一些能立竿见影的技能,你也可以写写前端(HTML, CSS)。
对于应该如何去学习,我不打算在这里论述,我只想列出一些有用的建议:
无论你要学什么,你读的第一篇文章应该是《提问的智慧》
用 Google 搜索技术相关的信息
购买付费可靠的科学上网服务,不要吝啬一顿饭的钱。
不要害怕英文,英文非常重要。
几乎所有最新的技术在刚出来的时候只有英文文档,优质的 Tutorial 大多数也是英文的。
这一点很重要。学校所教的内容是陈旧、过时的(虽然这也许不是学校的错),你必须和社区保持同步,不断接触和学习新的技术。AngularJS 发布至今都有 6 年了,但在我们学校仍然在教 ExtJS,很多学生甚至毕业出来找工作的时候,连 AngularJS 都没听说过。
国内的 V2EX 和国外的 Hackernews 都是不错的社区。
你并不能指望大学的课程或者大学老师能给你带来什么,一切都应该靠你自己。无论在多优秀或者多差劲的学校,学校本身能影响你的是辅助性的,而不是决定性的。在我的学校,几乎都在打游戏和应付考试,据我所知,即使是重点大学,这样的学生也大有人在,我渐渐明白,『你今后人生的艰难,恰恰不是因为你没能考上一个满意的学校,而是在这所学校里,在一个能让自己自由充分成长的黄金四年里,把自己给荒废了』(采铜的回答)。
如果你以后有意从事编程的相关工作,你应该要知道,在找工作时和其它竞争者拉开差距的,并非你从哪里毕业,在学校当什么干部,而是你有足够的能力,并且能清楚地让别人看到你的能力。
让别人看到你能力的方法是开源社区和博客,所以我建议你在学习的过程中,多为开源做贡献,甚至可以维护一个自己的开源项目。另外还可以把自己的所学记录和总结到独立博客中。一个好的 Github Account 和好的独立博客是一份最好的简历。也许你也只有在大学这自由的四年才能有时间做这两件事情。
我在广州大学华软软件学院读书已经有一年了,这一年里我遇到过许多对现在对未来感到迷茫的朋友,他们对教育抱有希望,对知识怀有渴望。我曾经试图做一些事情去改变现状,但是效果都不大,该混日子的还是继续混日子,到毕业后才开始抱怨就业难。我呸,互联网行业都缺人缺到什么地步了你跟我说就业难?
不过,只要我还没被学校开除,我还是会尽我所能地为迷茫的朋友做一些事情。至于做些什么,我还没想好,我不知道应该怎么做,如果你有好的建议,欢迎联系我。
如果你看完这篇文章,还有问题可以联系我的 Email: randypriv@gmail.com,只要你读过《提问的智慧》,问的问题经过自己思考,我都会尽快地详细地回复。
『你会编程。他们不会。这真他妈的酷。』
]]>今年的 1 月我还在为化学烦恼,今年的 12 月现在的我已经过上了那时梦寐以求的自由的生活。但是生活哪有什么所谓的自由,高考只不过给我开了一个闸,从一个小笼子,滚进了一个更大的笼子而已。而这个更大的笼子比以前按部就班的生活更让我感到害怕,就像《海上钢琴师》里 1900 放弃离开轮船后 对 Max 说的一样,让我害怕的不是我看到的东西,而是我所无法看到的东西,这里什么都有,可是唯独没有尽头。
还好上了大学以后,能遇到几个让我又有了寄托的朋友,我和他们一起做事,非常地快乐。
技术方面,哈哈,就不说了,我仍然迫切的希望能用技术给这个社会带来一点好的改变。
遗憾的事是好像很难再找到能聊聊天的朋友,而且好像变得不太爱说话了,所以有时候我还是挺怀念高一时候的我。最遗憾的事是 Google 还没有解封,当我以为『她』正在慢慢变好的时候,却再一次让我失望了。
新年愿望是,1,所有人都好 2,能继续写代码 3,生活的这片土地能再进步一些,我知道这的确需要一个过程,但我想自己,和我身边的人,还有未来的儿子和女儿,都能在这里活得快乐些。
最后附上 1900 对 Max 说的那段话,说的真好:
]]>All that city. You just couldn't see the end to it. The end? Please? You please just show me where it ends? It was all very fine on that gangway. And I was grand too, in my overcoat. I cut quite a figure. And I was getting off. Guaranteed. There was no problem. It wasn't what I saw that stopped me, Max. It was what I didn't see. You understand that? What I didn't see. In all that sprawling city there was everything except an end. There was no end. What I did not see was where the whole thing came to an end. The end of the world...