React 事件绑定的正确姿势

最近在写 React,对于将一个事件处理函数绑定到按钮上,总是有一种感觉:这么写也行,那么写也行。所以就参考了文档,整理了一下各种写法。主要是参考 React 的文档来写的,非常基础,非 React 的入门同学,这篇文章可以不看了。

绑定事件最基本的做法如下:

第 18 行将 this.handleClick 绑定到了按钮上。注意这里第 7 行在类的初始化函数中有一个绑定 this 的操作,这个是必须的。如果不写,那么 handleClick() 函数中的 this 将会是 undefined (这个是 JS 的特性,不是 React 的)。在 console 中会看到错误如下:

如果你写过 jQuery,从这个例子中可以看出,和 jQuery 绑定事件的区别如下:

  1. 不是传入一个 string ,而是函数
  2. 组织默认的形式不是 return false 而是 e.preventDefault()
  3. jquery 是小写命名,React 是驼峰命名
  4. 不要往 DOM 上面去添加 Listener,而是在 render 的时候提供 listener

如果不想每次在初始化函数中写一个 bind,可以有两种方案:

第一种,将函数定义为 class 的一个 field:

第二种,绑定的时候定义一个匿名函数(箭头函数),将匿名函数与元素绑定,而不是直接绑定事件处理函数,这样 this 在匿名函数中就不是 undefined 了:

第二种方法有一个问题,每次 LoggingButton 渲染的时候都会创建一个新的 callback 匿名函数。在这个例子中没有问题,但是如果将这个 onClick 的值作为 props 传给子组件的时候,将会导致子组件重新 render,所以不推荐。

Bind 函数的时候传递参数,下面两种写法都可以(推荐第二种):

在第二种中,e 代表 React Event,将会自动作为第二个参数。

 

2018年年鉴

每年年末的时候我都会写写东西,总结一下今年过的如何。今年元旦的假期在南京旅行,每天都玩的很累。就现在补上吧。

工作

今年换了工作,算是比较大的一件事。从以前写爬虫和后端变成了一个 SRE。新的工作有很多挑战,有技术上的,也有沟通方面的。工作方式发生了很大的变化,以前的工作非常纯粹,我们有专业的 PM 和设计,我只管完成需求,写代码就好了。工作也非常轻松,每天都六点下班,工作之余还能看看自己感兴趣的东西。同事们也很专业,没有什么可挑剔的,我们有 Code Review,有基于 Python 的技术栈,代码有测试。每天工作都很开心。技术进步也很快,在 Code Review 中大家会互相指出错误,这是非常宝贵的财富。大家经常玩新鲜的东西,看起来不错的可以用到我们自己的产品中。我很感激在前公司的这一年。

来蚂蚁金服之后,从以前工作是 100% 的编程,变成了有 50% 的时间在沟通,50% 的时间在编程。开会的时间很多,但不是所有的会都是有意义的。以前公司人少,无论找谁喊一声就行了,现在找人很麻烦,不是每一个人都回复很及时,更多的情况是他会让你去找另一个人,这样找来找去,最后找过五六个人还没解决一个很简单的问题是很正常的。而文档、帮助维护的又不是很好,找人问又成了一个常态,所以这对我来说成了一个很大的困扰。最近正好看到一个理论,讲的是一个组织做出来的东西,实际上是这个组织沟通方式的体现。

“organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.”

— M. Conway

我在公司内部的宣传中看到一个小姑娘说,自己进公司之前是比较内向的一个人,进公司三年后变成了一个怼天怼地的女汉子。这个公司比较欣赏这样的人,可我觉得很反感。你工作中怼的都是你的同事啊,大家低头不见的,怼来怼去这样好吗?大家在一起和和气气的工作,每天都开开心心的不好吗?为什么喜欢吵来吵去呢。

当然了,蚂蚁金服是很大一家公司,我觉得这个公司的任何一个人,都不能描述出这个公司是什么样子的,顶多是盲人摸象。每个部门,每个团队都不一样。我也不认为这些问题是蚂蚁金服存在的,不同的公司会有不同的问题,也不见得其他公司没有我所说的这些问题。说这么多负面的地方,原因是我对这家公司的期望是挺高的,结果来了之后发现并不是我想象的那样。不过好的是,我可以有机会去改变那些不好的地方。

话说回来,这一年做 SRE,一个难得的经历是有许许多多各种各样的故障可以看,可以参加 Review。每一个故障都是新的,有些很蠢,有些很有意思,我都感到惊讶,这样隐蔽的故障都能这么快找到原因。遗憾的是,我还没有亲手第一个找到过一个故障的根本原因。

关于加班,下半年基本上都 10 点之后下班。工作时间变得很长,也是让我困惑的一个地方。没有时间学习了,换工作之后,自己的技术成长明显没有以前快了,开发的效率也变低了。以前九点上班,6点下班,我能写很多代码,完成很多任务。现在一天工作10个小时以上,做的事情却没有以前多。幸运的是,现在住的离公司近,上下班很方便,晚上每天都加班,打车公司报销。花在路上的时间没有那么多了。以前我竟然每天花 4 个小时上下班!

明年想办法多留给自己一些时间吧,空闲的时间是真正长知识的时间。我见过一些同事,基本的计算机常识都没有,还停留在毕业生的水平,感觉就是毕业之后一直没有时间学习导致的。学习基本的原理(虽然工作中用不到),了解你使用的工具,这其实是会让你事半功倍的东西,可惜很多人看不到这一点。

开源和学习

今年开始接触了 openresty 和 lua,很有意思的一个领域。在读 PIL 但是还没有读完,PIL4 已经比第一版厚很多了。openresty 社区的人非常友好,从文档就看出来了,无论谁写的库都是非常标准的风格,Toc,简介,函数文档。明年继续深入学习一下这方面,多看看库的源代码。

openresty 的一个模块的控制台我是用 starlette 写的,一个基于 asyncio 的很小的框架。顺便看了它的源代码,提交了两个关于 staticfile 的 patch。后来没时间搞了。

自己翻译的这本书,已经很长时间了还没结束,今年进度已经到了大约 2/3 了,争取最近就完结它。

另外看了很多 Kubernetes 的资料和相关的软件,但是一直没机会用。我有一个不错的 idea,已经放了一年没有下手干了,不过短期看来,我基本没有时间做自己喜欢的事情。

社区

来了杭州之后,在杭州组织了 4 次 Python 社区的 Meetup。听了很多有意思的分享,学到很多东西。这个事情是很有意义的,希望明年把它继续下去,时间是一个大问题。

10 月份去主持了北京 PyCon 的语言特性专场,今年的分享特别精彩,认识了很多大神,打开了很多新世界的大门。去上海 PyCon 做了一个很入门的分享,反响还算不错。今年还发了很多 Tweets,平时看到有意思的东西都会转发一下。明年争取继续在社区中学习吧,也多分享自己的东西。

生活

今年的生活没有太大的变化,除了在公司的时间变多了。我尝试过健身来着,不过全年加起来估计才有 10 次左右吧,明年坚持一下。越来越觉得,健康才是最重要的,也许我开始变老了。

希望明年少浪费一些时间,多做一些有意义的事情。多学一些知识,多看书,少说话。

往年:

  1. 2013年
  2. 2014年
  3. 2015年
  4. 2016年
  5. 2017年 (看了一下2017年定下的4个目标,很好,一个都就没完成)
 

SRE&Devops 每周分享 Issue #6 Closing

Hi,这是这一系列的最后一篇内容,之后不会再每周提供定时发布。遇到有意思的文章我会分享在 Twitter 上,这种方式更加实时,也比较有互动性。这是我的 Twitter: laixintao 。以下是本期内容。

 

Ubuntu 发布的 《上云白皮书》

现在可选的云服务多种多样,公有云、私有云、混合云,还有不同的厂商提供了不同的服务,从 Bare mental Server 到 VPS 到容器服务。提供 Infrastructure 的经常还伴随着提供上层的服务。Ubuntu 发布的这个 PDF 介绍了各种云的概念,推荐一下,了解之后可以针对自己的需求和规模选择最合适的场景。

Envoy Proxy at Reddit

Reddit 的用户越来越多,服务规模也越来越大。Reddit 使用 Envoy 作为四层/七层负载均衡服务器,本文介绍了他们的方案。

Build a serverless Twitter reader using AWS Fargate

AWS 的一篇 serverless 手把手教程。

The Definitive PHP 5.6, 7.0, 7.1, 7.2 & 7.3 Benchmarks (2019)

最新版本的 PHP 的一些性能测试。

Using Golang to Build Microservices at The Economist: A Retrospective

经济学人使用 Go 语言来构建微服务的历史。

Why on earth did we choose Jenkins for 2019?

介绍了 Jenkins 的一些优点。

新闻:Red Hat 将 etcd 捐赠给了 CNAB

HOW DASHBOARDS ARE CHANGING HUMAN BEHAVIOR IN DEVOPS

Dashboard 怎么用,怎么设计,这是一个哲学。

Our learnings from adopting GraphQL

Netflix 介绍的使用 GraphQL 的经验。

rendora/rendora

给爬虫渲染出页面的一个项目。(使用无界面 Chrome 浏览器)

bloomberg/goldpinger

Debug k8s 的一个工具。

 

SRE&Devops 每周分享 Issue #5

这个周工作比较忙,分享的东西不多。

 

The headers we don’t want

介绍了几类被误用和滥用的 Header。有些不错的干货的,让我惊讶的是很明显用错 Header 的网站竟然有这么多,还长达 20 多年。不过有些观点我不同意本文,比如 Response 放上 Server,我觉得是有用的,比如对互联网上的统计,测试收集各种服务器的性能等。

Getting started with Jenkins X

Jenkins Kubernetes plugin 已经可以让 Jenkins 跑在 k8s 上了,Jenkins X 是持续集成 k8s 应用的一个方案,并且 Jenkins 本身要跑在 k8s 上。

​GitOps – Operations by Pull Request

Git 可以追踪所有的变更历史,可以轻松的回滚,使用 PullRequest 机制可以互相 Review。所以我一直想,如果用 Git 来做配置中心,或者将所有的线上操作通过 Git 来追踪(我上一家公司使用 Salt 就是这么做的),可以省很多事。

原来已经有人将这个想法实践了。这篇文章介绍了 Waveworks 基于 Git 的运维。我觉得本文能这么做最重要的一点是:运维工具必须是声明式的,表达一个最终状态,像 Ansible 那样。

这样可以使所有的操作都透明化,最终达到的一个效果是,文中提到他们有一次不小心将 AWS 上所有的节点都删除了,只用了仅仅 45 分钟,就恢复了回来。

deislabs/cnab-spec

CNAB:一份开源的、独立于云平台的规范,包括如何打包、运行分布式的应用。

DOCKER APP AND CNAB

Docker App 是遵循 CNAB 标注标准的工具,可以 build 符合 CNAB 标准的 bundle,也可以用来运行、升级 Bundle。

Announcing GitLab Serverless

Gitlab 宣布将在 12月22日上线 Serverless 服务。

 

Nginx(ngx_lua) 过滤 10w 个 User ID

今天的工作太刺激了,一天下来正好解决了一个有意思的问题。晚上来记录一下。

上次解决了当有很大的 HTTP body,在 ngx_lua 里面读不到的情况后,还留下一个解决性能问题。上次提到,我们对于用户的每一个请求,都要根据一个 json 形式的规则,来判断怎么样路由这个用户。为了让读者更明白这个问题,举几个例子:

  1. 给出一个 10万元素的用户 ID 列表,如果用户的 ID 在这里面,并且请求 URL 是 xx,Cookie 含有 xx,就转发到 Server A
  2. 用户 ID 在列表转发出 10% 的用户到 Server B

此模块我是用 ngx_lua 写的,现在有问题的实现是这样的,将这个规则保存在 ngx.shared_dict 里面。每一个请求过来,我就解析成 lua 的 table,然后判断规则。我的测试环境是一台内网的服务器,单进程开 Nginx,wrk 测试是 1800~1900 request/sec。开启这个模块之后,只有 88 requests/sec,由于每一个请求都要经过这个模块,这样的延迟是无法接受的。

规则是发到每台机器的 Nginx 上,要在 Nginx 所有的 work process 共享一个变量,不知道除了 shared_dict 还有啥方法。其实我想过自己基于 shared_dict 实现保存 table,就是我把 table 打扁平,按照 key-value 放到 shared_dict,但是这项工作想想就挺大的,而且要踩坑才能保证正确性。

今天发了一个邮件到 openresty 社区(这个社区非常活跃和友好!),问了这个问题。mrluanma 和 tokers 回复说可以用 mlcache 。其实我之前也看了一下这个项目,但是没有看完文档,不知道靠不靠谱,既然大家可以说这么用,就去试一试了。

这其实是一个缓存,首先 L1 缓存是每一个 Nginx 进程里面的 Lua vm 会有缓存,如果没有命中,那么第二层缓存就是 ngx.shared_dict ,如果再没有命中,就会调用用户的 callback,也就是所谓的 L3. 由于是一个缓存项目,所以有一些缓存方面的问题,比如 dog pile,此模块都处理好了。我的用法比较特殊,只是拿它来做多个 worker/多个 HTTP requests 的共享数据,所以很多地方没有细看。

新建一个 cache 的 Nginx 代码如下,需要写在 http 里面。

这里要注意 3 个地方:

  1. 因为要调用 set() 方法,我们是主动更新规则的,而不是等他过期。调用 set() 和 update() 要提供 worker 之间通讯的方式。mlcache 实现了通过 shared_dict 来通讯,所以我只要另外申请一个 shared_dict ,然后将这个 shared_dict 设置给 ipc_shm 就好了。
  2. ttl 和 neg_ttl 设置成 0 ,理由同上。
  3. 通过 _G 可以执行全局的变量,这样 lua 就可以直接使用 cache 这个名字了。

然后在设置规则的时候,直接通过 cache 来调用即可:

读取规则也是一样:

今天栽在这里很长时间,文档说第二个参数是 optional 的,我以为就可以不填。然后 set() 就填了两个变量。结果调试半天(Lua 奇葩的变量不够 nil 来补)。后来才明白这个 optional 的意思是你可以填一个 nil 进去,因为未定义的变量就是 nil 啊!难怪呢,我还想 lua 怎么实现的,难不成判断函数调用的参数个数?

另外一个点是 get() 方法要提供一个 callback 函数,L2 没有命中的时候提供就执行这个函数。在我这里,如果 L2 是 nil,那么 L3 也返回 nil 好了。

显示调用 set() 的一个非常重要的点是:一定要通知其他 worker 删除 L1 缓存。不然我们调用 set() 只是更新了一个 worker 的 L1 和 L2。在本文的场景下,worker 的数据不一致导致转发规则不一致是有问题的。这里只要在 set() 的之后调用一下 purge() 之后,通知其他 L1 去删除自己的缓存就可以了。 这里之前写的 purge() 函数的使用是有错误的,purge() 是清除缓存,包括用于 worker 交流的 shm 和 lua-resty-lrucache 的缓存,导致所有 L1 和 L2 miss 然后去 L3 更新,所以开销是比较大的(虽然在我这里,整个缓存=我的一个 table)。

正确的用法是这样的,使用 set() 的话,要多加一步,在 get() 前面调用一下 update() 。从源代码和文档得知,它的工作原理是这样的:set() 内置会广播一条消息,然后更新 L1 缓存(仅自己的 worker)和 L2 缓存。get() 之前 update() 这个调用会队列里面的广播事件,如果有事件的话,就先消费掉事件,没有事件的话,就什么也不做。注意所谓的 update 并不是更新 L2 缓存,而是消费所有的事件的意思。这样就做到一个 worker 更新某个值之后保持和其他 worker 一致了。

下图的第一个 worker 先 set 了一个值,然后通过 shared_dict 广播出去这个值的 name,回调函数是从 L1 删除这个值。其他 worker 蓝色的箭头表示 get() 之前 update() 去检查是否有事件需要处理。

修改之后性能从 88 requests/sec 上升到 300+ requests/sec,所有提升,但还是很慢。平均下来一个请求的延迟增加 3ms 多。

然后我又顺着这 10w userid 进行优化,规则里面这个 uid 是一个很大的 List,所以逻辑上是遍历查找一个用户是不是在列表里面的,O(n) 的效率。主要的耗时点就在这里。我想改成用 Set 结构来存,这样只要 O(1) 复杂度就够了。

问题是,Json 只有 Dict 和 List 两种数据结构,这个回答说的很好:

  1. 编程实现 List 和 Set 互相转换是很简单的
  2. Json 用于数据交换,你不能信任一个数据输入是 Uniq 的

但是我觉得 Json 增加 {"foo", "bar", "banana"} 这种形式好像没有什么不妥。

Anyway,我只能自己实现了,写了一个函数,在 Json 转换成 table 之后,找到我想转换的 Key,将它的 value 从 array 形式的 table(key 是 1 2 3 4 5 …)改成 Set 形式的 table (key 是各个元素,value 为 true),代码如下:

这样,在找的时候,只要看 value 是不是 true 就可以了:

这样改了之后,性能上升到 1800 requests/sec,跟不开启这个模块相比,基本上没有性能损耗了。

 

话说回来,这个问题跟我上一家公司的面试我的时候出的题目很像:给你一个 IP 列表,内存可以随便用,但是查找速度要快,如何看一个 IP 是否在表中?

我当时的方法是,一个 IP 用一个 bit ,bit 位要么是 0 要么是 1,表示此 IP 在或不在。表示世界上所有的 IP(IPv4)需要 2^32 个bit = 536M,将 IP 列表中的 bit 都置为 1,其余为0. 这里的关键是 IP 如何映射成 bit 表,IP 其实是 4 个字节而已,直接用 4 个字节所表示的数字作为 index 就好了。

巧合的是,这个周我正好认识了布隆过滤器。哈,我的想法真先进。