初窥基于 react-art 库的 React Native SVG

技术背景

在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到Android和iOS均有一套各自的API方案,因此采用一种更普遍接受的技术方案,更有利于代码的双平台兼容。

art是一个旨在多浏览器兼容的Node style CommonJS模块。在它的基础上,Facebook又开发了react-art ,封装art,使之可以被react.js所使用,即实现了前端的svg库。然而,考虑到react.js的JSX语法,已经支持将<circle> <svg>等等svg标签直接插入到dom中(当然此时使用的就不是react-art库了)此外还有HTML canvas的存在,因此,在前端上,react-art并非不可替代。

然而,在移动端,考虑到跨平台的需求,加之web端的技术积累,react-art成为了现成的绘制图形的解决方案。react-native分别在0.10.0和0.18.0上添加了iOS和Android平台上对react-art的支持,当然,没有文档。在文档基本等于没有的情况下,笔者苦逼地翻源代码,为大家带来了(全球首发?=_=)的入门文档。

示例代码

推荐大家采用react-art自带的Example: Vector-Widget。React.js和React-Native的区别,只在于下文所述的ART获取上,然后该例子就可以同时应用在Web端和移动端上了。

成功运行Vector-Widget后的效果图:
rotating-react

Vector-Widget额外实现了旋转,以及鼠标点击事件的旋转加速响应。Web端可以看到点击加速,但是在移动端无效,原因是React Native并未对Group中onMouseDown和onMouseUp属性作处理。本文着重于静态svg的实现,暂时无视动画部分效果即可。

此外还可以用该例子感受一下叹为观止的svg动画的性能占用(摊手)。

原理和调用

获取ART

package.json中需要引入art库,笔者的版本设置是art: ^0.10.0

Android与iOS

或者使用ES6的Destructuring特性:

Web端

基本组件

获取方式

接下来的所述的代码,web端和移动端都是通用的,这也是React Native的诱惑所在。

Surface

所有的svg component必须被一个Surface标签所包含。
Props如下:

  • width: Surface的宽度。
  • height: Surface的高度。
  • style: margin系列和padding系列都生效。

Group

Group用于组合art component。比如在一个函数中返回多个svg component的情况,此时就必须要用<Group>包一下,否则即报错。<Group>可以嵌套使用。

style:margin和padding系列均无效,我怀疑不接受style。

Shape

Shape用于生成路径,语法与svg中的<path>很相似。Shape的Props如下:

  • d: 语法与svg规范相同
  • stroke: 线条颜色,”#FFFFFF”的形式
  • strokeWidth: 线条宽度,{3}的形式
  • transform:接受 new ART.Transform()生成的object,具体见下文Transform条目。

Path

语法更近似于移动端。使用方法:

可以看到,取出的Path是一个构造函数。Path对象的中的函数功能如下,大多与svg规范一致,我就再啰嗦一遍了。svg规范中<path>的d属性请参见https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d :

  • push():
  • reset(): 清空Path
  • move(x, y): 等同于’m’,移动到目的坐标,参数x和y是相对目标下的目的坐标
  • moveTo(x, y): 等同于’M’,与move只差别在x和y是绝对坐标。
  • line(x, y): 等同于’l’,从一个坐标点到另一个坐标点画直线,参数x和y是相对坐标下的目的坐标
  • lineTo(x, y): 等同于’L’,与line只差别在x和y是绝对坐标。
  • arc(x, y, rx, ry, outer): 等同于’a’,从一个坐标点向另一个坐标点画椭圆曲线,x和y是相对坐标下的目的坐标,rx和ry是椭圆的长轴半径和短轴半径,outer只有0和1两个数字,代表是大角度还是小角度。
  • arcTo(x, y, rx, ry, outer): 等同于’A’,与arc只差别在x和y是绝对坐标。
  • curve(2个,4个或6个参数): 从一个坐标点向另一个坐标点画贝塞尔曲线。
    • 当参数为两个时,等同于’t’,绘制光滑二次贝塞尔曲线。
    • 当参数为4个时,等同于’q’,绘制二次贝塞尔曲线。
    • 当参数为6个时,等同于’c’,绘制三次贝塞尔曲线。
    • 有些精通SVG的同学这时候可能就要问我了,不对啊,二次贝塞尔曲线和光滑三次贝塞尔曲线的参数都是4个,你这里没有光滑三次啊?因为开发的同学留坑没写了呀(微笑)。

Transform

实现代码路径:art/core/transform.js

Transform对象中的函数:

  • transform(xx, yx, xy, yy, x, y): transform的相对坐标版本
  • transformTo: 完整的矩阵变换,把这张位图上所有的点都做一次矩阵乘法,得到的新位图,公式如下图所示

matrix

  • translate(x, y): 位移
  • move: 相对于原参考点坐标,增减参考点的x,y坐标
  • moveTo: 等同于translateTo,容易误以为是move的绝对左边版本,吐糟不能
  • scale(x, y): 将一个元素拉伸或者压缩指定的倍数,x是宽度缩放比例,y是长度缩放比例,如果只有一个参数,则x = y。如
  • scaleTo: 在缩放的同时保持原来的长宽比。例子和效果图如下:

three-rec

  • rotate(deg, x, y): 将一个元素旋转角度deg。x和y则是用于指定旋转的原点。

效果如下图所示:

rotate-rec2
rotate-rec1

react-art中的lib

引入lib中的module

在react-art的库中,有个神奇的lib文件夹,下面除了ReactART.js以外,还有Circle.art.js,Rectangle.art.js,Wedge.art.js等,其中Circle和Rectangle分别对应于svg规范中的圆形<circle>,矩形<rect>,而Wedge则是用于生成扇形。当然,这些module都很不完善,很有可能需要二次开发,如果这样,推荐拷贝库文件到工程中再行修改。

引入它们的语句为var Circle = require('react-art/lib/Circle.art');。这种不同寻常地引用方式是Facebook开发和维护的fbjs引入的,react-native依赖了fbjs,而所有需要被输出的js文件的头部都会以// @providesModule Circle.art
的形式标明。

Circle

使用示例:

值得一提的是,Circle.art.js是个半成品,可以看到它根本没有实现svg规范中的cx和cy,因此画出来的圆的圆心始终在左上角,显示出来的也就只有半个圆。请在实际应用中自行实现,或者使用笔者提供的修改版本

当然,不用cx和cy的话,也可以设置tranform来实现平移。如:

两种方法都可以达到平移效果,但是cx, cy的方式相对来说更简洁,可读性也更好。

Rectangle

使用示例:

使用上述代码,就很直观看到这个module的缺陷了,矩形四条边不等宽(扶额)。
rectangle
此外还接受的props有:

  • radius
  • radiusTopLeft
  • radiusTopRight
  • radiusBottomLeft
  • radiusBottomRight

这里的radius指的是圆角矩形的圆角半径,接受的值类型为数字,radius为四个角的通用半径,但如果设置了具体某个角的半径,则用后者。

Wedge

Wedge是楔子的意思,然而在这里却是生成各种角度的扇形=_=。
使用示例:

生成的图形如下图:

wedge

可选Props为innerRadius,用于生成一个圆环扇形,如下图。

binWedge

呐,一看这个module也是半成品,如果stroke有颜色而fill为白色就露馅了,曲线没闭合。因此,如有需求,请自行弥补。

技术缺陷

除了之前所提到的各种各样的实现上的不完善以外,svg规范在React Native上应用的最大问题在于,有一大批web上的svg支持的css属性,还没有在React Native上实现。

以stroke类的一大批属性为例,目前可用的只有线条颜色stroke和strokeWidth,而如stroke-dasharray,stroke-dashoffset这样的可以用之实现神奇的描边等效果的CSS属性,目前还没有被支持。

在性能占用方面, 静态svg尚可接受,但如果是svg annimation,实测红米Note 2上的CPU占用率即可达50%左右,故其在生产环境的大规模应用,恐怕还需进一步的性能优化。

多React Native项目时依赖管理的最佳实践

在实际开发过程中,经常需要同时运行和修改多个React Native工程,比如运行github上的开源项目以观察某种控件的实际效果。那么此时,各项目下的初始化(npm install)就会非常的痛苦,因为React Native的文件非常大,以0.17.0为例,安装后达到309MB。尽管,我们可以通过阿里npm等镜像站的方式加速下载的过程,但是下载后的进一步编译也非常地耗时。

此外,多React Native工程还带来了React Native自身的冗余,如果创建了十几个工程,那么多占用的空间轻松达到3GB以上,非常地不友好。

npm link原理

我的解决思路是:用npm link替代npm install。npm link [package-name]命令的原理是,去检索是否已经全局安装该package,去prefix/lib/node_modules/下检索是否已经安装了当前的package,如果是,则直接用软链接的方法在本地路径指向全局package。如果没检索到,则会先在全局路径下安装该package,再去建立软链接。npm获取全局路径的命令是:npm config get prefix

需要注意的是,有package.json的路径下,不要类比npm install,就这么执行npm link。此时npm link会把当前路径作为一个本地package,在全局路径下创建一个软链接。由此可知,npm link并不会像npm install一样,读取package.json中的依赖并自动配置。

配置过程

简单三步搞定。然后运行react-native run-android,打个Android包检测一下。

纳尼,报错如下:

Looks like you installed react-native globally, maybe you meant react-native-cli?
To fix the issue, run:
npm uninstall -g react-native
npm install -g react-native-cli

原因很简单,react-native框架其实由两个部分组成:react-native和react-native-cli,前者用于提供编译环境,后者则是封装了react-native开发过程中所要用到的命令,如react-native start,实质就是封装了sh ./node_modules/react-native/packager/packager.sh

官方文档要求全局安装react-native-cli,但是局部安装react-native,这是有原因的。如果你先全局安装了react-native-cli,会在/usr/local/bin下生成一个名为react-native的软链接,其指向为:react-native -> ../lib/node_modules/react-native-cli/index.js*。而随后再次”全局”安装react-native的时候,又会生成一个名为react-native的软链接,覆盖了react-native-cli安装时生成的软链接,其指向是:../lib/node_modules/react-native/local-cli/wrong-react-native.js。由此可见,React Native官方已经意识到了这个问题,然而不知何原因并不推荐全局安装React Native。然而笔者从节约硬盘空间和加快初始化的角度,认为还是有必要全局安装React Native,从而快速npm link的,所以有必要研究该报错的原理。

因此,针对这个报错,两种解决方法:

  1. npm install -g react-native,再npm install -g react-native-cli。然而,如果以后使用过程中又升级了全局React Native,此时需看方案2。
  2. cd /usr/local/bin ln -s react-native ../lib/node_modules/react-native-cli/index.js,即可重新创建一个指向react-native-cli的软链接。如果prefix的地址不是默认的,则ln -s prefix/lib/node_modules/react-native-cli/index.js

缺陷

当前这个自动添加统一依赖的方法,存在一个问题。所有依赖于全局路径下的React Native都必须是一个版本的,npm link并没有提供多版本号依赖的解决方法。因此,还是建议选择一个常用的React Native版本安装在全局路径,个别需求其他版本号的React Native的项目,使用npm install来配置局部依赖。

插说一句,npm自身的依赖管理设计还是非常优秀的,然而React Native实在是太大了,而且我们完全有理由相信,他会更大。他其实应该是与Android SDK, Java SDK一般重量级的开发SDK,因此更应该借鉴rvm,设计一个React Native Version Manager。然而却委身于node_modules,因而产生了这种无奈的冗余。

解决OS X 10.11上Ducky 2108s机械键盘无响应的问题

我的Macbook Pro mid 2013在升级到OS X 10.11后,ducky 2108s插上去后无反应。在StackExchange中找到了该问题的解决方法,总结如下:

  1. 怀疑OS X 10.11进一步削弱了USB口的供电能力,致使部分机械键盘可能会因为供电不足而没有反应。笔者在之前版本的OS X的使用中也遇到过,机械键盘用着用着可能就突然没有响应了,通常都是重启解决。解决办法:购买独立供电的USB Hub,出门左拐万能的淘宝
  2. ducky 2108和2108s两款机械键盘都在OS X下无法使用,无论是否使用了外接供电的USB Hub。联系support@duckychannel.com.tw 后得知,需要刷新键盘的固件。刷新只能在Windows下进行,但刷新完成后,键盘即可在OS X上正常使用。本文后附固件文件(仅适配2108和2108s)、固件刷新程序和固件刷新帮助手册。其他版本固件请联系 support@duckychannel.com.tw 。

点击下载 ducky_2108_firmware_and_tool.zip

点击下载 firmware update流程Chinese.pdf

探究Express Request中的url

url在解析过程是会被逐级删减。如:

采用第一种写法,浏览器会得到404 Not Found。第二种则正常。原因即在于,url在解析过程是会被逐级删减。

通过node-inspector调试上面的例子,可得到运行中req.url,req.originalUrl,req.baseUrl的值。

解释如下:
req.url = req.originalUrl – req.baseUrl。也是router.get传入的匹配路径的匹配对象。这也就可以解释上面的例子的运行结果了。all,post,put等等函数同理。

req.originalUrl与req.url类似。不同的是,它用于备份最初的请求url,从而你可以随意重写req.url来到处跳转,而不用担心丢失初始的url。比如,用于挂载中间函数的app.use()就会重写req.url来压缩挂载点的长度。

req.baseUrl存储当前router挂载的路径。即app.use('/path', router);。此时/path就是baseUrl。即便在挂载的时候使用的是正则等匹配表达式,baseUrl存储的也会是匹配的字符串,而不是正则表达式。

next函数在Express Router中的应用

例1

运行结果:
Hello!

运行结果:
Hello!
World!

上述例子说明了,next函数的作用是调用挂载在同一路径下的下一个中间函数。换而言之,如果没有next(),这一次request的处理过程就结束了。

例2

换一种写法:

无next函数运行结果:
Hello!

有next函数运行结果:
Hello!
World!

解释

需要说明的是,Express文档中出现了很多次middleware这个词。其本义为中间件,未免太大了,在这里译作中间函数。

中间函数栈是开发者把许多中间函数按一定顺序组织起来的,新收到的请求被开发者定义的第一个中间函数接收并处理,随后被传给下一个中间函数,直到栈底,就像一个自来水管道一样。这也正是为何在Express文档中,next函数的作用被描述为control flow。

Express中的文档中写到:
app.use函数的参数function,可以接受单个函数,多个函数,函数数组,以及混用上述三种形式。这是中间函数栈的一种组成方式,如例2。另一种组织方式为重复多次调用app.use函数来挂载同一路径下的多个函数,如例1。

因此可见,重复多次调用app.use函数来挂载同一路径下的多个函数,与使用多参数传入函数,效果相同。另外,例2又一次佐证了next函数的作用,即传递给控制权给挂载在当前路径的中间函数栈的下一个函数。

我的大学

大学的奋斗至此,初见曙光,就此记录一下自己的感慨。

大一的时候刻苦地学习高数,晚上刷题刷到一点钟,但是成绩依然很差。由此明白了,自己实在不是学习数学的料。幸而在做科创时窥见了java的大门,大一下的时候又学会了C,一直坚持着有空就刷HOJ。虽然刷的很少很简单的水题,但至少,对敲代码更为熟练了。但其实内心非常的苦闷,因为刘汝佳的书完全看不懂,去参加校内ACM又被打击的无以复加。内心对本专业和编程都充满了绝望。

大二的时候,鬼使神差地在大二的创新研修课上选了输入法,从此加入实验室,从而看到了一个崭新的大门。惊才绝艳的师兄们,简直是我心目中的神,而且还是一堆神,把实验室堆成了奥林匹斯山。算法狂魔吴嘉伟,无所不能的老任,充满产品气质的李阳,神秘而强大的hadoop高人李爱宝,超级学霸岑武斌,肌肉比代码更强大的孟哥,项目经验无比丰富的秦师兄,超级超级有钱的恭叔。开始写Java的时候,真的是非常的痛苦,代码量太大,幸得咬牙坚持了下来,用一个学期熟悉了整体的代码,在大二下的时候疯狂地刷代码,每天晚上七点到十点走,坚持了差不多一个学期,完整参与了WI九键的开发,也积攒了初步的项目经验。这时候,跟在师兄们身边,虽然很多所见所闻,当时都不一定能理解,但正如谢逊要求年幼的张无忌硬背《九阴真经》一般,当师兄们离开后,我慢慢地琢磨师兄们所说过的一言一语,领悟了更多的深意。

我一直深深地感谢实验室和师兄,是因为他们给予在最黑暗的时光里苦苦地追寻的我,一个光明的道路。身处一个大牛云集的牛棚之内,我学习到的是师兄们的视野,师兄们的做事风格,还有师兄们对技术的洞察。

大二下的暑假,我一个人留在学校,自学了git,vim,linux等等一系列的开发工具和环境,梳理了自己大二一年的技术积淀,一点一点地打磨WI输入法2.1版,从而实现了技术上的升华。

大三上,自学了Python。系统地阅读了输入法内核代码,从这个庞大的C语言项目中,感受到了开源代码的灵魂-C语言中无穷无尽的奇技淫巧,伟大的UNIX哲学,以及经典不朽的VIM,GCC,GDB。认识了新加入的一批软院大神,号称”赵师兄不走,阿里不穷“的铨哥,懒虚怂大神亮叔,萌神高扬,帅的不当gay可惜的富帅,巨有钱的骚博,安卓大神兰兰。在这一个充满活力的团队环境,我从他们身上,学到了太多,无论是技术知识,技术理念还是技术视野。

特别是亮叔,坦率而言,我真的很嫉妒亮叔。作为一个拿github当朋友圈刷的宅男,一个看美剧不用字幕的英语达人,一个遍历了几乎所有的软件技术的人,亮叔简直是为技术而生的“计算机之子”。在和亮叔“撕逼”的大半年里,我总是通过抬杠获得亮叔气急败坏的被动传授(^_^),从而学到了很多珍贵的技术视野。

大三上的暑假,系统地阅读《现代操作系统》《数据结构与算法分析》《计算机网络:自顶向下方法》,涉猎《设计模式之禅》,《代码整洁之道》,理论与实践相印证,有如任督二脉皆通,对技术满怀敬意,对代码满怀热爱,对生活满怀期盼。

还要深深地感谢斯逗比,盛哥,CC哥,飞哥。在我作死翘课的半年多里,帮我各种掩护,使我能够全身心地投入到代码之中。

我一直觉得,WI输入法,也许不能成为一个呼风唤雨的输入法霸主,也许始终只是保持着一个小众的地位,那又怎么样?WI输入法团队里的大学生,始终代表了工大最优秀的移动开发的团队,我们,就无愧于闫于闻师兄和其他三位创始师兄所开创的事业。“留一口气,点一盏灯,有灯就有人。须知念念不忘,必有回响。”

什么是IT?什么是技术?什么是GPL?什么是FSF?什么是GNU?什么是OpenSource?他们都基于一个根本的崇高的理想,推动信息的平坦化,最终实现政治的清明,并让每个人希望拥有平坦的人都能享有这种知识。这就是我对技术的梦想。我,并没有忘记高中时的理想,匹夫虽弩,未敢忘忧国。

信息的获取成本

这个寒假闭门谢客,专心看书,深深地感受到看书之难,坚持不易,然而收获亦不菲。其中感悟,记录如下:

看书难,看硬书更难。所谓硬书,就是那些厚度能比砖,字体小于蝇,图画稀如金的书,而往往已经不属于普及阅读之流,列入一个专业的经典之例。前半个寒假啃一本《现代操作系统》,早上起床,先对着书页发呆,呆上半个小时。时而翻翻昨日的日记,时而看看印象笔记中的摘录的励志文章,如此反复地做足了半个小时的思想工作,再翻开书,一行一行地琢磨字句的含义。比如文件系统中的一个“段”字,顿时就能联想到segmentation fault,想到多少次编译不通过,却始终不能理解报错的本义。我本是处于项目经验有余,理论知识不足的状态,旁人或许以为如此一来,看书就可以与实际经验结合,速度自然一日千里。大谬。正因为实际敲代码中遇到了很多业务场景,对理论知识的印证,就往往会有多种解释。这正是,看的多,想的也多,而且生怕想错了,以后用的这块知识,还要走弯路,因此读书时所写的笔记越发不敢下笔。子曰:述而不作。《诗》曰:兢兢业业,如临深渊,如履薄冰。书法家说,王羲之之后,苏黄米蔡,都逃不开二王的笔意,这些例子,讲的都是同一个道理。如此读书,当然费时,硬书之硬,正是因为此。

当然,付出必有收获。虽然说来惭愧,看了半个月的书,一本《现代操作系统》看了不过四章,然而那十几页笔记不会骗人,都是我自己靠自己的知识,自己对知识点的理解,从而总结成的重点,画出的关键算法的流程图,一些大块知识点的思维导图。100多页的阅读,看似寥寥,却是真真切切地解答了我这一个学期,在Android NDK编程中遇到很多问题。因为NDK的核心就是Linux下的C编程,我所写的输入法内核,又涉及到了大量的内存映射,内存读写的问题,因此,很容易遇到类Unix环境的操作系统知识。概括而言,就是一个程序的生存环境的问题。至今记得一个数学老师的教诲,“一个数学函数,首先要搞清楚他的生存环境,就是他的定义域,才能更深刻地理解函数”。操作系统上的理论知识,其价值也正在于此,虽然大多数程序员不会有机会去写操作系统,然而深入地理解操作系统,才能使自己的程序适应他的生存环境,或者更进一步地,更好地利用自己的生存环境。前者谓之兼容,后者谓之优化,如是而已。

更进一步地推进我们的思考,我为什么明知自己长于实践,而困于理论,却一直以来没有去好好看硬书。原因有三:
1. 忙。在学期内的时候,我的时间很大程度上,并不为自己的意志所左右。课业上,被我怒弃的微电子,很可能突然一个通知收什么作业,我就不得不连夜抄作业。或者本周突然发现积压了几个电路实验,就只能放下手头的编程,准备如何混过。输入法团队内的开发任务,也大多数是在学期内推进的。此外,如果有学弟晚上来到了实验室,立刻得放下手头一切工作,给学弟讲解业务知识,分配开发任务。毕竟学弟来趟实验室不容易(哭),不能让他们来适应我的时间表,只能我去适应他们的时间表。总而言之,人一旦有了工作,处在一个团队之中,你的时间表就必须要适应很多人,正如一个图中的顶点,有很多入度很多出度,这直接导致了你的时间表被切的很碎。
2. 看硬书需要一个足够长的,安静的环境。这一点就是第一点的补充,我得承认我自己的懒惰,所以看书之前我需要半个小时来整理自己的心绪,从而心平气和地看硬书,也只有这样,读书才能读的进去。恐怕这也正是为什么,知乎上如此多的人,认为大学的时候不好好看书,工作后想要看大部头的硬书,难度更大的原因了。毋论结婚生娃,话说起来最烦小孩子了(逃)。Pocket,Instatpaper这样的软件之所以流行,也是因为这一点,即收集高信息含量的文章,将其留到一个更适合阅读的环境。
3. 平时解决理论问题,主要靠看网页上的文章。要么是博客,要么是技术网站上发的文章。这类文章,优点是短小精悍,一针见血。缺点也很多,其一是良莠不齐,往往要搜索大量的网页,才能找到一两篇满意的。其二是有所缺漏,因为大多数都是个人的技术感悟,往往会因为个人技术经验的问题,不能涵盖一个问题的方方面面。其三是不成系统,比如进程,内存,文件,三者是相辅相成的,很难脱离群体讲清楚单个概念,因此阅读技术文章,往往只能达到似懂非懂的境地,而不能彻底地掌握一个技术方向上的全局。“横看成岭侧成峰”,终究不如,“会当凌绝顶,一览众山小。”这,恐怕就是大部头硬书的优势了,虽然离实际生产环境太远,没有实践经验很难与开发相联系,但是作者都是洋人大牛,书也是洋洋洒洒几十万字,事无巨细,字字珠玑,细细品悟能咀嚼出很多技术上的精髓。
4. 综上所述,珍惜宝贵的寒暑假,毕业说远不远,就在眼前了。

由此想到,我们每天看微博,刷知乎,刷朋友圈,真正获取到的有用信息能有多少呢?发表言论越容易,言论也就越廉价,所以邮件列表比qq更具有沟通价值,所以硬书信息量大于博客,博客信息量大于微博,微博信息量大于朋友圈(这里假定微博关注的是大V,朋友圈关注的是周围朋友),朋友圈信息量大于某人人(转发low文章的信息量,还不如让我知道朋友去哪儿旅游了,后者无害,前者祸害)。阅读信息量高的文本,付出的脑力上的折磨,得到的是更高的单位有效信息获取量。正如健身付出的身体上的折磨,得到的是更强健的体魄,这都揭示了同样的道理,只有毅力顽强之人,才能给予自身以更大的升华。