缘来我们在这里

乱世天狼的博客


  • 首页

  • 归档

青岛游记

发表于 2020-04-07

引子

你站在那里,午后的阳光轻洒,微风轻拂长发,这一刻,我看到了春天的嫩芽。

​ —— ctw

正文

2020年4月3日,处理完手中令人头大的工作,我踏上了去青岛的旅程。坐上飞机,带上耳塞,有春天的鸟儿在歌唱,春天来了,春天来了。

重庆南路1号甲,喆·啡酒店,来到了房间,看到了水,养乐多,每日坚果,日式饼干,还有薯片。每一样东西还有非常温馨的话语:老张辛苦了,吃点儿宵夜放松下吧,期待明天的一切,有点儿甜,wink。这一波操作始料未及,空前绝后,都快感动的我老泪纵横了,我只想告诉馨馨,明天是美好的一天啊。

4月4号,上午8点,七拐八拐到了馨馨住的地方,馨馨给我开了门,一起吃了早餐后去买菜。买了茄子,蛤蜊,西红柿,扁豆角。午饭尝的是馨馨的手艺,不得不说,真的很好吃,如果非要形容的话,那就是唇齿留香,回味无穷,怪不得枝丫想要馨馨来上海。一起买菜,一起做饭,一起吃法,这个场景,如果不提醒我自己,我都会觉得自己在梦里。美女给我做饭,我是不是遇到了田螺姑娘了。

我莫名的紧张,连续三天加班,可能让我气色有些憔悴,额头上还起了一个包,我29岁,馨馨才23岁,亭亭玉立的年纪。虽然聊的挺开心,三观也基本一致,但是哪个小姑娘不想要找一个帅气多金的男朋友啊。我就是一普通人,有优点,有缺点,还有对世界的那一点初心。姑娘能接受我么?我也不知道答案。

我们去了栈桥,天很蓝,万里无云,水很蓝,风很大,海浪一波波拍在沙滩上,激起海鸥一片。栈桥笔直延伸向大海,尽头是回澜阁,人流熙熙攘攘。栈桥旁边有一片小海滩,在海边,我给她拍了照,面对大海,回眸浅笑,那一刻她的笑容好似海面上的粼粼波光,不断荡漾。我按下了快门,扑捉到这美好的一刻。然后我们去坐了皮划艇,皮划艇速度很快,海浪拍打着脸庞,阳光照耀在两个年轻人的身上,好似这时光也静止那么一刻。

然后沿着海边我们去了鲁迅公园和小青岛,我们遇到了一家子拍照的人,遇到了一个胡言乱语的算命人,看见一群人在打沙滩排球,在踢足球,看见了有几只风筝在天上飞舞,看到了琴女的雕像,看到了一树桃花和姑娘的笑容。

你站在那里,午后的阳光轻洒,微风轻拂长发,这一刻,我看到了春天的嫩芽。

4月5号,我们点了青岛特色的甜沫,是一种小米面做成的汤,中间加了一些木耳,面筋等熬制而成,味道类似于胡辣汤,又不太一样。吃完早饭,准备去八大关风景区,八大关其实就是八条道路,靠着海边,取了中国险要关隘的名字,所以风景尤美。

八大关靠近海边,正好到了一块堤坝所在之处,堤坝右边是一片怪石林立的浅滩,左边是一片干净的沙滩。我们沿着沙滩走了走,看到有个小孩子在沙滩上挖坑,都快把自己给埋了起来,有个小孩子光着脚在沙滩上跑来跑去,对着海浪,我们拍了第一张合照。巧笑倩兮而倾城,美目盼兮而多情。我们走上堤坝,突然看到太阳旁边有了一层光晕,朦胧而梦幻。堤坝右边很多人在赶海,刚退潮的大海給人们以丰厚的馈赠。我们看到石头上非常多的白色的贝壳,就很疑惑,这些贝壳是怎么嵌入到石头上面的,后来看到有阿姨在敲碎这些壳,收获牡蛎,我们才知道原来牡蛎是附着在石头上的。我们也加入了赶海的大潮,到退潮后的小水坑旁,翻开石头就能看到伪装的很好的小螃蟹,和一些叫不上名字的浮游生物,手机倒置来拍摄,一个小水坑就变成了一个精彩纷呈的小世界。

走过乱石滩,我第一次牵了她的手,好凉,好暖。凉是因为刚才玩水还没干,暖是因为春日的阳光照在心头。这一刻我无比希望乱石滩延绵到世界的尽头。

离开乱石滩,我们来到了花石楼,花石楼是曾经蒋介石和宋美龄住的地方,我们去了他们曾经的卧室。卧室里有两幅画,一幅是蒋介石在看宋美龄作画,一幅是宋美龄画的荷花。最理想的爱情是不是就是这样呢,你在闹,我在笑,一起携手到白头,你依然是我心头的宝。陈毅退休也曾住在这里,还做了一首诗,洋溢着昂扬的斗志和坚强的意志,真正的事业是我们愿意不停的为之奋斗到老的那一缕初心,那一抹信念。

然后我们去吃了小林家常菜,非常具有青岛特色,鲅鱼饺子很不错,扇贝很香。粗心的我才知道原来,她是惯用左手的。

吃完饭,我们去了海底世界,青岛的一处景点。水母馆里面有梦幻一般的水母,透明的,发光的,巨大的,微小的,还有带着长长触角的。五彩缤纷,绚烂而梦幻。海豹馆里有非常萌的小海豹,海豹在陆地上是靠前爪拖着身体移动的,但是速度一点儿都不慢。海豹的头是圆的,眼睛圆滚滚,身体如绸缎般丝滑,在水里,在陆地上都来去自如,非常欢快。海豹馆之后我们来到了海鲸馆(giants of ocean),第一次见到鲸鱼的骨架,巨大无比,都比得上一两座小房子大小了。还见到了一种非常别致的鲸鱼,鼻子上方有长长的独角。过了海鲸馆,我们来到海底世界上层,看到了直立起来的北极熊标本,看起来异常凶猛,还有活灵活现的小企鹅,我们不知道它老是抬脚要干嘛,就耐心观察了一会儿,没想到一会儿发现,原来它是要方便。上层有动物的演化史,我们都知道人是从海里原始生物一步步进化而来,但是这里展示了一系列的标本,化石,展示了人类整体的进化历程,而这就是人们总是向往大海最深刻的缘由。

最后,经过指引,我们来到了真正的海底世界,展馆的海底世界在地下,是一个比较窄的通道,有类似于机场的电动履带,人站上去,就可以领略被海洋生物包围,如梦似幻的感受。可以清晰的看到各种说不上名字的鱼类成群结对在上面,在下面,在左边,在右边欢快的游动。我们录制了视频,记录这美丽的风光。

回味无穷的海底世界结束了,我们在门外合影留念。然后去吃晚饭,吃的是牛道的烤肉,味道不错,第一次认识到了苏叶这种蔬菜。

由于明天早上她要临时加班,我要赶飞机,所以要在今晚告别。我们去超市给朋友买了礼物,然后回到酒店,看了会儿电视,她坐着,我躺着。我们俩其实是两种人,我自由随性,不喜欢过多约束,她温柔而内敛,希望得到别人的认可。我明白她是有失望的,我第一次谈恋爱,很多细节做的不周到,长得不帅,甚至精神因为连续加班导致不太饱满。我看着她,她看着电视,相对无言,我不知道明天是否是什么样子,也不知道她能否接受我,不过好像这个小姑娘在我的心里投下了一个影子。回想起早上看到小姑娘在blibli上发的视频,就是个小姑娘嘛,希望精彩的生活,希望明媚的未来,渴望被人爱,被人懂。我很想告诉她,你值得。你温柔,内敛,活泼,可爱,上进,给身边的人以温暖,应该拥有幸福。

我送她回家,然后就该走了。

飞机起降,我离开了青岛,不知道离小姑娘是否也远了一些呢,又要回归工作了呢,珍重,我最亲爱的小姑娘。

2019.07.06.md

发表于 2019-07-06

6月份过去了一个星期了,是时候静下来反思一下了。

这个月成长是什么?

从生活作息上其实并没有什么成长,没有养成良好的作息习惯。在前半月精神状态一直不好。在团队管理上,其实现在做的并不是特别好。现在要思考的一个问题是,如果把所有人的积极性调用起来!第一步是获取所有人的信任,目前来说做的一般,不算好也不算差。从知识点上,学会了小程序和taro,而且坚持读英文原版书,让自己对新的东西有了信心。通过5,6月份的反复斗争。终于是找到了平衡点。对于要思考的东西,比如创业,其实并没有太多助益,只不过在和汪紫照聊天的过程中多了一个想法。还有自己的app一直没有着落。

这个月最大的收获就是了解了自己的人性,同时掌握到了克制自己人性的办法。

新的一个月如何打算

找到成长和管理的平衡点。能够形成真真切切的领导力。通过身体锻炼,技能锻炼,提升整体魅力。领导力的核心在于魅力。魅力在于智慧和身体技能。诚实的对自己,不着急,稳着点去寻找每一项事物的关键点。加油!申请的服务器和微信小程序要用起来。

回顾目标

坚定信念,30岁前不创业,尽可能把自己打造的优秀。学会敏锐的观察力和思考力,让自己掌握能够迅速破解一切事物的能力。

重构

发表于 2019-02-24

重构 - 改善既有代码的设计

重构原则

一、重构定义

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

重构(动词):使用一系列重构首发,在不改变软件可观察行为的前提下,调整其结构

二、为何重构

  1. 重构改建软件设计(更好的结构,让软件更好维护)
  2. 重构使软件更容易理解
  3. 重构帮助找到bug(写明显没有bug的代码,而不是没有明显bug的代码)
  4. 重构提高编程速度

三、何时重构

三次法则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。

  1. 添加功能时重构
  2. 修补错误时重构
  3. 复审代码时重构

代码复审有助于经验传播,更多人能够理解系统中的更多部分。结对编程就是代码复审的一种体现。

四、怎么和经理说?

  1. 质量
  2. 如果是进度驱动,不建议告诉经理!

五、重构的问题

  1. 数据结构
  2. 修改接口
  3. 难以通过重构手法完成的设计改动
  4. 何时不该重构

六、重构与设计

  1. 先设计一个足够合理的解决方案(不要过度设计)
  2. 使用重构改善设计

七、重构与性能

重构可以帮我们写出更快的软件

八、重构起源

略

代码的坏味道

‘如果尿布臭了,就换掉它。’ – 语出beck奶奶,讨论抚养小孩的哲学

  1. 重复代码
  2. 过长函数
  3. 过大的类
  4. 过长参数列
  5. 发散式变化:一个类收到多种变化影响
  6. 霰弹式修改:一种变化引发多个类相应修改
  7. 依恋情结:函数对于某个类的兴趣高过对自己所处类的兴趣
  8. 数据泥团:相同的几项数据经常多次出现
  9. 基本类型偏执:使用小对象(未理解,存疑)
  10. switch:switch太多重复,可以使用多态来替换(待验证)
  11. 平行继承体系:当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。
  12. 冗余类
  13. 夸夸其谈未来性
  14. 暂时性字段
  15. 过度耦合的消息链
  16. 中间人:过度使用委托
  17. 狎昵关系
  18. 异曲同工的类
  19. 不完美的库类
  20. 纯稚的数据类
  21. 被拒绝的继承:如果子类不想或者不需要继承超类的函数和数据
  22. 过多的注释:帮我我们找到坏味道

构筑测试体系

编写优良的测试程序,可以极大的提高我的编程速度。

频繁进行测试是极限编程的重要一环

  • 单元测试:每个测试类,测试函数
  • 功能测试:测试整个系统的功能

测试是一种风险驱动的行为,测试的目的是找出现在或未来可能出现的错误。所以我不会去测试那些仅仅读或写一个字段的访问函数,因为它们太简单了,不大可能出错。测试的要诀是:测试你最担心出错的部分。

任何测试都不能证明一个程序没有bug,但并不影响「测试可以提高编程速度」。当测试数量达到一定程度时,继续增加测试带来的效益就会呈现递减态势,而非持续递增。测试要集中在可能出错的地方,观察代码,看哪里复杂;观察函数,思考哪些地方可能出错。

重构列表

重构的记录格式

  • 名称
  • 摘要
  • 动机
  • 做法
  • 范例

寻找引用点

  • 被删除的部分在继承体系中声明了不止一次
  • 编译器可能太慢
  • 编译器无法找到通过反射机制而得到的引用点

不要盲目的查找-替换,用心检查

成熟的重构手法

  1. 基本技巧:「小步前进,频繁测试」已经得到多年的实践检验。
  2. 设计模式为重构行为提供了目标

重新组织函数

Extract Method

你有一段代码可以被组织在一起并独立出来

  1. 动机

当我看到一个过长的函数或者需要注释才能让人理解用途的代码,我就会将这段代码放进一个独立函数。

如果习惯看大型函数,恐怕需要一段实践才能适应这种新风格。而且只有当你能给小型函数很好的命名时,它们才能真正起作用,所以你需要在函数名称上下点功夫。

  1. 做法

    • 创造一个新函数,根据这个函数的意图来对它命名(以它做什么来命名,而不是以它怎么做命名):即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好的方式昭示代码意图,你也应该提炼它。如果你想不出来一个更有意义的名称,就别动
    • 将提炼出来的代码从源函数复制到目标函数中
    • 仔细检查提炼出的代码,看看其中是否引用了‘作用域限于源函数’的变量(包括局部变量和源函数参数)
    • 检查是否有‘仅用于被提炼代码段’的临时变量。如果有,在目标函数中将它们声明为临时变量
    • 检查被提炼的代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量值被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。如果很难这么做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动的提炼出来。你可能需要先使用「split temporary variable」,然后再尝试提炼。也可以使用「replace temp with query」将临时变量消灭掉。
    • 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数。
    • 处理完所有的局部变量后,进行编译
    • 在源函数中,将被提炼代码替换为对目标函数的调用
    • 编译,测试

临时变量往往为数众多,甚至会使提炼工作举步维艰。这种情况,我会尝试先用「replace temp with query」减少临时变量。如果即使这么做了提炼依旧困难重重,我就会动用「replace method with method object」,这个重构方法不在乎代码中有多少临时变量,也不在乎你如何使用它们。

Inline Method

一个函数本体与名称同样清楚易懂,在函数调用点插入函数本体,然后移除该函数

1
2
3
4
5
6
int getRating(){
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return _numberOfLateDeliveries > 5;
}

改造后

1
2
3
int getRating() {
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}

  1. 动机
    • 本书经常以简短的函数表现动作意图,这样会使代码更清晰易读。但有时候你会遇到某些函数,其内部代码和函数名称同样清晰可读。如果这样,你就应该去掉这个函数。
    • 另一种需要使用「inline method」的情况是:你手上有一群组织不甚合理的小型函数。实施「Replace Method with Method Object」之前就这么做,往往可以取得不错的结果。
  2. 做法
    • 检查函数,确定它不具多态性
    • 找出这个函数的所有被调用点
    • 将这个函数的所有被调用点替换为函数本体
    • 编译,测试
    • 删除该函数的定义

Inline Temp

你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构首发。将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

Replace Temp with Query

你的程序以一个临时变量保存某一表达式的运算结果。

Introduce Explaining Variable

你有一个复杂的表达式。将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

Split Temporary Variable

你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量。

Remove Assignments to Parameters

代码对一个参数进行赋值,以一个临时变量取代该参数的位置。

Replace Method with Method Object

将这个函数放进一个单独对象中,如此依赖局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。

Substitute Algorithm

你想要把某个算法替换为另一个更清晰的算法。

在对象之间搬移特性

类往往会因为承担过多的责任而变得臃肿不堪。这种情况下,我会使用「Extract Class」将一部分责任分离出去。如果一个类变得太“不负责任”,我会使用「Inline Class」将它融入另一个类。如果一个类使用了另一个类,运用「Hide Delegate」将这种关系隐藏起来通常是有帮助的。有时候隐藏委托类会导致拥有者的接口经常变化,此时需要使用「Remove Middle Man」。

本章最后两项重构–「Indroduce Foreign Method」和「Introduce Local Extension」比较特殊。只有当我不能访问某个类的源码,却又想把其他责任移进这个不可修改的类时,我才会使用这两个重构首发。

Move Method

你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。在该函数最常引用的类中建立一个有着类似行为的新函数。将就函数变成一个单纯的委托函数,或是将就函数完全移除。

Move Field

你的程序中,某个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改源字段的所有用户,令他们该用新字段。

Extract Class

某个类做了应由两个类做的事情。建立一个新类,将相关的字段和函数从旧类搬到新类。

Inline Class

某个类没有做太多事情。将这个类的所有特性搬移到另一个类中,然后移除原类。

Hide Delegate

客户通过一个委托类来调用另一个对象。在服务类上建立客户所需的所有函数,用以隐藏委托关系。

Remove Middle Man

某个类做了过多的简单委托动作,让客户直接调用受托类

重构的意义在于:你永远不必说对不起 —— 只要把出问题的地方修补好就行了

Introduce Foreign Method

你需要为服务的类增加一个函数,但你无法修改这个类,在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。

重复代码是软件万恶之源

Introduce Local Extension

你需要为服务类提供一些额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数。然这个扩展品成为源类的子类或包装类。

还是子类吧,包装类要累死人

要遵循原则:函数和数据应该被统一封装。

重新组织数据

Self Encapsulate Field

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。

为这个字段建立取值/设值函数,并且只以这些函数来访问字段

Replace Data Value with Object

以偶一个数据项,需要与其他数据和行为一起使用才有意义

Change Value to Reference

你从一个类中衍生出许多彼此相等的实例,希望将它们替换为同一个对象。

将引用对象改为值对象

你有一个引用对象,很小且不可变,而且不易管理,将它变成一个值对象

Replace Array with Object

你有一个数组,其中的元素各自代表不同的东西。以对象替换数组。对于数组中的每个元素,以一个字段来表示。

Duplicate Observerd Data

你有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。

一个分层良好的系统,应该将处理用户界面和处理业务逻辑代码分开。经典的MVC模式

这个没有理解

Change Unidirectional Association to Bidirectional

两个类都需要使用对方特性,但其间只有一条单项链接。添加一个反向指针,并使修改函数能够同时更新两条连接。

相当于双向绑定,可以带来方便,也有可能带来混乱。

Change Bidirectional Association to Unidirectional

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。去除不必要的关联

Replace Magic Number with Symbolic Constant

创造一个常量,根据其意义为它命名,并将上面的数值替换为这个常量

Encapsulate Field

如果你的类存在一个public字段,将它声明为private,并提供相应的访问函数

Encapsulate Collection

有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。

Replace Record with Data Class

你需要面对传统编程环境中的记录结构,为该记录创建一个哑数据对象

Replace Type Code with Class

类之中又一个数值类型码,但它并不影响类的行为。以一个新类替换该数值类型码。

Replace Type code with Subclasses

你有一个不可变的类型码,它会影响类的行为。以子类取代这个类型码。

Replace Subclass with Fields

你的各个子类的唯一差别只在‘返回常量’的函数上,修改这些函数,使他们返回超类中某个(新增)字段,然后销毁子类

简化条件表达式

Decompose Conditional

你有一个复杂的条件语句。从if-then-else中分别提炼独立函数

Consolidate Conditional Expression

你有一系列条件测试,都得到一样的结果。将这些测试合并为一个条件表达式,并将这个表达式抽出来一个独立函数

Consolidate Duplicate Conditional Fragments

在条件表达式上的每一个分支有着相同的一段代码。将这段重复的代码挪到条件表达式外。

Remove Control Flag

在一系列布尔表达式中,某个变量带有‘控制标记‘的作用。以break或者continue去掉控制标记

Replace Nested Conditional with Guard Clause

函数中的条件逻辑使人难以看清正常的执行逻辑,使用卫语句表现所有的情况

Replace Conditional with Polymorsphim

你手上有多个条件表达式,它根据对象的不同而选择不同的行为。将这个条件表达式的每一个分支都放进一个子类的覆写函数里,然后将原本的函数声明为抽象函数

Introduce Null Object

你需要再三检查某对象是否为null,将null值替换null对象

Introduce Assertion

某一段代码需要对程序状态作出某种假设。以断言明确表现出这种假设。

简化函数调用

Rename Method

生活就是如此。你常常无法第一次就给函数起一个好名称。这时候你可能会想:就这样将就着吧,毕竟只是一个名称而已。当心!这是恶魔的召唤,是通向混乱之路,千万不要被它诱惑!如果你看到一个函数名称不能很好的表达它的用途,应该马上加以修改。记住,你的代码首先是为人写的,其次才是为计算机写的。而人需要良好名称的函数。想象过去曾经浪费的无数时间吧。如果给每个函数都起一个良好的名称,也许你可以节约好多时间。起一个好名称并不容易,需要经验;要想成为一个编程高手,起名的水平是至关重要的。

Add Parameter

如果有其他选择,尽量不要用这个方案,因为过长的参数列不是好事,往往是代码的坏味道。超过3个很多人已经不耐烦了。

Remove Parameter

程序员可能经常添加参数,却往往不愿意去掉它们。他们打的如意算盘是:无论如何,多余的参数不会引起任何问题,而且以后还可能用上它。

这也是恶魔的诱惑,一定要把它从脑子里赶出去!参数代表着函数所需的信息,不同的参数值有不同的意义。函数的调用者必须为每一个参数操心该传什么东西进去。如果你不去掉多余参数,就是让你的每一位用户多费一份心。是很不划算的,更何况,“去掉参数”是非常简单的一项重构。

Separate Query from Modifier

某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,一个负责查询,一个负责修改。

Parameterize method

若干函数做了类似的工作,但在函数本体中却包含了不同的值。建立一个单一函数,以参数表达那些不同的值。

Replace Parameter with Explicit Methods

你有一个函数,其中完全取决于参数值而采取不同行为。针对该参数的每一个可能值,建立一个独立函数。

和上面的方法恰恰相反,如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同的参数值做出不同的行为,那么就应该使用本项重构。

Preserve Whole Object

你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象

Replace Parameter with Methods

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够直接调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。

Indroduce Parameter Object

某些参数总是很自然地同时出现。以一个对象取代这些参数。

Remove Setting Method

类中的某个字段应该在对象创建时被设值,然后就不再改变。去掉该字段的所有设值函数。

Hide Method

有一个函数,从来没有被其他任何类用到。将这个函数修改为private

Replace Constructor with Factory Method

你希望在创建对象时,不仅仅是做简单的建构动作。将构造函数替换为工厂函数。

Encapsulate Downcast

某个函数返回的对象,需要由函数调用者执行向下转型。将向下转型动作移到函数中。

Replace Error Code with Exception

某个函数返回一个特定的代码,用以表示某种错误情况。改用异常。

许多程序都使用特殊输出来表示错误,Unix系统和C-based系统的传统方式就是以返回值表示子程序的成功或失败。java有一种更好的错误处理方式,异常。这种方式之所以更好,因为它清楚地将“普通程序”和“错误处理”分开了,这使得程序更容易理解 —— 我希望你如今已经坚信:代码的可理解性应该是我们虔诚追求的目标。

Replace Exception with Test

面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查。

处理概括关系

Pull Up Field

两个子类拥有相同的字段。将该字段移至超类。

Pull Up Method

有些函数,在各个子类中产生完全相同的结果。将该函数移至超类。

Pull Up Constructor Body

你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。在超类中新建一个构造函数,并在子类构造函数中调用它。

Push Down Method

超类中的某个函数只与部分子类有关。将这个函数移到相关的那些子类去。

Push Down Field

超类中的某个字段只被部分子类用到,将这个字段移到需要它的那些子类去。

Extract Subclass

类中的某些特性只被某些实例用到。新建一个子类,将上面所说的那一部分特性移到子类中。

Extract Superclass

两个类有相似的特性。为这两个类建立一个超类,将相同特性移至超类。

Extract Interface

若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。将相同的子集提炼到一个独立接口中。

Collapse Hierarchy

超类和子类之间无太大区别。将它们合为一体。

Form TemPlate Method

你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。

Replace Inheritance with Delegation

某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉两者之前的继承关系。

Replace Delegation with Inheritance

你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。让委托类继承受托类。

大型重构

经验丰富的面向对象开发人员发现:对于一个长时间、大负荷运转的系统来说,将业务逻辑与用户界面隔离开来是至关重要的。

Tease Apart Inheritance

某个继承体系同时承担两项责任。建立两个继承体系,并通过委托关系让其中一个可以调用另外一个。

Convert Procedural Design to Objects

将过程化设计转化为对象设计。你手上有一些传统过程化风格的代码。将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。

Separate Domain from Presentation

某些GUI类之中包含了领域逻辑。将领域逻辑分离出来,为他们建立独立的领域类。

Extract Hierarchy

你有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况。

移动端真机测试

发表于 2019-02-19
  • http://appium.io/
  • https://testerhome.com/topics/2522
  • https://github.com/openstf/stf

随记两首

发表于 2019-02-15

不平而鸣

己亥年正月初十

钥匙不慎锁门里,覆水浸入硬盘中。
莫谈跌倒伤膝盖,猪年诸事不太平。
无有寒风梅不香,更经雨雪菊才清。
风霜雨雪都熬过,今年花胜去年红。

会佳人

己亥年正月初十

高楼如笋车如龙,春风散入不夜城。
寒雨清风浮人冷,明眸善睐暖人情。
欢声笑语话家常,别有忧愁暗边生。
漫漫长路我来伴,风雨过后是彩虹。

总纲

发表于 2019-02-13

大梦谁先觉,平生我自知。你来了,是和我命中注定的缘分!
我为你 提笔序
你娇若梨花带雨
西湖岸边我与你共聚
我为你 唱一曲
你倾城一笑不语
倚门回首面似凝脂玉
我为你 提笔序
你娇若梨花带雨
你的美不止诗词几句

主要内容

技术

主要由浅入深介绍前端知识,html5 -> css3 -> js -> jquery -> react -> reactNative,另外包含每个知识点所必备的基础,比如打包,jslint,模块化,前端历史等。

好的前端工程师,应该会一门后端语言,这里主要会介绍go配合mongoDB的使用,因为go和mongoDB代表的是21世纪的趋势。

生活

这里的内容会比较随意一点,发表关于事件的看法,走出人生低谷的心路,克服懒癌的方法,以及生活随笔。

写作方式

每篇文章都会有一个引子,引子必须生动形象,让人一看就懂。或者故事,或者比喻,或者名人名言等。然后每篇文章都要自己审核两遍,要求通俗易懂,同时具有一定深度。篇章可以短小,但是必须精悍!虚心接受朋友,网友的意见。

更新频率

尽量保持一天一篇,技术和生活交叉更新

双12问题反思

发表于 2018-12-13

双12问题

改动https导致增加unsafe_componentWillMount生命周期不生效

深刻反思

  1. 遇到「非常规」问题,一定要所思索,不要轻易绕过
  2. 所有动作,一定要经过检验,一定要经过检验,一定要经过检验
  3. 不允许犯第二次错误

软件工程

发表于 2018-11-22

软件工程

瀑布流模型

需求收集 - 系统分析 - 编码 - 测试 - 执行 - 操作与维修

适用场景:已经有了同类产品,计划先于实施

迭代模型

测试 - 验证 - 设计 - 编码 - 测试 - 验证 - 设计 …

适用场景:需要尝试的场景,迅速迭代,尤其是互联网

螺旋模型

上面两个模型的组合

瀑布流 -> 瀑布流

适用场景:迭代速度一般,需要一段时间稳定的方案

V - 型

验证和确认模型,瀑布流模型的加强

加强了瀑布流的稳定性和效率

大爆炸模型

任意编程,随机产生结果,不适合大型软件项目,但是是好的学习和试验。

适用场景:兴趣驱动,非目标驱动,有可能产生成果,但大部分可能是无用工。

管理智慧

发表于 2018-08-27

九种说服人的技巧

  1. 对于有智慧的人,要旁征博引,使其乐于听闻
  2. 对于见解广博的人,以分析事理为主,使其心生佩服
  3. 对善于分析事理的人,要简明扼要,把握住事务的要点,以吸引其注意
  4. 对达官贵人,要顺着他的权势,因势利导,使其乐于接受
  5. 对富有的人,要使其觉得华贵高雅
  6. 对于贫穷的热,要以利益相诱惑
  7. 对于卑贱的人,身段要柔软,以谦卑言辞感动,不可傲气凌人
  8. 对于勇猛的人,要果敢明快
  9. 对于愚鲁的人,言辞要敏锐

做事有哪些基本原则

  1. 守本分
  2. 守规矩
  3. 守时限
  4. 守承诺
  5. 重方法
  6. 重改善

做事要讲究方法,然后才能做好

怎样建立制度才合乎人性

  1. 管理一定要制度化,但制度必须合理
  2. 合理的制度,大家一定要自动遵守
  3. 制定制度时,由下而上比较容易合理
  4. 下面定的不合理,上面还可以有意见
  5. 上下交流,彼此尊重,叫做好商量
  6. 自然孕育而成的制度,比较合乎人性

处理事情之前怎样思虑比较合理

  • 思虑的方式和处理事情的方式相反,凡事先看规定,合乎规定才可以去做;
  • 不合规定,要先研究有什么可以变通的;不能变通最好改变事情以符合规定;
  • 如果有困难,最好和上级商量以求谅解;
  • 合理合法,还要考虑可能产生后遗症;

create-react-app使用

发表于 2018-07-16

引子

武陵春·春晚

宋:李清照

风住尘香花已尽,日晚倦梳头。物是人非事事休,欲语泪先流。
闻说双溪春尚好,也拟泛轻舟。只恐双溪舴艋舟,载不动许多愁。

正文

更新新版本

Create React App主要分为两部分:

  • create-react-app: 全局命令行用于创建新的项目
  • react-scripts: 生成项目中的依赖

create-react-app几乎永远不用更新,因为所有的配置其实都在react-scripts

create-react-app每次生成新项目的时候,总会用最新版本的react-scripts,所以创建的app必然能够获得所有最新的特性

如果要更新一个现有工程的react-scripts,打开更新日志,然后找到现在的版本(如果不确定,看下package.json),然后根据迁移指南升级。

大部分情况,更新package.json中的react-scripts版本,然后运行npm install即可,但是为了有可能存在的大版本更新,还是要看下更新日志,然后找到现在的版本(如果不确定,看下package.json),然后根据迁移指南升级。

维护者:我们会尽可能保持更新尽可能小,这样可以保证用户升级react-scripts是无痛的。

反馈

维护者:我们对于反馈,一直是非常开放的心态,来反馈吧

目录结构

创建完之后,工程目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my-app/
README.md
node_modules/
package.json
public/
index.html
favicon.ico
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg

在创建的这个工程中,以下文件必须是固定的文件名

  • public/index.html 是页面模版
  • src/index.js 是js入口

其他的文件可以酌情删除或修改

可以在src内部创建子目录。为了快速的rebuild,只有在src目录才会被webpack处理。所以需要 把所有css和js文件放到src目录下,否则webpack会忽略掉。

只有在public文件夹下面的文件可以被public/index.html使用
在js和html中使用资源请阅读下面的说明

其实可以创建更多顶层目录。
这些文件不会在被正式编译覆盖,所以可以用来放一些像文档之类的东东

可用的一些脚本

npm start

在development模式下运行,打开 http://localhost:3000 在浏览器中查看

当你编辑并保存的时候,这个页面会自动重载。你还可以在console中看到一些lint的错误

npm test

在观测模式下发起测试,更多信息请参考 运行测试

npm run build

构建正式环境的app,并放置于build文件夹
这个操作会在正式环境打包react,并优化构建至最优表现

构建会压缩文件并且文件名包含内容哈希

参考发布资料查看更多信息

npm run eject

声明:这是一个单项操作。一旦eject之后,不能回去

如果你对默认的配置选项不满意,可以在任何时间eject。这个命令会移除项目中的单向依赖。

另外,eject会复制所有的配置文件并且迁移所有依赖(webpack,babel,eslint等)到项目中,这样你可以自主修改,覆盖这些东东的默认配置。所有的命令,除了eject,依然可以使用,但是他们会指到拷贝出来的脚本,所以你可以更改它们。在这一点上,你完全随意。

你甚至可以完全不用这个命令。一些辅助的特性能解决大部分中小型项目配置需求,所以你不是必须要用这个特性。另外,如果你不曾准备好要定制化,这个命令将很鸡肋。

支持的浏览器

默认情况,生成的工程使用最新版本React

支持的浏览器,要看react相关文档

支持的语言特性和polyfills

此工程支持最新js标准的一个超集。
除了ES6预发特性外,还支持:

  • Exponentiation Operator (ES2016).
  • Async/await (ES2017).
  • Object Rest/Spread Properties (stage 3 proposal).
  • Dynamic import() (stage 3 proposal)
  • Class Fields and Static Properties (part of stage 3 proposal).
  • jsx 和 flow

学习更多请查看各种阶段性提议

鉴于我们推荐谨慎地使用实验性的建议,Facebook在生产环境重度使用这些代码,所以我们打算提供编码范式,如果这些提议在将来更改的话。

请清楚一点,这个项目仅仅包含很少一部分ES6 polyfills:

  • Object.assign() 基于 object-assign
  • Promise 基于 promise
  • fetch() 基于 whatwg-fetch

如果你是用其他ES6+的特性需要 runtime支持 (such as Array.from() or Symbol), 确定你已经手动包含了相应的polyfills,或者你要运行的浏览器已然支持这些特性

编辑器语法高亮

要在你喜爱的编辑器中配置语法高亮,请参照babel的相关文档并且遵循相应的指引,文档指引包含了好几个流行的编辑器。

在编辑器中展示lint

注意:这个特性依赖react-scripts#0.2.0以上和npm 3以上

一些编辑器,Sublime Text,Atom,和Visual Studio Code,提供ESLint插件。

它们本省并不需要linting. 你可以在你的终端和浏览器console中看到linter。但是,如果你更喜欢在编辑器内部看到lint结果,你需要一些额外的步骤。

你需要为你的编辑器安装一个ESLint插件。然后在根目录添加一个.eslintrc文件:

1
2
3
{
"extends": "react-app"
}

现在你的编辑器可以看到linting 警告了

要说明一点,即使你将来编辑了你的eslintrc,终端和编辑器console中的lint不会变。因为Create React App有意提供一个mini规则集,用于发现一些常见错误。

如果你希望project强制使用一个编码锋哥,考虑使用Prettier取代ESLint规则。

在编辑器中调试

这个特性目前仅仅被Visual Studio Code和WebStorm支持

Visual Studio Code

你需要最新版本的VS code和VS code chrome调试插件

然后添加下面的代码到你的launch.json文件并且将它放到.vscode文件夹,在你的app根目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "0.2.0",
"configurations": [{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/src",
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
}
}]
}

注意:如果你更改了HOST或者PORT环境变量,URL的值会和上面的不同

运行npm start启用你的app,按F5或者点击绿色的调试图标,开始调试。你现在可以写代码,设断点,调试你新写的代码 —— 全部由编辑器自动处理。

WebStorm

略

自动格式化代码

Prettier是一个主动的代码格式化工具,支持js,css和JSON。使用Prettier可以自动格式化代码,保证工程内部代码格式一致。查看更多,请到Prettier github 官网,要看具体介绍,请到Prettier 官网

如果想要当我们git commit的时候就format代码,我们需要装载下面的依赖:

1
npm install --save husky lint-staged prettier

当然,也可以用yarn

1
yarn add husky lint-staged prettier

  • husky 使通过npm脚本使用githooks更加简单
  • lint-staged 允许我们对git 暂存区资料运行脚本。参考学习使用lint-staged的blog
  • prettier是一个js格式化工具,我们会在commits之前运行

现在我们可以确保所有文件会被正确的格式化,当我们在工程根目录的package.json中添加几行之后。

将下面的代码添加到scripts区域:

1
2
3
4
  "scripts": {
+ "precommit": "lint-staged",
"start": "react-scripts start",
"build": "react-scripts build",

下面我们添加一个lint-staged区域到package.json中,例如:

1
2
3
4
5
6
7
8
9
10
  "dependencies": {
// ...
},
+ "lint-staged": {
+ "src/**/*.{js,jsx,json,css}": [
+ "prettier --single-quote --write",
+ "git add"
+ ]
+ },
"scripts": {

现在,当你提交了一个commit,Prettier将会自动格式化变化的文件。第一次你还可以运行./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}"来格式化整个项目

下一步你可能想要将Prettier整合到你喜欢的编辑器。请阅读prettier编辑器接入

更新页面的<title>

你可以在生成工程的public文件夹找到HTML文件,然后编辑<title>标签,更新成你想要的。

注意一般情况下不需要频繁编辑public中的文件。例如,添加一个样式表不需要编辑public中的HTML就可以做到

如果你需要基于内容动态更新页面标题,可以使用浏览器document.titleAPI。或者更复杂的情景,当你想要根据React组件更新标题的时候,你可以使用React Helmet,一个第三方库

如果你再正式环境使用定制化服务并且想要页面在发送到浏览器之前更改标题,你可以遵循这个地方的建议。另外,可以预编译每个页面至静态html文件,参考资料在这里

装载依赖

生成的项目默认包含React和ReactDOM依赖。当然也包含一系列Create React App开发幻境用到的一系列依赖。你使用可以装在其他依赖(例如:React Router):

1
npm install --save react-router

当然还可以用yarn:

1
yarn add react-router

这种方式适用于任何库,不仅仅是react-router

引入组件

幸亏有Babel,这个项目支持ES6模块。

同时,你依然可以使用require()和module.exports,但我们建议你用import 和 export取代它。

例如:

Button.js

1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';

class Button extends Component {
render() {
// ...
}
}

export default Button; // Don’t forget to use export default!

DangerButton.js

1
2
3
4
5
6
7
8
9
10
import React, { Component } from 'react';
import Button from './Button'; // Import a component from another file

class DangerButton extends Component {
render() {
return <Button color="red" />;
}
}

export default DangerButton;

请认清 default和命名的exports之间的区别。这是常见的导致错误的根源。

我们建议,当exports仅仅一个东东的时候,使用default imports和exports(例如,一个组件)。

当一个实例需要export几个函数的时候,命名的exports就非常有用了。一个模块可以有至多一个default export和任意数量的命名export

学习更多ES6模块化:

  • 什么时候使用大括号
  • 探索ES6: 模块
  • 理解ES6: 模块

代码拆分

区别于每次加载全部的app代码之后,用户才能使用,代码拆分允许你将代码拆成小的代码块,按需加载

示例如下:

moduleA.js

1
2
3
const moduleA = `Hello`;

export { moduleA }

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react';

class App extends Component {
handleClick = () => {
import('./moduleA')
.then(({ moduleA }) => {
// Use moduleA
})
.catch(err => {
// Handle failure
});
};

render() {
return (
<div>
<button onClick={this.handleClick}>Load</button>
</div>
);
}
}

export default App;

这种写法,只有在用户点击‘load’button的时候,才会加载moduleA.js

你还可以使用async/await 语法,如果你喜欢用的话

结合React Router

如果你使用React Router进行代码拆分,请参照这个教程。还有相关的github repo

还有,看一下React文档中关于代码区分的部分

添加样式表

这个项目使用Webpack处理所有的样式。Webpack提供了一个定制化的方法,拓展了import概念,可以引入css。为了表明一个js依赖一个css,你需要 在js中import相应的css

Button.css

1
2
3
.Button {
padding: 20px;
}

Button.js

1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';
import './Button.css'; // Tell Webpack that Button.js uses these styles

class Button extends Component {
render() {
// You can use them as regular CSS styles
return <div className="Button" />;
}
}

这不是一个React必须的模式 但是许多人发现这种特性非常方便。但是你需要了解到,这样做会让你的代码在使用其他打包工具的时候,不是那么轻便。

在测试环境,使用这种方法表示依赖还会让你的样式更改,能飞快的通过reload来更新。在正式环境,所有的css文件会被结合成一个压缩过的.css文件

如果你担心使用webpack特殊语义不太好,可以把你所有的css放到src/index.css文件。这样只会被src/index.js引入,当你迁移其他打包工具的话,很简单。

css后处理

这个项目压缩css并且通过Autoprefixer自动添加前缀,所以你不必担心这些东东
例如:

1
2
3
4
5
.App {
display: flex;
flex-direction: row;
align-items: center;
}

1
2
3
4
5
6
7
8
9
10
11
12
.App {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}

如果你想禁掉自动添加前缀,请参考此处

添加css预处理(Sass,Less等)

通常来说,我们不推荐你在不同的组件中复用相同的class。例如,与其在<AcceptButton>和<RejectButton>中使用一个.Buttoncss类,我们更推荐创建一个<Button/>组件,拥有.Buttoncss类,这样可以在<AcceptButton>和<RejectButton>中render(但不要继承)

遵循以上规则,有时会让css预处理变得没多大用,主要是mixin和nesting被组件组合取代了。当然,如果你觉得预处理有用的话,可以完整的引入一个预处理。我们下面会使用sass演示,当然你可以使用less或其他类似的东东。

首先,命令行装载一个sass的依赖:

1
npm install --save node-sass-chokidar

当然也可以用yarn啦

1
yarn add node-sass-chokidar

然后在package.json中,把下面的几行加到scripts:

1
2
3
4
5
6
7

"scripts": {
+ "build-css": "node-sass-chokidar src/ -o src/",
+ "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",

注意:用其他预处理的时候,将watch-css和build-css命令替换相应预处理文档中描述的命令

现在你可以重命名src/App.css至src/App.scss,并运行npm run watch-css。监控器会监测src文件夹下面所有的sass文件,并紧挨着生成相应的css文件,在我们的例子里面就会覆盖原来的src/App.css。由于src/App.js引入的依然是src/App.css,所以样式依然会成为app的一部分。你现在可以编辑src/App.scss,然后src/App.css会自动更新。

想要在不同的sass文件间公用变量,你可以使用Sass imports。例如:src/App.scss和其他组件样式可以引入@import "./shared.scss";然后可以公用其中的变量定义。

为了使用绝对路径引入文件,你可以在package.json的命令中加入--include-path选项。

1
2
"build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
"watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive",

这样可以让你像下面那样引入样式

1
2
@import 'styles/_colors.scss'; // assuming a styles directory under src/
@import 'nprogress/nprogress'; // importing a css file from the nprogress node module

这时候你可能想要让git忽略所有css文件,所以你可以在.gitignore中加入src/**/*.css。

最后,你会发现当npm start的时候,自动watch-css非常方便,还有就是npm run build的时候build-css。你可以用一个&&操作符来分别执行两个脚本。但是没有一个跨平台的方法可以并行运行两个脚本命令,所以我们要装载一个npm包:

1
npm install --save npm-run-all

或者使用yarn

1
yarn add npm-run-all

然后我们可以更改start和build命令,使其包含css预处理的命令:

1
2
3
4
5
6
7
8
9
10
11
12
   "scripts": {
"build-css": "node-sass-chokidar src/ -o src/",
"watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
- "start": "react-scripts start",
- "build": "react-scripts build",
+ "start-js": "react-scripts start",
+ "start": "npm-run-all -p watch-css start-js",
+ "build-js": "react-scripts build",
+ "build": "npm-run-all build-css build-js",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}

现在运行npm start和npm run build同时可以打包Sass文件。

为什么使用node-sass-chokibar

node-sass有下面一些issues:

  • node-sass --watch在虚拟机或者docker环境下有性能issues
  • 不断的编译#1939
  • 检测到文件夹下面的新文件时,会有issues,#1891

添加图片,文字,和文件

用webpack打包,类似于图片和文字这样的静态资源的方式和css非常类似

你可以在 一个js模块中import一个文件。这相当于告诉webpack打包的时候将该文件包含在内。和引入css不同,引入文件拿到的是一个字符串值。这个值是最终关联到你的代码中的最终路径。例如:image的src属性或PDF的href属性。

为了减少服务端的请求,少于10000字节的图片会返回一个data URI而不是一个路径。这个默认设置会对下面的文件生效:bmp,gif,jpg,jpeg,和png。SVG文件不包含在内,因为#1153

这里是一个例子:

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import logo from './logo.png'; // Tell Webpack this JS file uses this image

console.log(logo); // /logo.84287d09.png

function Header() {
// Import result is the URL of your image
return <img src={logo} alt="Logo" />;
}

export default Header;

这样保证当工程被打包的时候,Webpack将会把图片放到build文件夹下,并且给我们提供正确的路径。

在css中也是一样的:

1
2
3
.Logo {
background-image: url(./logo.png);
}

webpack会找到所有css里面的相对路径并且编译的时候将他们替换成最终的路径。如果你拼写错误或者不小心删掉了一个文件,你将会看到一个编译错误,就像你引入了一个不存在的Javascript模块一样。最终的文件名会包含webpack生成的内容hash值。如果文件内容更新的话,webpack会更新文件名,所以不用担心资源的持久化缓存。

请认清一点,这只不过是webpack的一个定制化特性

这种方式并不是React必需的,但是许多人喜欢用它(React Native用了类似的图片处理技术)。另外一种处理静态资源的方法将会在下一节描述

使用public文件夹

注意:这个特性在react@0.5.0版本或以上可用

更新public文件夹的html

html就在public文件夹下面,所以你可以动它,例如,设置标题。包含编译代码的<script />标签在编译过程中会被自动加进html。

在模块外部添加静态资源

你可以添加其他静态资源到public文件夹

注意我们一般建议在js文件中import静态资源。例如:添加样式表,添加图片,文字。这种技术可以提供如下优势:

  • 脚本和样式被压缩和打包到了一起,减少额外的网络请求
  • 文件少了之后,直接在编译阶段报错,而不是曝露给用户404
  • 文件名包含内容哈希,所以不必担心浏览器缓存它的旧版本

然而依然有另外一个 出口,你可以在模块系统外部添加静态资源。

如果你将一个文件放到public文件夹,它不会被webpack处理。另外就是它会原封不动地被复制到build文件夹中。为了关联public文件夹下面的静态资源,你需要使用一个特殊的变量-PUBLIC_URL

在index.html中,你可以像下面一样使用它:

1
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

只有包含在public文件夹下面才会被%PUBLIC_URL%前缀解析到。如果你需要使用src或node_modules下面的文件,你必须复制它到public文件夹,并详细说明你的意图。

当你运行npm run build,Create React App会将%PUBLIC_URL%替换成一个正确的绝对路径,因此,即使使用客户端路由,工程也能正常运行。

在js代码中,你可以使用process.env.PUBLIC_URL,达到相同的目的:

1
2
3
4
5
6
render() {
// Note: this is an escape hatch and should be used sparingly!
// Normally we recommend using `import` for getting asset URLs
// as described in “Adding Images and Fonts” above this section.
return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;
}

请注意以下几点:

  • 在public文件夹下面不会被预处理或者压缩
  • 如果文件不存在,编译的时候并不会告知用户,并且会抛出一个404给用户
  • 这里的文件名不会包含hash值,所以每次文件改动之后需要加一个查询参数,或者更换文件名

什么时候使用public文件夹

一般情况下我们推荐从js中引入样式表,图片和字体。public文件夹用于处理一些不常见的case。

12…7

乱世天狼

乱世天狼 | 前端 | html | css | html5 | css3 | react | redux | webpack | 哲学 | 精神 | 生活

67 日志
48 标签
© 2020 乱世天狼
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4