海阔天空的云

我们在自己的世界里独自狂欢

0%

起因

我在11月底的时候,在知乎上看过一个帖子,讲的是某人通过搬运B站上面的视频,获取了很大的经济收益。我当然是很羡慕的,后来简单研究了一下,发现YouTube 有API供开发者调用,这意味着上传的工作完全可以由计算机来完成。我很快想到,既然别人能够搬运B站上面的视频,可能这些人,还没有技术,只是靠手动搬运。那么我是不是可以通过代码来实现,视频的全自动搬运呢?

我觉得这是个不错的点子,后来又经过研究,得知YouTube上的视频,大概每千次观看量,视频的所有者可以得到大概5元人民币的收益(已经过换算)。而内容的影响力又是绵延不绝的,只要是好的内容,接下来就是躺着挣钱了。我当然是看好YouTube和他的收益情况的,毕竟我要做的,也不是自己制作视频,而是靠搬运。但紧接着,我又发现,谷歌对于YouTube频道的收益有一个门槛,只有达到了在过去的一年里,有1000个订阅者,4000个小时的累计播放量,才能够申请通过广告分成的方式获取收益。经过简单的计算,我发现,实际上,只需要每天10个小时的播放量,就可以达到4000小时累计播放量这个门槛,而只要有几个爆款视频,订阅者也会越来越多,这个门槛并没有多难以跨过。

说干就干

接下来就是写代码了。甚至不想看代码思路的直接跳过此部分。

说一下,我搬运视频的代码思路。这种做法,毕竟是侵权的。

首先,通过node爬虫,模拟浏览器的访问,获取到B站某个up主的所有视频,接下来通过node的child_process来执行you-get 的命令行命令,使得这个up主的视频能够批量地被下载下来。当然,我并不想竭泽而渔,也觉得老视频没有多少意义,所以每个up主,我最多只爬去最新的100个视频。

接下来,我会将视频通过YouTube接口的方式,批量地上传到指定的频道上面去。

后来,随着我的开发,细节也越来越完善。

  • 支持持续订阅某个up主,思路也无非是隔一段时间,爬去最近这段时间内他的上传的视频。

  • 支持将同一个up主的视频放到同一个播放列表里,之所以这样做,一则是因为视频更方便管理和浏览。二则是害怕某天突然被告知,我偷了他的视频,这样删除的时候,也只需要找这个播放列表里的视频删除可以了。

  • 原有的视频下载和上传的脚本是隔离的,导致做不到自动化。后来,这个工作实现了自动化。只需要在一开始将某个up主进行订阅,之后,下载,上传,长期订阅和上传这个up主的视频都能够自动完成。

坦白讲,写这个代码写得还挺开心,很大一部分因为是以前很少用到js里异步控制的async await,但是这次代码写起来,用了很多,也用得很爽吧。另外以前也很少写node的后台服务,最多也就是写两个爬虫,这次却借助这个想法,顺便学了一波node服务的部署和运维(为了做这件事情,也为了科学上网,我当时还买了一台阿里云vps,后来因为科学上网,服务挂了。我又换到了搬瓦工,这才一切稳定)。当然,有人可能会觉得我这种行为侵权,这个话题,我一会儿再聊。

过程

我便改进代码,边上传YouTube视频。由于最近六学火热,我最初选取的是B站上某个知名六学家作为我的实验样本。后来,又在B站上找到了几个体育类,搞笑类,影视类的up主,来上传他们的视频。我也在这个过程中慢慢发现,YouTube每个频道每天只允许上传最多100个视频。所以我这种bot也不可能像个疯狗一样毫无节制地去上传视频。这也意味着,我要选取尽量符合观众期望的视频来上传。因而,有很多天,我经常会打开YouTube的后台,去看最新的浏览量分析,来看哪些视频更受欢迎。去看最新的搜索词排行,来看到底用户喜欢看哪些视频,以决定我接下来要选取哪个up主作为我下一个订阅者。

我的确从YouTube的后台看到,随着我的视频越来越多,代码功能越来越完备,各项数据也越来越好了。虽然一开始没有爆款的视频,但是凭借着人海战术,还是很快就达到了每天10小时的播放量,接下来,就只差多几个爆款,来增长一波粉丝了。

但是,但是,但是

版权是个大问题

搬运视频,不可避免地会碰触到版权这件事。我最初对这件事,做个些简单的调查。当时考虑电视剧电影一定会是版权的高压线,而且在YouTube上搬运这类视频的人也不在少数,我并不想去趟这类混水。所以,我也是将搬运的内容,选择为了短视频,因为的确看到YouTube上面有大量来自国内视频网站的短视频,想到既然别人可以,我为什么不可以呢,于是也就选择了这个领域。

但是,从搬运的前几天起,邮箱里就开始接收来自官方的版权警告。比如下面两个截图,就是我随意从邮箱里YouTube发来的邮件里截取到的。

版权警告

版权警告

看过越来越多这种版权警告,发现YouTube很智能。这种版权警告,应该完全都是由机器来判断和筛选,因为有些刚刚上传的视频甚至都没有播放量。不过我并没有对这种版权警告有所警惕,只是觉得,既然YouTube很智能,能够帮我查找到哪些视频是侵权的,哪些不是,那我只需要把一些漏网之鱼搬运上去,也就很好了。

但是随着我的频道观看量和订阅量越来越多,也收到了下面这种邮件。
版权警告

版权警告

这个视频,其实是这个视频频道刚刚创建之初上传的,YouTube大概反应了一段时间,才把这个视频封锁了,而且还给了我严厉的警告。这是第二次copyright strike,当第三次收到这种邮件之后,我那个视频频道就被封锁了,而且很快,我谷歌帐号关联的另外一个我平时看视频的频道,也被封锁了。

我发了两封邮件过去,想要解释我的动机。我解释了我是个程序员,只是为了学习知识,不过,谷歌没有饶过我,截至到现在(2019年1月13日),我的YouTube帐号仍然处于封锁状态。

申诉无效

我侵犯的谁的版权

接下来解释一下这个问题, 我侵犯的谁的版权?

很多读者,可能从一开始就对我这种行为表示不屑,毕竟这涉及到侵犯版权。但我还是要辩解两句,首先,从上面我的侵权截图也可以看出来,我搬运的视频几乎都是影视片段和搞笑类的小视频,之所以侵权,侵的也是那些影视片段原作者的权益。再说明白点,虽然我是从B站搬运的视频,但YouTube并不认为我对他们有侵权行为,甚至,即便是那些B站up主把那些他们剪好的小视频发布到YouTube上,他们也依然会侵权。就像是前两年很火的谷阿莫那样,他的确付出了很多时间精力,但实际上他用到的素材也是涉及侵权的,在YouTube一样行不通。当然我也不否认,我的侵权行为,毕竟你从贼的手里偷到了路人的钱包,难道你就是清白的吗?

我还记得我那个频道刚刚建立十几天的时候,因为搬运了某个在B站上有1万+粉丝数量的up主的小视频,因为我的播放列表名字就是这个up主的名字,因而在YouTube上搜索很容易找到。那个up主在我搬运他的最新的一个视频下面留言,说他就是B站上的谁谁谁。也只是说了这么一句,我也并没有回复他,我又能说什么呢?

教训

关于盗版这件事情,我自己的态度一直模凌两可。我不认为这件事完全没有正面价值,不然,我也不会去做这样的事情。在写此文的时候,我甚至会想到13年去世的亚伦·斯沃茨 ,他是个反对禁止网络盗版法案的运动的人,很大程度上,我是欣赏这种黑客精神的。所以,我对版权上的问题,其实是没有多少反思的。因为如果真要较真的话,我搬运的那些B站视频up主们也毫无例外都有问题。

当然我也要说,是谷歌为了版权方的利益,对YouTube进行了控制,让这个社区更能够良性发展,他也并没有错。只是,在谷歌YouTube这张大的鱼网之下,有的成为了漏网之鱼,因此你还是能够在YouTube看到大量的国内视频网站上的盗版视频。有的成为了网中之鱼,成为了牺牲品。这其中包括了我自己,也包括那个曾经依靠视频广告分成获利颇丰的“B站授权搬运工”。我也许可以做的更好,比如只搬运搞笑类的视频,不搬运视频剪辑类的视频,这都可以让我的视频频道活得更久一些。但是,我并不想那么做了,它终究有一种做贼的感觉。

1月份

工作照常做,但是心里已经有点不踏实了,毕竟马上就要过年了,所以其实工作效率并没有多高。不过似乎大家也都没有强到哪里去,如此而已。1月份经历了人生中第一次年会,之所以说是第一次,是因为虽然从15年实习开始就算是上班了,但是却一直没有参加过年会。直到这个时候,才终于参加了第一次年会。然而年会也并没有多有趣,演出的节目并没有很有趣,大概也是演给70后居多的boss们看的。演出无聊,也没有拿到奖品,当然觉得不好玩了。不过,这次年会保留下来两张照片,是与其他同事的合影。其中有两位同事后来离职了,于是这两张照片也成为回忆,让我想起了之前与那两位同事一起工作的那段经历。为了年会,还买了一双皮鞋,一件衬衫,都只穿了那么一次,就放到柜子里了。总之,我对年会的印象并不好,甚至不想参加今年年会。说到这,你可以已经明白了,我还在这家单位接着干呢。

2月份

2月赶上过年,工作中断了几天,但好在工作并不紧张,所以也就还好。

过年,这件事,越来越驾轻就熟了,以前可能会惶恐于走亲戚串门子,不知道怎么称呼那些叔叔阿姨,哥哥姐姐,也不知道怎么跟他们打交道。现在却觉得这也算是一件沟通感情的好事情了。套路已然熟悉,接下来就是表演了。于是表演地也还不错,至少是及格了。也没有像那些年那样,跟我的老舅在酒桌上翻脸,跟我爸爸的老舅在酒桌上耍脸子。其实我到现在我也不明白,为什么那两年,我都跟这些舅们过不去。我并不想说那个时候年轻,也不想说这个时候成熟。也许,大家只是对对分多了两分了解。

过年如旧,但参加了1次高中同学聚会,这聚会也让我喜欢不上来。打算今年如果继续举办的话,不再去了。为什么我不喜欢呢?人们好多年没见了,于是开始聊天,聊到的内容非常肤浅,“在哪里工作”,“上什么班”,“有没有对象”,我甚至觉得今年的聚会,聊天的套路依然如此。其实我也能够理解,彼此之间并不熟悉的人们,能聊的内容本来也并不多,为了化解尴尬,聊一些最浅显的话题当然并不是错。但是,前面说的是理性,而感性的我,并不喜欢如此,我选择逃避。

想起来,几个月前,看到的推特上有人表态,说他明确拒绝了同学的邀请,去参加同学聚会。我当然做不到直接拒绝,可能哪天找个借口就不去了吧。

2月份写过几篇博文,大多是谈吃的,比如就是喜欢饺子 。这当然还是因为过年的缘故,会很容易让人想起来聚餐,饮食之类话题。另外,还有一个原因,也是想找一些由头,来回忆一些可能即将就会忘记的事情。

3月份

三月份,真正值得说的,大概两件事。

第一件事,去了一次香山。这个时候,天气还略显寒冷,爬香山的人也并不是很多。而偏偏就是这个时候的香山,让我第一次去,就喜欢上了,之后我又陆续去过两次。

我第一次去过香山以后,曾经想,以后要每一个月都争取去爬一次山,后来却发现,之后,也不过只又去了两次而已。上一次去的时候,正是十一国庆节期间,我以为游客们会去到故宫长城这些热门景点,香山的游客应该不会很多。但我却没有想到,去到那里,已经人流如织了。爬过两次山之后,这一次爬很轻松了,以至于,我对香山有了疲倦。

第二件事,由于Y同学在固安出差,因此我们得以相聚两次。当我写下这些的时候,也觉得时间很快: 这件事似乎就是昨天才刚刚发生。那两天,我带他去了还在下着雪的北京故宫,去了五棵松附近吃了顿潮汕火锅,去南锣鼓巷附近吃了顿烤鸭。我们当然不光是玩,也聊过去这两年的变化,成长,经历,也怀有对未来的憧憬。他说他以后不想在深圳长期工作,想要回到西北发展,去西安工作。我说了我的生活,我的遭遇。拭目以待吧,相信明天会更好。

4月份

同样是两件事

第一件事,去了工体看了场国安对阵恒大的比赛。这是我第一次在现场看正式比赛,因为是第一次,很新鲜,很有趣,加上比赛过程一波三折,我这次看球的体验实际上非常好了。以至于在接下来的几个月里,我又来到工体看过两场球。不过略显遗憾的是,我三次来到工体,看了三场平局。即便是第三次,我以为稳稳地能够拿下的长春亚泰,最后也跟国安踢成了平局。说起来,也很有意思,在现场看过几次足球之后,再去现场看篮球,即便都是国内最高水准的比赛(足球是中超,篮球是CBA),也会觉得现场看篮球相比足球来说太无聊了 。我是喜欢现场几万球迷一起助威的声势的,喜欢大喊牛逼,大喊口号的,当然喊傻x就是另外一回事了。关于看球,也许以后还会单独开辟一篇单独来讲,总之,我是享受的吧。

第二件事,去了北京草莓。后来写了我的2018北京草莓全纪录 ,应该是非常全面了,没有什么可再补充了。

5月份

到五月份为止,入职一周年,于是开始考虑是加薪还是辞职找工作的问题。如果找工作当然是要总结知识经验喽,又赶上那段时间自己也的确有一些知识上的积累想要分享,于是写了很多篇技术博文。其中大部分是关于React的,后来我就把他们结集,放到简书的一个文集React分享 下面了。不过话说回来,后来顺利地加了薪,工作量也没有增加,我也就继续在这家单位工作了。大概由于动力不足,后来也就很少再写技术博文。

第二件事,买个手机,被骗了。后来,我把这件事,写成了博文,又有万余字。

我遭遇的北京实体店卖手机骗局,希望你们不会遇到
我现在觉得, 走在街上,路边都是黑店!
在被侮辱,被威胁之后,我跟骗子达成了和解

6月份

6月的主题是世界杯了。至今,如果你让我自己想,我也会觉得六七月份是我整个2018年最愉快的两个月,工作上没有很多烦心事,也不用加班,下班后看两场球,评评球,看看足球相关的八卦,连朋友圈里面,那个时候讨论世界杯也成为了政治正确的主流,觉得好极了。优酷那档《这就是世界波》节目也很不错,它陪我度过了几乎一整个世界杯期间的夜晚,我也开始认为王自健是一个很不错的主持人,而越来越看不上李诞了。

7月份

7月份两件事

第一件事,当时大概有些无聊,关注了BEJ48的ddd,于是只要B站上那个应援会up主一更新视频,我就会去瞅一瞅看一看。但是,隔着屏幕的美女,看得久了,也是会腻的,于是新鲜感没过多久也就散去。慢慢地,我又没有兴趣了,对那个应援会up主取消了关注,相应地,ddd也很少关注了。

第二件事,有了一次相亲。相亲始于互相成为微信好友,终止于见面。相亲对象是一个做财务的女生,比我还大两岁,但我本着“学习”“的态度去了。见面之前,还曾经在微信上热烈地聊过三五天,见面之后,其实对方也没有明确表态不同意继续交往,我们就也继续聊着。但感情就以肉眼可见的速度,疏远了。我继续拖延着,这样父母就不会让我再去找下一个,对方也继续拖延着,经常是我发过去一条微信,对方隔一个小时才回复,而且各种理由,今天说是去健身房了,明天又说是去图书馆了,显得自己很忙的样子,只是太忙了没有时间理我。于是我也只好只是说句,那你忙吧,明天再聊。想要约来第二次见面,就迟迟约不到。终于在加了微信一个月之后,对方提出了我们不适合。其实我又怎么会不知道呢,不过就是一直拖延着罢了。我没有问她哪里看不上我,觉得显得有些扯皮和无聊。却矫情地发了一首徐志摩的诗,发在朋友圈,只给她一个人看。现在想想,倒也觉得有几分可笑了。

第一件事和第二件事有什么联系呢?关注的ddd,是隔着屏幕的人。然而觉得新鲜的时候,也曾经认为,还是屏幕里的人好,现实中的妹子怎么这么难猜呢。那个时候,我也像是一个迷恋二次元的肥宅,不愿意去做现充。可当我的那股新鲜劲过去之后,现实中的妹子也选择放弃了我。

8月份

八月,因为”谈判破裂“,心情不好,于是去了一趟泰山。关于这次旅行,我写了两万多字,凑了四篇博文。下面是第一篇

济南是个小姑娘,泰安才是山东大汉(1)

该说的,我也都说了,就不再多说了。

另外,继续追《权利的游戏》,去年的夏天看了这部剧的前两季,今年的夏天,看了这部剧的三四五季。我一时也归纳不出个原因,反正总是很喜欢在春夏看美剧,在秋冬看日剧。顺便一提,今年的春夏,除了《权利的游戏》之外,还看了《摩登家庭》,《硅谷》,都是追了好几年的,已经成为习惯的剧,本来还想看《毒枭》的,但是看了半集就看不下去了,也许时机没有到吧。这个冬天,看了今年唯一一部日剧,《我们无法成为野兽》,剧当然是好剧,只是看到第六集之后,因为其他变故,看剧的过程又中断了,反倒不想再继续追下去了。但,应该还是会找个时间补上剩下的剧集的。

9月份

工作上,一直想在项目中引入的postcss autoprefix终于被我引入进来的。相应地,也学到了好多新的知识,不必详细说了,之前也写过博文记录这件事: 在webpack项目中配置postcss,实现autoprefix

生活上,终于跟父母进行了一次旅行。虽然去的地方也不远,只是在北京,只是去了天安门,故宫,北海公园而已。但整件事现在回想起来,还是挺有意思的,我会想起很多小事,比如我妈不敢地铁站的电动邪梯,于是我爸只好搀扶着我妈走楼梯,而我以前甚至似乎从来都不知道我妈是不敢坐电梯的;又比如我爸我妈不认路,全程我来当导游,一下有一种当家做主的感觉。再比如我那个不听话的小表弟,到了天安门哭着喊着要他妈给他买这买那,去了故宫又觉得这里不好玩,嚷嚷着赶紧走;我也会想起,我给同行的父母和老姨一家办了5张北京公交卡,我们一行人在北京从来没有分离过,结果退卡的时候,其中4张卡退卡金额一致,只有一张卡多了几块钱,所以一定是有人逃了票,又会是谁呢?

这个旅行的所有景点,在此之前,我都去过,有的甚至去过不止一次。但是跟父母去的话,又有新的好玩的点。从这个角度来说,这也算是旅行的意义了。

其实整个夏天,母亲都在念叨着,谁谁谁又去哪哪哪玩儿去了。后来老爸大概是被念叨烦了,才带她来了北京。他们本来还想来我住处参观一番,然而我的住处真的是寒舍,委婉地拒绝了他们。父亲本来想要去八达岭长城的,但是从南城往北城走,这实在是太远了,后来也并没有去。而我,过了差不多两个月之后,自己一个人跑到八达岭逛了一圈,跟想象相差无几,没有多少惊喜。也许到了2019年的中秋节,能够跟父母一起去八达岭吧,到那个时候,我都能够给他们当导游了。

10月份

那位“前女友”说过她爱玩网易某款吃鸡游戏,而当时我的手机根本带不动吃鸡游戏。后来我换了个手机,终于能够玩吃鸡了,那位也不在了。我又将那款吃鸡手游卸载了,直到后来因为表弟结婚,去他家吃包子,看见他的电脑上有网易那款吃鸡端游,看他们玩觉得蛮有意思,于是觉得既然玩不了端游,那就玩手游吧,就又重新把游戏装回来了。不过这次,我不光装回了网易那款吃鸡手游,还装了腾讯那款吃鸡手游。结果两相对比,发现腾讯那款手游更加出色一些,对新手也很友好。于是几乎每天都要玩上一两把。我的段位也一路从青铜打到了黄金2。

有那么一段时间,很痴迷。于是也跟很多玩家一样,去看一些打游戏的直播视频,去知乎上看那些“刺激战场的技巧有哪些”之类的帖子。当然,这样一段时期,很快就又过去了,现在又觉得游戏不过是套路罢了。

第二件事,我买了一个魔方,经常会在空闲时候,学习学习魔方的公式。后来经过一番学习,终于拼出了第一个完整的魔方。但是最后一个公式,我没有记住。后来,我每次也只是把前两层都拼好。然后,就心满意足地打乱魔方,重新拼下一个了。

有那么两个周末,我一个人背着包,包里放着kindle和魔方,去到住处附近的一个公园,坐在一个角落里。我会安静地看会书,看书觉得没有意思了呢,就玩会儿魔方。魔方觉得没意思了呢,又打开手机玩会吃鸡手游。总是有事情做,总是很悠闲。我很难说这种状态是好是坏,但对我都是经历,而这种经历,今年经历了,明年就未必了

11月份

可能还是孤独吧,11月份,竟然又开始在B站上看聊天直播,开始我只是看吃鸡的直播,有一个叫做大忽悠的主播觉得很有意思,看了三五天之后,偶然间又看到一个聊天主播,发现她人气并不高,只有几百人关注,而长相甜美可爱,简直就像是被遗忘的丑小鸭,于是赶紧关注了。后来,还专门充值了直播的虚拟货币,虽然也没有充值多少钱,但是要知道,我此前从未给主播冲过钱的。其实,这样一个小主播,直播间里面同一时间段内在说话的,也不超过五个访客,即便是你不充值,你要是在弹幕里跟她聊上两句,他也是会搭理你的。当然,主播们当然不是靠空气活着的,她们当然是需要打赏的,于是你每次打赏点小钱,她们也是会对着手机屏幕,喊出你的昵称,说上两声谢谢,比一个心,嘬一下嘴。我后来了解到,这个主播,当时还是一个学民乐的大三学生,家在呼和浩特,在包头上学,我关注她的时候,她才开播没有多久,她没有多少才艺,偶尔会唱两首歌,唱的歌也不是多好听,有一个跟老婆关系不是很融洽的大佬,几乎每天都会到她的直播间逛一逛,说说生活,有时候,我甚至觉得在那个直播间呆了近一个月,我知道那个大佬的故事,要比知道那个主播的情况还要多。大佬好像也只是想找一个对象倾诉,于是直播间里,大佬在屏幕上喋喋不休地说着,主播和几个听众看着。我想起这些事,总会觉得每个人都不容易,逃避也并不可耻。我从十月中旬开始看这位主播的直播,看到11月中旬,看着他关注人数基本不动,直播间人数一两百就算是高的,我知道她快要离开了。果然,毫无预告地离开了。

第二件事,去看了改革开放四十周年的展览,也顺带着看了一个《大唐风华》的展览。其实最初,我是在某公众号上,看到小编有推荐《大唐风华》这个展览,可正当我准备过去看的时候,却听到一个新闻,国博闭馆改造。等又过了近两个月之后,再次得到国博的消息,已经是说,国家领导人们集体去参观了改革开放四十周年的展览。

12月

一件事是相亲,因为还没有结束,所以也不想先说太多。另外一件事则是工作上的变动,也不想说太多。除了这两件事之外,最让人意外的,应该还是奶奶生病这回事了。

某天突然接到老妈的电话,告诉我,第二天要去县医院看一看,因为奶奶住院了。我并没有在电话里问起她的病情,怕自己如果知道了一个坏的结果,会失眠,而老妈也没有直接告诉我奶奶的病情。然而,当天晚上,我虽然很早就睡了,但还是做了不好的梦,第二天早上起来去吃早餐的时候,更是往最坏的方向想了好多回,心情并不舒畅,情绪也一直在压抑。等到坐了一小时火车,打了个的来到医院,见到在病床上处于昏迷状态的奶奶的时候,突然发现,自己前一天夜里的梦,以及当天早上所有最坏的结果都有可能出现。旁边的亲戚跟我讲,喊喊你的奶奶吧,我于是坐在床边喊了两声,积压的情绪得到了释放,哭了出来,哭了好一会。这次哭,后来也让我想起,小的时候,一直跟着奶奶的我,突然要离开奶奶,去爸妈那边生活,同样是哇哇地哭了一场。

此后一天半的时间,我都在医院里陪护,索性奶奶也渐渐好转过来,从沉睡中苏醒过来。她醒来的时候,我就在旁边,竟然也把我吓了一跳。她拼命用力去闹腾,试图摆脱呼吸的口罩,插在身上的输液用的针管。我于是赶紧去叫医生,医生回答病人已经苏醒了,这是好症状。我没有特别的欢喜,因为此前,已经从医生的口中得知奶奶并无生命危险,只是有可能会有并发症。可能的并发症包括脑血栓,肺炎,糖尿病等,但后来奶奶苏醒以后,情况的确越来越好,我于是也安心地在周日的下午离开了医院。

后来隔了几天,听老爸说奶奶已经出院了,又过了几天,我回家去看望。说话已经没有问题,但活动还是有些费力。回家的当天,我在奶奶这屋待了很久,想要做一个补偿。但是,第二天,眼见着奶奶的确好转了,而我也的确还有更有意思的事情要做,也就又去做别的了。

亲情,大概是永远都可以聊的一个话题。

结语

整个2018年还是很平淡的,一切行走的轨迹都非常地自然,也几乎没有做什么选择,相比2016年的自己,简直可以说是非常平淡了。偶尔有两三件事情,给生活造成“波澜”,其实也并不是自己主动去制造的,而是被动接受的。

我希望2019年做出更多主动的改变。

我希望2019年能够增加更多的睡后薪水。

当然,我还有很多美好的希望,这里不展开了。

缘起

最近的工作,接触了很多云计算相关的皮毛知识,虽然是皮毛,但也毕竟让我对此多了几分认识。于是就再整理整理思路,写下来吧。

发展历史

曾经有段时间,哪怕是一点都不了解互联网的人,也能够经常听到“云计算”,“大数据”这样的词汇,他们的热门程度,类似于较早之前的“AI”,”VR”,“人工智能”。不过作为一个搞计算机的,“AI”和“VR”其实我一直也从没有能够从技术上应用过他们,以至于一直觉得十分遥远。但”云计算“却随着了解地越来越多,觉得不再那么高山仰止了。

亚马逊应该是最早布局和应用云计算的。他从02年正式发布了AWS,之后这个业务也给他带来了很高的回报,股价一路飙升。后来微软和谷歌也紧随其后,开始布局云计算。国内云计算的发展稍晚了一下,但是也是很牛的。前一段时间,有篇《阿里云的这群疯子》的文章,讲了阿里云从初创到繁荣的历史转变,一时间也刷爆了朋友圈。阿里云诞生自09年,腾讯云诞生自10年,两个巨头也是你争我赶,虽然在我们外人看来已经很牛了吧,但毕竟亚马逊云计算发展的比较早,还是亚马逊的AWS更加给力一点。

发展历史终究只是浮云,对于开发者来说重要的是能够实际应用这些云计算技术,并为自己创造价值。

saas, paas, laas

关于这三者其实网上已经有很多人在科普了,比如我文章下面参考链接里的阮一峰大大。但是我还是想将我的理解表达出来,给自己一个回顾。

saas

按照我的理解,这三种服务面向的群体实际上并不是重合的。

什么是基础设施

我先说说我自己觉得最好理解的,laas,Infrastructure-as-a-service, 也就是基础设施即是服务。那么问题来了,什么是基础设施呢,我们试想假如你已经有了一个好点子,要建立一个面向全国范围内的电商网站,这个网站一旦投入使用,立刻就会成为行业前十,但是你没有太多的投资。这个时候你需要自己在万网或者狗爹(goaddy)买一个域名,如果是面向国内用户的站点,你要对你的域名进行备案,这些过程都还算简单。

接下来你需要买一台服务器,如果你自己有一些特别的需求,你还可以定制你的服务器。有了服务器,有了IP,你需要你所购买的域名能够指向你的IP,你又需要DNS服务

你要给服务器装上系统,装上合适的数据库,之后你的程序能够在你的服务器上面跑起来了。但是即便如此,可能还是会有很多问题,比如为了容灾的需要,你可能有不止一台服务器,比如3台,但是你只能把这3台服务器放在一起,于是面临的第一个问题,那就是你可能只能保证你服务器所在地的访问速度,而其他区域则保证不了访问速度。但是你只是个小的创业者,根本没有能力在全国范围内,搭建多个服务器来满足全国高效的需要。于是你陷入了苦恼,感慨原来创业并不只是”就缺一个程序员了”。

然后你姑且容忍了没有CDN的问题,你考虑到你的站点虽然面向全国,但是一半的用户来自北京,于是你把那几台服务器放到了北京的联通机房。除此之外,你还需要大量时间来做运维,来支持你的服务器的正常运转。我曾经见过公司从服务器选型到网站服务安全稳定运行的整个过程,的确是需要大量的时间成本以及很高的知识储备的。

解决方案

有了需求,接下来就要有人来做了。就拿亚马逊来说,他很早就做成了全美最大的电子商务平台,而他的站点规模,决定了他一定要使用上大规模的服务器,以及应用上CDN等技术。这个时候,亚马逊说,我想把我的空余的服务器租出去,注意,我不是像dell公司那样把服务器买给你再做做售后服务工作那么简单,我是把服务器租给你,如果你愿意的话,我还可以给你一整套的解决方案。也就是说,你服务器上安装的数据库也可以从我这里来租,数据库的配置我都可以预先给你配置好,保证是一个最佳实践,你无须再为此付出大量时间精力。

我也可以把我自己成熟的CDN技术分享给你,让你的网站也能够无论在哪里,都能够有一个很快速的访问速度。如果你的网站上有大量的多媒体资源(图片,视频等等),你也可以把这些多媒体资源存放在我这里。我保证,你的多媒体资源的访问速度,与我们亚马逊自己的多媒体资源访问速度相当。凡此种种,还有很多。由此,其实我们不难看出来,当今互联网以及计算机发展的一大潮流,就是从原来的购买转变为后来的租用,类似的情况,单就美国来说,还有租用影片的netflix, 以及微软的office虽然也在卖,但也有更多的用户选择了office365。

租用的好处与坏处

让我们暂时岔开一下话题,聊一聊究竟这种租用的方式能够带来哪些好处呢?为了不岔开得太远,我们还是以云计算服务为例,我们来算一笔帐,假设一家小公司初创,为了自己的网站能够安全稳定的上线,他需要服务器以及上面提到的一套解决方案,他为此要付出很大的成本,而又由于是初创企业,自己无法准确估计自己的需求,可能最后买到了合适的服务器,以及应用上了一套解决方案,一旦业务突飞猛进,他又要对此进行升级,相应地,又要花费大量的时间人力成本。而租用的方式,则是能够在一定规模内,实现付出最小,利益最大。当然,我也说到了是一定规模内,我们看到公司一旦达到了一定规模,租用的成本要远远大于直接买的方式,因此我们也看到像dropbox就离放弃了AWS转而自建基础设施和网路。

都有哪些基础设施

为了方便,我们这次把视线瞄准国内,看看阿里云是怎么做的。下图是阿里云的产品一览

阿里云产品一览

我们也可以看到,就我们刚才所说的例子来看,用到了弹性计算(云服务器)、存储服务(oss等)、CDN技术以及数据库这些基础设施。阿里云将这些服务拆分成一个个的产品进行出售,我们可以根据自己的需要进行量身定制。

感兴趣的同学,可以通过参考链接,进入阿里云的观望去瞅一瞅。

laas 的好处

所以回过头来再来看云计算中的laas,能够看到他是有很大的优势的。主要表现在:

  • 租用的方式能够让付出最少,收益最大。前面已经提到过
  • 快速完成基础设施的应用,专注业务核心
  • 运维成本降到最低

saas

软件即服务

这个其实按照我的理解,并没有laas复杂。想对于laas而言,我认为主要区别在于:

对于购买laas的使用者,他对于在基础设施之上所搭建起来的应用程序有非常高度的掌控权,毕竟laas只是提供了基础设施的服务,真正的应用程序,还是需要自己来开发。但是saas则并非如此,他聚焦在了更高的层次上,直接为你提供了应用程序,因此你对应用程序本身的掌控力非常差,如果让我举一个例子的话,我会说当今很流行的微商城服务,比如微盟,你通过购买他的这个微商城服务,然后再在自己的微信公众号后台进行一些简单的配置,可以快速地建立起一个微商城来,如果你有需要,当然也可以快速完成微信小程序的创建。相比laas而言, 这种方式更加高效了。只是,会更加受制于人。他有点像是曾经经常被讨论的自有商城和天猫店铺的区别,在这里就不再多做阐述了。

paas

paas,也就是“平台即服务”,我接触的比较少,也就简单说一下我的理解。我自己用过的是,2013年左右的GAE,当时通过一些简单的配置,完成了一个应用,也就是goagent的开发(相信经历过那个年代科学上网的朋友,对此定会不陌生)。为了写这篇文,我也找到了一篇2014年其他人的博文,介绍goagent的原理,感兴趣的朋友,可以通过参考链接过去看看。我用goagent 的时候,还对云计算这一块毫无所知,完全是根据网上的傻瓜教程来搭建服务的。现在会过头来看,就像是阮一峰博文中所述的那样,paas是正好介于laas与saas之间的一种服务

阮一峰博文图片

还就goagent所使用的GAE为例,他并不只是给你基础设施,他也给了你一个应用程序所必须的环境(在这里是python环境),他也不同于saas,他没有给你一个完整的软件服务,让你直接就能够使用,他还是要让你自己来写程序,来实现某些功能的。其实写到这里,我又想起了当年玩贴吧的时候,也有朋友基于百度的BAE(与GAE类似),实现了百度贴吧的自动签订功能,当时依然觉得有趣,也仍不知所言。只是照猫画虎,但这照猫画虎,却让我对计算机有了更深的兴趣。

综述

其实说起来,我最早用的云服务是个人服务,百度云。后来,又用了很多其他的个人云服务,我早在4年多以前,我就说过要构建一个个人云端信息库。我就表态过,自己对于“云”这件事很乐观。我想既然2c是这样的,2b也应该不无例外。包括前面我的许多分析,也都表明,我自己是支持云计算的,即便从社会分工的角度来说,让专业的人做专业的事,这件事,从来也没有错。

标题说的个人工作实践指所见到的公司从服务器选型到网站服务安全稳定运行的整个过程,准备上云计算的过程,使用微盟微商城的体验,使用goagent的体验等等。

参考链接

前面

作为一面全栈工程师(偏重前端),对待老大交代下来的后端任务也是需要认真完成的。前段时间,有个工作,要通过淘宝的OAUTH进行授权,进而获取到access_token,通过access_token来作为授权码,进行所有需要登录权限的API访问,这些API 包括但不限于用户,商品,交易,评价,物流等API.

过程

在这里也无须去科普OAUTH2.0协议到底是什么了,感兴趣的可以自己去查wiki.

我来说的仍然是我自己的理解,所以OAUTH到底做了什么呢?它是一直验证机制,这个机制实现了两步验证,仍然以淘宝API获取access_token为例,淘宝认为开发者访问用户的信息,是以应用为单位的,每一个应用需要一个app_id,app_secret,我们是先要通过app_id 来置换到一个叫做code的字段,这个字段只是作为一个过渡,我们能够通过code值,再调取一个api,才能够最终获取到access_token.

拿实际例子来说,

、授权操作步骤

    此处以正式环境获取acccess_token为例说明,如果是沙箱环境测试,需将请求入口地址等相关数据换成沙箱对应入口地址,操作流程则同正式环境一致。
    实际进行授权操作时,测试的数据 client_id、client_secret、redirect_uri 均需要根据自己创建的应用实际数据给予替换,不能拿示例中给出的值直接进行测试,以免影响实际测试效果。下图为Server-side flow 授权方式流程图,以下按流程图逐步说明。
授权步骤

1)拼接授权url
拼接用户授权需访问url ,示例及参数说明如下:
https://oauth.taobao.com/authorize?response_type=code&client_id=23075594&redirect_uri=http://www.oauth.net/2/&state=1212&view=web

参数说明
名称
client_id
response_type
redirect_uri
state
view

2)引导用户登录授权
引导用户通过浏览器访问以上授权url,将弹出如下登录页面。用户输入账号、密码点“登录”按钮,即可进入授权页面。
授权

3)获取code
上图页面,若用户点“授权”按钮后,TOP会将授权码code 返回到了回调地址上,应用可以获取并使用该code去换取access_token;
若用户未点授权而是点了“取消”按钮,则返回如下结果,其中error为错误码,error_description为错误描述。分别如下图所示:错误

4)换取access_token

方式1(推荐):

通过taobao.top.auth.token.create api接口获取access_token(授权令牌)。api服务地址参考http://open.taobao.com/docs/doc.htm?docType=1&articleId=101617&treeId=1

最后

说起来,我最早使用OAUTH进行登录或者授权操作,还是早些年在用微博的时候,如果OAUTH的应用已经非常广泛了,了解它对我们,无论前端开发还是后端开发都有很多好处.

参考链接

http://open.taobao.com/doc.htm?docId=102635&docType=1

http://open.taobao.com/api.htm?docId=285&docType=2

需求

##1希望能够让开发者写代码更轻松

以往没有引入postcss autoprefixer之前,我们为了css相关特性能够在各个浏览器上的兼容,引入了scss,利用它的mixins来实现prefix。但是每一个mixin 都有一个自己的语法。

比如以往,如果我们我们想要写一条

div {
    display: flex;
    align-items: center;
    justify-content: center;
}

为了各个相对早期浏览器的prefix,我们需要mixin,然后这样写

import 'common/flexbox';

div {
    @include flexbox;
    @flex(1);
    @justify-content(center);
}

然后按照这样的约定,来加上需要的prefix。

这当然是一个方法,但是对于开发者来讲,实际上增加了一些学习成本,而且相当于将标准放到了一边,去使用另外一套标准。

我们之前的mixin,由于已经有好几年的历史。当时为了兼容市面上那些还有很大市场份额的浏览器,prefix写的很全。比如下面这个mixin flexbox到代码:

@mixin flexbox {
    display: -webkit-box;
    display: -webkit-flex;
    display: -moz-flex;
    display: -ms-flexbox;
    display: flex;
}

为了写这篇博客,我又确认了一下,那个mixin flexbox的开源库来自于2013年,距离现在已经五年了,五年的时间,淘汰了很多过时的浏览器,五年前可能需要兼容到ie8,现在移动端开发甚至完全可以无需理睬IE的存在,所以这样的一套prefix就显得有些过时了。那位说了,我更新我的mixin不就好了,比如在2018年做移动端开发,也许我只需要将上面的代码改写成:

@mixin flexbox {
    display: -webkit-flex;
    display: flex;
}

但是我们也许需要想地更远一些,也许再过两年,我们甚至不需要对-webkit-的支持了。那时候,难道我们又要改一遍这个mixin吗?

postcss解决了这样一个痛点,可以直接书写标准的css(现在是css3,但是过几年也可能就是css4了),我们有了这样的工具,再指定一个浏览器兼容表,就能够实现自动prefix,而这相比于sass/scss mixin的方式,维护起来更加简单,应该是一个很好的实践。

2对用户更友好

这当然也是显而易见的,以前我们可能为了兼容更多浏览器,而写更多的prefix,可是随着时间的推移,很多prefix完全并不需要。我们通过postcss来处理,简单明了,缩小了生成的css文件的体积,最后反映到用户那里,会快上一丢丢!

步骤

1 安装postcss-loader ,postcss-preset-env

yarn add -D postcss-loader  postcss-preset-env

安装postcss-preset-env,无需再安装autoprefixer,由于postcss-preset-env已经内置了相关功能。

2 添加.browserlistrc 文件到项目根目录

1% in CN
android >= 4.4
ios >= 8
not ie <= 11

这个需要根据项目的世纪情况来自由选择,我是考虑我们的项目是移动端项目,且确实有用户在用Android4.4 或者ios8,但是再很难看到低于这些版本之下的系统了。

3配置postcss.config.js

module.exports = {
  loader: 'postcss-loader',  
  plugins: {
      'postcss-preset-env': {},
    }
}

4 配置webpack.config.js

{
test: /\.scss$/,
use:[ 
    MiniCssExtractPlugin.loader,
    {
        loader: 'css-loader',
            options: {
                root: 'static',
                minimize: true,
                importLoaders: 1
            }
    },
    'postcss-loader',
    'sass-loader',
]
},
{
test: /\.css$/,
use: [
    MiniCssExtractPlugin.loader,
    {
        loader: 'css-loader',
            options: {
                root: 'static',
                minimize: true,
                importLoaders: 1
            }
    },
    'postcss-loader'
]
}

由于我们的项目中使用了scss,因此需要sass-loader。这里需要注意各个loader的顺序,一开始我的顺序是MiniCssExtractPlugin.loader,css-loader,sass-loader, postcss-loader,结果发现并没有能够autoprefix,原因是如果想让postcss-loader认识并且处理,需要先用sass-loader进行处理,转化成postcss-loader可以认识的代码。

另外需要注意的是,我们这里还在使用css-loader v0.28,目前已经有了v1.0,版本改动很大,以至于我们暂时不能升级。

熟悉前端开发的朋友,应该对 Babel这个项目并不陌生,早在两年多以前,阮一峰大大就已经写过文章《Babel 入门教程》 对他进行了介绍,那个时候,其实Babel应该已经算得上是网红开源前端库了。这两年,Babel其实也一直在发展,我这里想说的是我看到的Babel的成长

babel-preset-env

首先是babel-preset-env,详细的介绍文档在这里:https://www.babeljs.cn/docs/plugins/preset-env/

我也并不会去介绍怎么去用这个,只是想谈谈自己的体会。我记得刚开始使用babel的时候,的确有时候会用上一些stage-2,stage-3 的特性,那时候还为了了解这些所谓的stage去看了ECMASCRIPT 新版本发布的流程,觉得也很有意思。但是虽然有意思,但是配置起来却的确繁琐,有时候,你的确需要为了配置这样一个Babel看好多相关文档,这无疑算是一个痛点了。现在好了,就如同文档里面所提到的:

在没有任何配置选项的情况下,babel-preset-env 与 babel-preset-latest(或者babel-preset-es2015,babel-preset-es2016和babel-preset-es2017一起)的行为完全相同。

babel-polyfill

为什么你这么大

我们都知道,Babel为了存心让我们配置起来更困难,故意将他的功能分拆成了两部分,其一是语法上的转化,这个默认情况下他自会帮我们处理。而另外一部分,就是新API的polyfill,需要我们引入babel-polyfill来完成。当然,我前面说,Babel是存心折腾开发者的确是开玩笑的,毕竟并不是所有的时候我们都需要polyfill的。这有点像是React项目也有两个核心包,ReactReactDOM类似。

    import React from 'react';  
    import ReactDOM from 'react-dom';

babel-polyfill是好东西,能够将新API作用在老的浏览器上,但是我们要注意不要滥用。比如我们随便在百度上搜索一篇文章,讲解如何使用 babel-polyfill的引用和使用,往往都会看到有这样的描述:

module.exports = {entry: ["babel-polyfill", "./app/js"]};

这样做,从功能实现上来看当然是没有错的。但是,如果我们原来的入口是

module.exports = {entry: ["./app/js"]};

那么很容易就能够发现由于入口处增加了babel-polyfill,导致webpack在处理之后,最终的到的入口核心js文件比原来增加了有100kb左右。对于这个问题,也已经有issue,不过很奇怪这位网友把issue提到了babel-loader这个项目下。

然后针对这个问题,有老外网友介绍到了transform-runtime等。但是经过我的测试,这也并不是一个很好的实践。

动态识别

为了polyfill 这件事情,其实是有两种思路可循的。第一个思路是根据浏览器缺失哪些特性来补全哪些特性,这个思路的代表是 polyfill-service,这个项目 。它能够根据浏览器的UA来判断当前浏览器缺失哪些特性,进而进行补强。但是经过我的调研,这个项目在天朝可能还存在水土不服的问题,一个很明显的事实是将安卓微信内置的QQ浏览器X5交给这个库来处理,它会认为当前的浏览器是safari,原因大概是因为UA上有safari这个字段。考虑到我们天朝还有类似微信这样很怪异的UA,我认为并不适合在当前这个时间来对此进行实践。(在这里多说两句,polyfill-service这个库和另外一个知名项目fastclick同属于英国金融时报,而最近我发现fastclick在现代浏览器上也有一些tricky的地方,在他们的issue区有能够找到一些吐槽,然而,这个库已经两年多没有人维护了)

把polyfill-service仍在一边,我们接下来说另一个思路。能不能根据我使用了多少新API,来决定我引入多少polyfill的内容呢?比如我只在我的项目中使用了ES6的set,没有使用其他新API。那么我引入polyfill的时候,能不能只引入set的polyfill呢?答案当然是可以的,这就是babel-7的新特性,没错,由于当前稳定版是babel-6,因此这个特性还处在测试阶段。但是根据我自己的测试,表现很多。

不过有趣的是,虽然还只是Beta版,Babel却将之写入了正式版的文档当中,结果当时我为了测试这个新特性,都已经测试到怀疑人生了。文档戳这里,感兴趣的同学可以去看看。总之,实际上只是对babel增加一项配置。

      "useBuiltIns": "usage"

不过由于涉及到升级Babel,当时我在测试的时候,还是有些不顺的。但是一旦迁移成功,应用上useBuiltIns的这项配置,的确能够让polyfill的体积大幅度减小。

browserlist

前面的动态polyfill,的确可以让webpack生成的js文件体积更小,但是能不能再小一些呢?毕竟我们前端开发的目的就是极致的用户体验,当然可以。这个时候browserlist能够帮到我们。

关于这个话题,网路上相关的文章非常多,我就不再多谈了。

感兴趣的同学,可以看看ant-design项目中使用的browserlist。

https://github.com/ant-design/antd-tools/blob/master/lib/getBabelCommonConfig.js

写在前面

需要说明一点是,关于比较常规的React性能优化,可以看这篇文章:
React 16 加载性能优化指南,我要聊到是一些非常规,与实际开发密切相关的坑,但也正是因为这是我个人遇到的个案,或者并不构成普遍意义。

我前面一篇《React开发实践5–详细说说滚动记忆》 其实跟React关系不大,只是因为做React的项目遇到了问题,也就顺手写下来了。最早遇到这个问题,的确第一反映是React这个库的锅,但是稍稍理性的想一想也知道,React的本质也是JS啊,于是很快就将注意力转移了。

说回头来,上一篇文章说到的那个业务需求,在React-router的官方文档里专门有一节在讲,也就是Scroll Restoration。这里也说到了我在上一篇结尾列出来的那个文档:scrollRestoration

如此说来,我现在资料颇多了。有了前面的铺垫,我想我也就不用再废话了。

CMS 这个坑

首先我引入了一个库react-router-scroll-4,眼尖的朋友看出来了为什么还要带个4呢,这是因为原本的库是react-router-scroll,因为这个库不支持react-router v4,因此有开发者又fork出来一个能够支持react-router v4的分支,我用的也就是这一个了。我看过他的源码,核心思想其实就像上面提到的react-router官方文档所介绍的一样,通过session-storage 来处理。

之后,我开始对商品列表页和APP首页进行改造。商品列表页改造地非常简单,无非是对组件进行一次包裹。但是APP首页的改造却遇到了麻烦。同样是对该组件进行包裹,结果现象却是无法实现滚动记忆。这让我好生无奈。这又是为什么呢?我首先想到了是不是这个react-router-scroll-4支持不够好,毕竟这是一个连github项目库都没有的npm包,顺着这个思路,我去看了这个库的源码,又通过打印log的方式去debug,发现了问题所在:原来这个库对 dangerouslySetInnerHTML这种注入HTML的方式没有进行处理,这里面的关键点在于生命周期,此处我并不想多讲,但最终导致无法实现滚动记忆。而我们之所以要在项目中使用 dangerouslySetInnerHTML,也是因为项目中有用到CMS的模块,历史遗留问题,一时无法解决。

但是你知道的,虽然我后来通过修改这个第三方库react-router-scroll-4,最终实现了首页的滚动记忆,但是前面我也提到了由于使用了CMS系统,造成首页的几乎所有点击都是普通的a标签href的跳转,即这样的交互将原本的React app的优势:独立的路由体系打散了。事实证明,在性能上也造成很大问题。

为了更好地说明问题,举个例子。如果是在同一个路由体系下,从首页/切换到 /abc,这个过程,只会去加载abc路由所需要的资源。但是如果脱离了这个路由体系,而是通过普通的a标签href跳转,进入/abc的时候,相当于又重新进入一遍app,这个时候原本一些bundle的资源又会被重新加载一边,虽然这些资源可能被浏览器缓存了起来,但是缓存好了和根本不需要又是两回事,对不对。

所以,如果能够尽量在进入一个React app之后,尽量不再脱离这个app,也就是一直React-router的方式进行跳转,用我前面文章提到过的说法,就是假跳转,其实是能够有很好的优势的。

原来有一个CMS,我会调用一个接口,返回一个HTML页面内容。如果能够将HTML转成REACT组件,这样是不是更好呢?我后来找到了html-react-parser这个库,其实还有另外一个库,不过另外一个库有一些问题,比如图片的url上面如果有大写字母会转成小写字母,造成图片加载失败。

这个库很好地解决了我的问题。接下来,我做了如下改动,一切都水到渠成。

  • 在第一次进入首页的时候,将首页内容(cms html)放置到React state中进行储存,在下次回到首页时,无须重复调用获取首页内容接口,即可快速获取首页内容,达到尽快地相应

  • 将原本html上面的a标签上的click事件进行劫持,根据情况,将原来的a跳转改为react-router 的history.push(),使之不脱离这个app。由于这里没有脱离app,则当接下来用户点击返回回到首页时,还是会按照react-router的方式去返回,则次过程也不会脱离app了。

这里有个小tips,如果我们获取e.target.href的话,我们会发现即便是我们原本写的a标签是这样的

one product

最后得到的 e.target.href也会自动添加上网址。可是我们知道history.push()只认相对路径,因此e.target.href并不能满足我们的需求,而通过获取a标签上的href属性,也就是getAttribute(‘href’),能够保留a标签上的href属性值,而这个值,正是我们所需要的。

结论:

React应用还是应该干净一些,避免使用 dangerouslySetInnerHTML,使用它会有很多tricky的事情发生,比如在componentDidMount的时候,dom上某个元素还处在undefind的状态,原因很可能是因为这个元素是由 dangerouslySetInnerHTML产生的,而他往dom里面添加节点是在componentDidMount之后。

我还是不很确定,通过history api完成跳转的,滚动记忆的情况。

react-router的假跳转,其核心当然还是利用了history api


我在上一篇 薛定谔的导航栏里面,关于代码的部分,用到了下面的语法:

document.addEventListener('focus', this.IosFocusHandler, true);

有同学可能会问了,为什么我们一般都默认capture:false,怎么这里监听focus事件的时候,就改成了捕获模式呢?

MDN上面对这一块已经说的很清楚了,因为focus事件是不支持冒泡的。但是虽然不能冒泡,但是一个事件三个阶段(捕获,处理中,冒泡),还要前两个阶段呢!所以,当我们改为捕获模式的时候,就能够在捕获阶段,拿到focus事件是否触发的消息了。

顺便提一句,与focus事件很像,focusin事件支持冒泡,所以按道理来说,如果我们想要在document上面监听是否有focus的动作,其实是可以使用focusin事件的,也无需再加capture:true ,但是从caniuse.com 可以知道firefox浏览器,直到17年出的firefox52才终于支持上focusin事件。

所以,就有了这个SO的问答: Focusin and focusout methods not working in firefox

如果想想在firefox浏览器下有好的表现,还是应该用

document.addEventListener('focus', this.IosFocusHandler, true);

focus事件加上capture:true 的写法的。

类似地,blur事件和focusout事件的区别也是冒不冒泡的区别。那么问题来了,为什么focus事件不直接支持冒泡,再去掉一个重复的focusin事件呢?

我只能试图从focus和blur的语义上来理解了。毕竟,规范这东西,都是人规定的。


昨天在阅读react-router的官方文档的时候,发现了以前没有注意到的一个东东。<UpdateBlocker>

当时由于在试图解决一个问题: 路由切换的时候,某一个公用组件又重新didmount,即进入下一个生命周期,因而也没有好好地阅读文档,误以为这个<UpdateBlocker>的作用是能够阻止组件进入下一个生命周期。

后来几经测试,也没有得到满意的结果。后来仔细阅读文档,才发现与我之前的理解有很大差别。

React-router文档里面提到的这个<UpdateBlocker>,实际上是只是告诉你结合React能够实现怎样的功能

我们知道有些时候,我们并不想让组件发生更新,或者根据实际情况有选择地进行更新。这样也能够最大限度地保证WEB APP的性能。而这个<UpdateBlocker>的作用正在于此。

这时候,其实还是要说回到React 的官方文档,从reactpurecomponent这一章节可以看出来,

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

就是说React.PureComponent内部已经实现了 shouldComponentUpdate()方法,我们可以拿来就用。而不用像使用 React.Component那样,如果为了实现一个同样的判断组件是否需更新的功能,手动写这样一个方法。

P.S 渣英语理解错了,还以为React.PureComponent


需求

自从在本单位工作之后,大部分时候做的都是移动开发。因此,也就不可避免地会遇到很多移动开发的坑。这些坑来源于真实的业务场景,因此,简单总结一下。

第一个坑是关于导航栏的,我们知道市面上的大部分国产APP,它的iOS版本和Android版本,在UI设计上几乎都没有区别,我们做的APP也是如此。因此,也是会在APP的底部有一个导航栏。

这个时候,有意思的事情就出现了。如果是H5页面的话,通常情况下,这个底部导航栏都是通过fixed定位来实现的。而由于移动终端虚拟键盘的作用,Android和iOS对此的反应又并不相同,因此,就出现了这个坑。

我看了很多APP,比如美团,微信,孩子王等APP,当光标聚集到输入框上面的时候,往往会有一个动画,然后将这个搜索页全屏处理。且不论这些APP是否是内嵌H5的,但这样的交互,如果在H5上面也如是一样,就能避免上面提到的由于fixed定位产生的坑。

这个坑的详细表现

在iOS上面的表现:

软键盘唤起后,页面的 fixed 元素将失效(即无法浮动,也可以理解为变成了 absolute 定位),所以当页面超过一屏且滚动时,失效的 fixed 元素就会跟随滚动了。( 来自Web移动端Fixed布局的解决方案

而在Android上面的表现是:

软键盘唤起后,页面的 fixed 元素并没有失效,但是视窗的高度将重新被计算。(原来的视窗高度-虚拟键盘的高度)

所以,如果我们仍然要把底部导航栏和元素放在一个页面上,就需要着手进行改造。不然,就非常影响用户体验。

解决方案

Web移动端Fixed布局的解决方案 提到的当然是一种解决方案,但是,我试着总是不能符合我的要求。

这就说会到我这篇文的题目《薛定谔的导航栏》,我的想法是这样的:如果弹出了虚拟键盘,则将导航栏的样式设置为 display:none,反之,如果隐藏了虚拟键盘,则将导航栏的样式设置为display:block; position:fixed。所以,一个小白,如果看到了弹出虚拟键盘的页面,你问他,现在到底页面上存在不存在导航栏呢?

假如他回答存在,你把这个虚拟键盘隐藏掉,结果当然是有的。可是当时有没有呢?并没有吧。

加入他回答不存在,你把这个虚拟键盘隐藏点,结果却是有的,很明显又和实际看到的不一样了。

知识点

所以接下来和核心问题,就是如何在iOS上和Android上去检测虚拟键盘的弹出和隐藏了。

下面进入直接贴代码说明问题环节,这里要说明一下,由于我使用了React框架,因此,实际上,我下面写的代码都是JSX文件的一部分

iOS

addIosKeyboardHandler = () => {  // IOS上,弹出输入框后,同样隐藏底部导航。
    document.addEventListener('focus', this.IosFocusHandler, true);
    document.addEventListener('focusout', this.focusoutHandler, true);
}

removeKeyboardHandler = () => {
    document.removeEventListener('focus', this.IosFocusHandler, true);
    document.removeEventListener('focusout', this.focusoutHandler, true);
}

IosFocusHandler = () => {
    if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
        window.scrollTo(0, 0);
        const footer = document.getElementById('footer');
        footer.classList.remove('fixed');
        footer.classList.add('hidden');
        window.setTimeout(() => {
            document.activeElement.scrollIntoViewIfNeeded();
        }, 100);
    }
}
focusoutHandler =() => {
    window.scrollTo(0, 0);
    const footer = document.getElementById('footer');
    footer.classList.add('fixed');
    footer.classList.remove('hidden');
}

这里有几个点需要注意:

  • focus事件需要通过捕获(capture: true)的形式才能触发,在document上面的监听
  • focusout要比blur事件在描述虚拟键盘隐藏这件事情上更加准确,blur事件不会冒泡
  • 当弹出输入法准备敲击时,先将页面滚动到顶部

Android

Android 上和IOS上又有一些不同,主要的不同点在于:当弹出虚拟键盘后,通过点击返回键,隐藏掉虚拟键盘,这个时候,如果我们对此不做任何处理的话,那么光标还是会在输入框里面,换句话说,此时对于输入框俩说,并没有发生blur事件,因为很显然,光标还在输入框里面。

因此还是根据Android上的表现来做判断,既然弹出虚拟键盘会让视窗的高度变小,而收起虚拟键盘又会让视窗的高度变大,那么我们就从这里着手。

addAndroidKeyboardHandler = () => {
    this.maxHeight = document.documentElement.clientHeight;
    window.addEventListener('resize', this.AndroidKeyboardHandler.bind(this, this.maxHeight));
    document.addEventListener('focusout', this.focusoutHandler, true);
}

removeAndroidKeyboardHandler = () => {
    window.removeEventListener('resize', this.AndroidKeyboardHandler.bind(this, this.maxHeight));
    document.removeEventListener('focusout', this.focusoutHandler, true);
}

AndroidKeyboardHandler = maxHeight => { // 解决 Android 弹出输入框时,底部导航一同浮起的问题。隐藏底部导航
    const presentHeight = window.innerHeight;
    const footer = document.getElementById('footer');
    if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
        if (presentHeight < maxHeight) { // 隐藏底部导航
            footer.classList.remove('fixed');
            footer.classList.add('hidden');
            window.setTimeout(() => {
                document.activeElement.scrollIntoViewIfNeeded();
            }, 100);
        } else {
            document.activeElement.blur();
            footer.classList.add('fixed');
            footer.classList.remove('hidden');
        }
    }
}

小提示

这个时候只需要把我上面代码中的addIosKeyboardHandler 方法在componentDidMount的时候,进行触发即可。

当然,这里可能要补充一个小知识点,由于componentDidMount的时候,通过React渲染的dom已经处于loaded的状态,因此无需再像以往通过window.onload或者jq的$(document).ready去添加事件处理程序。

下面的问题

这样的方法可以解决大部分的问题。但是还是会有一些历史遗留代码与此方案相处地并不融洽。

比如在购物车页面,会有一个输入框,可以改变选定商品的数量。购物车的结算button是fixed的定位,当时考虑到在他下面还有导航栏,因此,提前计算好了导航栏的高度。于是这个部分的样式大概是这样的:

  display: fixed;
  bottom: 1.4rem;

恩,我们使用了rem布局。结果就会造成由于我前面的处理,只会在出现虚拟键盘的时候将footer(底部导航栏)隐藏掉,并不会对结算button进行处理,因此,如果看Android上的表现,这个时候,这个button所在的部分就会和键盘之间有1.4rem的间距。而诡异的是,结算button部分还是处于fixed的状态,而这,并非我所期待的。

这就是另外一个问题了。

所以,还是说回来。这也是为什么之前看到其他一些人有一些友情提示:在做移动开发的时候,不要将input元素和其他fixed的元素放在一起。而像我这种更要命,我直接在页面底部放了两个fixed的元素。

我目前的措施是针对这种情况,单独处理。毕竟,这也只是特例。