Category Archives: Uncategorized

gin并发的小坑及解决

最近遇到了一个问题,背景是这样的。设备端会发送一张图片到服务器上,然后服务器分析这张图片并且上传到七牛云上,然后再保存成一个事件,结果发现总会有一个设备的图片被串到别的设备的事件当中。

本以为这是七牛云的bug,因为我的文件的后缀都是一串时间戳.jpg,只有前面的url会不一样,猜测是因为七牛云只对后面多少位长度的字符串做截取处理,所以造成了存储别的字符串。

后来发现是这个原因,gin的处理函数通常是用一个*gin.Context类型的参数,上传图片我开了个新的goroutine去做,会把这个指针直接传给goroutine,因为我接受设备的事件是个异步操作,所以这个context在gin调度的时候会认为已经用完,分配新的请求的内容到这个地址,于是上传时用的指针指向的内容是另外一个值,造成了上传图片错误,在并发数量大的情况下会发生这样的事情。所以修改为了按值传递参数,在goroutine执行时,会生成一个副本,就不会串了。

goroutine有2点需要注意的。

  1. 传递指针时,需要保证,这个指针不会被别的代码所改变
  2. goroutine可以用的内存不多,如果选择按值传递参数的话,需要保证那块内容的内存足够的小,这样才能发挥goroutine的优势。

这篇东西觉得有必要写下来,缺失了一些信息,但写给自己看。

postgresql操作json数据心得

0x00 背景

最近解决了一个很有趣的问题,想在博客上分享一下,开始做crud工程师(笑)了以后,自然是渐渐地又熟悉起了sql。公司的项目正在使用postgresql,然后项目中有一个报表的数据模型,主要存储了各个时间段内来访人员的情况,其中有一项年龄分布的数据,因为年龄很多量大,需要用到jsonb的数据结构去存储。本文的故事就将会针对json的数据库操作展开讲讲。

0x01 问题

现在存在这样一个业务场景,很有可能同时有多个人与一个设备产生互动,然后设备将数据发送至服务器,有可能同时就会有多个数据来访数据产生。

原先统计报表时是这么做的,先用查询语句查询出目前的年龄分布json,然后在内存中计算完成后,用update语句去更新数据。看起来非常平常简单的操作,但是在并发多的情况下会出现问题。

比如两个routine几乎同时收到一个来访客人记录分别为20岁和22岁。未更新前,两个routine都会查到一个原来的值,例如{“20”:1, “21”:1, “22”:1},表示20,21,22岁各有一人。

两个routine分别计算,

新来访客人为20岁的,就会在update语句中把更新值写成{“20”:2, “21”:1, “22”:1}

新来访客人为22岁的,就会在update语句中把更新值写成{“20”:1, “21”: 1, “22”: 2}

因为总会有先后顺序,最终存储的结果必然为两个结果中的一个,但上面的结果都不对,结果应该为{“20”:2, “21”:1, “22”:2}

所以,需要用一种方式直接摒弃前面的查询语句,直接在一句update语句中完成这件+1的事,这样才能保证数据递增操作的原子性。

0x02 json如何实现递增?

我们把问题分解一下,这是一个update语句,并且等号的两边一边是json数据的那一列,另一边是给某值+1。

所以两个步骤

  1. 把json中某一年龄的值取出来
  2. +1后塞回一个json

查询了一下postgregsql官方文档,发现分别有以下的函数或操作符能够完成以下操作。

->>操作符,可以做到取出这个json里面特定值的作用

jsonb_set函数能够改变一个json的值。

具体的用途可以查询文档得到,https://www.postgresql.org/docs/9.6/static/functions-json.html

于是我们就得到了如下语句(以来了一个23岁的用户为例):

update report_events set age_count = jsonb_set(age_count, '{23}', (to_char(to_number(age_count->>'23', '999') + 1, '999'))::jsonb, true)

因为->>取出来的是字符串类型的结果,所以需要to_number函数先转为数字,再+1,然后再想方设法转为jsonb类型,才能用jsonb_set赋值到原来的jsonb上。

0x03 case when语句的使用

上述的语句看起来非常不错,解决了我之前的问题,但还是有两个致命问题:

  1. 当原数据中没有23这个key的时候会取到null,导致计算结果也为null,会造成整个json的数据被擦除。
  2. 原数据本身为null的时候也不能用->>取值

想起来sql里面有一种case when语句的用法,其用途就是在不同情况下可以select或update不同的值,于是修改了上一步的操作。


update report_events set age_count = (
case when age_count is NULL then
'{"23": 1}'::jsonb
when age_count->>'23' is not NULL then
jsonb_set(age_count, '{23}', (to_char(to_number(age_count->>'23', '999') + 1, '999'))::jsonb, true)
else
jsonb_set(age_count, '{23}', '1'::jsonb, true)
end
);

上述语句就是进行了2种缺陷场景的考虑最终该写的句子,在测试环境中没有问题,改吧改吧上线了。

0x04 版本差异

我没有注意我看到的文档是9.5版本的,测试环境是一个比较新的版本,阿里云的生产环境却是9.4的版本,上面的sql语句造成了一些语法错误。

于是我对比了以下,我惊讶地发现9.4版本没有上述的jsonb_set函数,还好有帮助文档提供了解决方法。可以启用一个扩展。

create extension jsonbx;

不过似乎还是有其他问题,根据反复尝试,我发现->>操作符不能在case when后使用,于是我就又在找其他替代的方案,找到了?操作符,这个操作符就是判断这个key是不是存在于这个json的顶级key中。正是我们想要的,所以最终sql语句就变成了:
update report_events set age_count = (
case when age_count is NULL then '{"29": 1}'::jsonb
when age_count?'29' then jsonb_set(age_count, '{29}', (to_char(to_number(age_count->>'29', '999') + 1, '999'))::jsonb)
else jsonb_set(age_count, '{29}', '1'::jsonb) end );

 真是百转千折呀。

记一次服务部署遇到的问题

0x00 背景

最近呢在用golang进行开发,在前人的基础上维护一个后端的项目。
项目启动了一个web server,放在daocloud上使用容器化的部署方式,写一个Dockerfile去serve。
根据前人的Dockerfile,步骤主要如下:
1. 用apt-get下载nginx
2. 用go get下载govendor(golang的一个包管理器)
3. 使用govendor下载vendor.json下的所有依赖
4. 在容器内启动server
5. 将项目中写好的nginx配置文件拷贝到/etc/nginx/目录下,覆盖了sites-enabled/default文件
6. 启动nginx反向代理

0x01 引入grpc

因为有2套业务系统,目前系统间通信用的是RESTful接口,想用一种更高效的通信方式。
于是让新来的同事研究了grpc,并且实现了一个原来的接口,grpc基于HTTP2,可以实现更高性能的服务。
当grpc server完成后,我看到nginx也支持配置grpc的代理,所以想尝试一下。
搜索到了一个文档是这样配置的。

server {
listen 80 http2;
access_log logs/access.log main;
location / {
grpc_pass grpc://localhost:50051;
}}

于是我加进了项目中的配置文件,push到库上
但是发现nginx挂了,报出错误说没有grpc_pass这个指令。

于是我又查了错误信息,原来nginx是从1.13版本开始支持grpc这个功能的,
输入nginx -v查看容器中的nginx版本,为1.10

0x02 在容器中安装新版nginx

到nginx官网上寻找支持,找到了一篇文章,可以配置一些源,安装nginx
于是我在Dockerfile里在安装nginx前加上了以下语句

RUN wget http://nginx.org/keys/nginx_signing.key; apt-key add nginx_signing.key;
RUN echo 'deb http://nginx.org/packages/debian/ stretch nginx' >> /etc/apt/sources.list
RUN echo 'deb-src http://nginx.org/packages/debian/ stretch nginx' >> /etc/apt/sources.list

注: stretch是Debian 9 的codename,我使用的golang1.9.4的镜像操作系统是Debian 9,
我一开始以为是Ubuntu16.04,写的是xenial,结果一直在安装时报错,说libssl不符合要求,依赖无法安装之类的报错。

0x03 反向代理失效的解决

修改完上一步之后,非常完美,容器没有报错,nginx没有报错,服务的程序没有报错,但就是API服务访问不了。。。
一直以为是自己程序上的错误,所以检查了又检查,甚至回滚到了没有grpc前的版本,依然连不上。
在容器内访问对应端口,ok,代理出去的端口访问,失败了。
于是我在google上搜索proxy pass doesn’t work,找到个stackoverflow的问题,给了我一点启发。

为什么简单的proxy pass不生效?
这个链接的答案是说要删除掉一些nginx.conf中的include语句,使得一些默认的80端口不生效。

这使我想到要仔细查nginx的配置文件,从nginx.conf文件开始查,果然发现1.10版本nginx和1.14版本nginx默认的配置不一样,
1.10会默认带上sites-enabled/default这个文件,
而1.14版本却不会带上这个文件,所以我复制的文件根本不在生效的配置里,反向代理自然没有生效。

于是我在nginx.conf中增加了include sites-enabled/default,整个问题终于解决。

tooltip的z-index失效问题

See the Pen ELgXJN by Ni (@nisiyu) on CodePen.


前些天做了个网页,tooltip显示时发现tooltip总会盖在别的元素下方,纵使我给tooltip加了很高的z-index,还是会显示在下面。

本以为是第三方组件的问题,但是我发现同样的问题不会在别的地方出现,所以我猜测有某些样式会影响到z-index让其失效,当我一个个style的勾选框去掉时,发现tooltip的父元素去掉transform属性时z-index会失效。这里直接给出codepen。

  1. 原来我遇到的那个问题是因为用了translate来使元素居中,但其实可以有别的居中方法。所以需要减少不必要的transform。
  2. 实在难以居中要用这个方式时,还是只能规避,用js去计算位置,就可以避免transform。
  3. 要用z-index时,需要检查一下代码,父级元素中是否有transform同时出现。

爬虫解决方案探索

最近呢,我有点愁该如何送我的女神礼物,于是就在想自己设计一个推荐算法,想着先抓取市场上的美妆产品,然后拿下来搞点事情。

选择了考拉海购和小红书,这期间也遇到了一些些小问题,学习到了一些基础的爬虫技术,拿出来分享一下。

因为之前有人跟我说了一下想爬点评的数据,我在github上调研过一下相关的crawler,找到了一个点评爬虫,然后从这份代码中学习一点东西,应用在自己的代码中。

本文将主要讲述一些不需要登录就能够获取到的公共信息的抓取方法和工具,给出一些推荐的理由以及工具的对比。

0x01 puppeteer

29446482-04f7036a-841f-11e7-9872-91d1fc2ea683

https://github.com/GoogleChrome/puppeteer

puppeteer是一款chrome浏览器的nodejs模拟器,可以用它的api,在命令行进行浏览器的操作。puppeteer并不能算作是一款爬虫工具,比爬虫来说,应该更加强大一些,也可以做些跟前端e2e测试有关的事情。

一个电商网站通常是由很多请求组成的,一般都会先请求HTML文档,然后会请求样式CSS文件和JS文件,然后JS文件里面会发起请求一些API,然后在HTML文档上做操作,丰富网页的内容和形式。

用puppeteer有一个好处是,它可以模拟真正的浏览器操作,因此可以抓取到用户真正看到的数据。

安装puppeteer需要下载chromium,所以会从Google网站上下载,所以国内用户可以通过cnpm来进行下载,这样会从一个国内源下载chromium。

但是实际用的时候效果并不好,我选择了抓取考拉海购的一个商品列表,puppeteer会长期无响应。可能是因为浏览器在不断根据滚动请求更多商品数据,不会停止,而且也下载了很多css文件,耗时很长。

  • 使用语言:nodejs
  • 用途:仿真模拟浏览器操作
  • 优点:可以抓出js渲染出的数据
  • 缺点:需要下载的数据量很大

0x02 requests

requests-sidebar

http://docs.python-requests.org/en/master/

requests是一个常用的Python库,可以用于发送HTTP/HTTPS请求。Python标准库里有一个urllib,不过API不够人性化,所以有人就开发了requests,宣称是HTTP for humans。

使用方式就是先建立一下Session,然后使用Session的.get()方法请求对应url

  • 使用语言:Python
  • 用途:发起HTTP请求
  • 优点:简单易用
  • 缺点:作为一个发包器没啥缺点

0x03 beautifulSoup

10.1

https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

抓到原始数据后,只是一个html文档或者一个json串,这时候还不太容易直接从html文档中提取数据,因此需要一个工具通过操作API,能够迅速定位到文档中的特定位置的信息。beautifulSoup就是一款这样的工具,可以用简单的API操作读取html文档或者xml文档特定节点的特定数据。

使用BeautifulSoup时,感觉跟xPath有点像,然后就查阅了一点资料,发现却是有拿这个东西跟xPath的库lxml相比较的,得出的结论就是beautifulSoup更加易用,但是效率较低。

此外还有一个问题就是,我在抓取小红书的数据时,商品详情中的数据都是存储在一个INITIAL_SSR_STATE的js变量中的,这段js的数据是后端生成的,所以发现是个标准的json字符串,可以直接截取下来用Python的json库读出来。但是如果它不标准的话,或者是其他js代码,可能用beautifulSoup没有多大用,这一点,看了一个帖子里面提到了selenium这个前端测试工具,感觉可以用来读js内容。

后来又发现nodejs的cheerio包很适合做这项工作,特别是知道用$操作selector,不过没有尝试。

  • 使用语言:Python
  • 用途:解析HTML内容
  • 优点:简单易用
  • 缺点:效率低,对js没有很好的解决办法

0x04 Tor

tor-logo

https://www.torproject.org/

在抓小红书商品数据时,我先获取了一些商品id,然后继续请求商品数据时,商品抓了20个,突然发现所有的商品抓取全都403了,原来是被反爬虫机制抓住了,直接被封禁了IP,当时我直接用了公司的wifi访问的,公司的wifi就一下子上不了小红书了,手机APP也打不开,换到4G才行,惊了一身汗,还好过了几个小时就恢复了,于是就想小心翼翼地去挂代理。

一开始先找了些免费代理的网站,但效果都极差。于是我想到了以前做安全时听说过的工具Tor,可以用于匿名浏览网页,然后搜索了一下相关的东西,果然找到了一篇文章。

http://www.cnblogs.com/likeli/p/5719230.html

洋葱一层层的,原理上应该就是跳了多级代理的方式,根据文章中的教程挂上了以后就会1分钟换1个代理,只要结合privoxy在requests库中指定本地开启的socks代理就可以了,查询了一下自己的IP,发现已经到了世界的一个随机的角落。

不过很可惜的是,这款工具“无法在中国大陆”使用,我也是为此特地“出国”才用上的,可惜可惜,想用这款工具的同学,需要自备护照,签证和机票。

屏幕快照 2018-03-29 下午5.30.23

查询了一下自己的IP,发现自己已经穿越到了德国,这个网站的提示也非常嘲讽:马上来人接您了!

所以看到的同学千万不要用这个方法干违法乱纪的事,要找还是找得到您的。

这个方法除了要出国之外,还有个问题就是如果要连接国内的网站会非常缓慢,抓取数据可能经常会断连,所以需要在爬虫中做好异常处理。

  • 使用语言:没有
  • 用途:匿名代理
  • 优点:可以解除IP封禁限制
  • 缺点:需要出国使用,连接国内网站较慢

以上这些就是我最近探索的全部内容了,谢谢大家。

react项目启用ts

公司里最近让我负责一个项目,我试着引入了typescript,感受一下强类型的威力,原先已经有个react项目模版,改成了ts版的,这里把工作的笔记写在这里,可能不太全,将就着记录。

尝试步骤

  1. 添加编译设置
  2. 修改js代码至ts
  3. types文件改动

编译选项设置

  1. typescript官网有一个如何在React使用typescript的文档,里面介绍的是用create-react-app创建一个app脚手架,但是问题是创建出来的项目是用一个react-ts-scripts的工具编译的。我观察了很久没找到它是不是用webpack的,觉得似乎没什么可以配置的地方,最终还是用webpack中typescript的loader,这样可以用一套更加熟悉的工作流。
  2. loader可以使用ts-loader或者awesome-typescript-loader,这两个loader都出现在了官方的文档中,我使用的是awesome-typescript-loader,据文档称比ts-loader快一些。同时也在原来配置eslint-loader的位置改成了配置tslint-loader
  3. 在项目根目录下添加了2个文件: tsconfig.json以及tslint.json,这两个文件设置了一些基本的ts编译选项。类比一下,和.babelrc或者eslint.json差不多。文件内容来源于一个github上的一个react-ts-boilerplate
  4. 我曾经想尝试只用awesome-typescript-loader而不用babel-loader,但是编译出来的代码虽然target是es5,但有些浏览器不能跑(这点我一直在疑惑为什么),放在微信web开发者工具里面一直报错,所以在项目里ts编译后还是用了babel的loader再转一下,保证和原来一样能够运行。

修改js代码至ts代码

  1. 首先,就是把js代码扩展名变成ts,然后就开始根据ts的编译错误提示进行修改。
  2. 需要注意的一点是模块引用有着些许的不同,引入模块默认的export要按照as的语法写,例如原来引入React是
    import React from 'react';

typescript中必须这样

    import * as React from 'react';
  1. 为函数以及变量添加类型,具体定义类型的语法一般是: type,如果不确定可以通过加any类型使代码被编译器所接受。
  2. 全局变量的操作,有一些代码依赖于别的script tag,如微信的JS SDK,会在window下面挂一个wx对象。这时候如果直接调用wx.someAPI(),那么ts检查会报错,没有wx对象。但是如果调用window.wx,编译器还是会报错,因为window对象在types里已经有了一个window类型,下面没有wx这个field,除了改写types文件,还可以用类型断言的语法:
    (<any> window).wx.someAPI();
    // 或者
    (window as any).wx.someAPI();
  1. React相关写法。typescript中的类型定义中,interface是一个比较重要的语法,可以规定object中有哪些类型的field。这个就非常适合定义React组件的props和state的类型,typescript会对调用组件用的props进行类型检查,React定义为了一个泛型,一个标准写法如下:
    interface someStateType {
        ...
    }
    
    interface somePropsType {
        ...
    }
    
    class someComponent extends React.Component<somePropsType, someStateType> {
        ...
    }

types配置

  1. 安装@types包,一般常用的js第三方库目前都有了声明文件,在membership-h5项目中,package.json配置了以下包:
    "@types/node": "^9.3.0",
    "@types/react": "^16.0.34",
    "@types/react-dom": "^16.0.3",
    "@types/react-router-dom": "^4.2.3",
    "@types/react-tap-event-plugin": "0.0.30",

有了这些包后,例如在用react代码时,ts检测到是js包,则会自动到node_modules/@types/目录下去找react的声明文件,那静态检查时用的规则就是按照声明文件为主。

  1. 但是如果是其他第三方库,有可能没有types文件。
    • 有的时候是这样解决的,例如某公司内部库,会在所有js文件加一个同名的.d.ts文件,这样引入进来时直接把.d.ts引入进来
    • membership项目中采取的是这样的方式,自己觉得最好不要修改node_modules已经存在的东西,公共库还不是太了解,没有很大胆量去做声明文件。tsconfig.json中,有一个编译选项,是typeRoots。于是在配置里面加上了这句,用dts目录 放.d.ts文件
    "typeRoots" : ["./node_modules/@types", "./src/dts"]
  1. 可以建立专门的types文件为业务数据建模,dts目录下写了commodity.d.ts,定义了一些业务数据的类型和存在的fields,如积分商品,兑换记录。这样定义好还有文档的作用。

todos

  1. 取消项目中现在暂时填写any的部分,完善类型。
  2. 调查有没有办法完全取消babel-loader。
  3. 利用typescript的静态类型检查做更加完善的数据校验函数,防止出现一些取undefined下数据的异常。
  4. 找到一些tslint warning的比较好的解决办法。

关于学习和提问方法的感悟

上周我在公司进行了一些分享,主要是想以非技术的视角,说一些自己进步的动力和一些原因。

我把工作分成了三个阶段,第一个阶段是初级的阶段,第二个阶段是突破自己的阶段,第三个阶段就是挑选自己爱的技术进行钻研的阶段。

初级阶段,我倔强也不爱问人,造成了进步比较慢。
第二阶段也并没有变得爱问人问题,但是我开始尝试自己修改别人的代码运行,后来就发现原来有些没接触的框架也没那么难,自己试着做一做总能做出来,于是突破了自己,自此之后感觉前端问题都能通过自己研究研究出来。
第三阶段就是不断练级的阶段,自己读文档,找项目,找issue来帮助自己解决一些问题,并且自己通过读源码来解决问题。

有时候自己钻研效率低,但辩证地看,自己钻研促使了我成为了一名工作中比较独立的工程师,同事有很多时候并不是研究自己遇到问题的专家,即使他们更有经验他们也需要重新学习,有可能会耗费很长的时间。自己建立一套思维过程比什么都重要。

自己其实一直在酝酿,什么时候该向身边更有经验的工程师求助这个话题。
1. 遇到什么事情,首先选择调研,项目官方文档和搜索引擎能告诉自己的事,绝对不应该麻烦别人。
2. 没有相关内容,可以进行求助,但是需要有些条件。包括描述自己现在遇到的状况背景,以及自己尝试过的方法,遇到的错误。这样可以给出很多信息给被求助者,排除错误答案或者节省尝试时间,而且也会更赞赏你是一个动脑子的人。
3. 当自己的背景知识高度太低以至于搜索引擎很难帮助到自己的时候,毫不犹豫应该麻烦别人给自己进行一次知识普及。例如不知道一个特殊的技术名词,只能用通俗语言描述现象时,该问一下相关的技术名词是什么,否则连搜索引擎搜索的关键词都不知道写什么。这些技术名词可以说是你和这个世界的共同语言,这点共同语言其实也应该在平时的学习中进行积累,多看点未必有立即直接作用的内容,储备下来在脑海中成为索引,这样才能使前两步更加有效。

那些年玩前端遇到的坑们(六)——vuejs与异步组件

最近对我司产品进行了一点小小的优化,难度不大,但过程有点小小的波折,

0x00 缘起

我们的vendor.js有2M,app.js有1M,天呐?那么大,我们的项目文件下载就很慢了!

当老板感叹起这个的时候,刚刚熟读过vuejs官方文档的我提出来,我们还有异步组件这一项可以试试哟,有很多很多的组件其实不用全部载入,比如我们的代码编辑功能并没有开放入口,而且只有少数人才会用,但是引入的codemirror库却很大。vuejs异步组件文档中提到了,可以实现按需加载。

0x01 拨开云雾

那个章节写了一堆,异步的require之类的有点糊涂,慢慢地进行尝试,然后又阅读了webpack相关文档。发现其实跟vuejs基本上没啥关系,主要就是webpack提供的功能,总的来说要实现按需加载需要有几个条件:

  • 需要用webpack2.6及以上
  • ES6的话可以用import()函数方法(有async function也可以用async function的语法)
  • 如果要将多个异步组件打包到一个包中,需要用注释来指定chunk名字,这样既能够与ES2015 Loader spec规范相符,webpack也可以实现自己的特性。

0x02 渐入佳境

我们的工程是基于vue-cli生成的,可能版本不是特别新,webpack版本只有2.2,升级到2.7,这一步轻松解决。

在一些特定的异步组件将原来的import依赖的方式改写

import EditorLayout from ‘src/layouts/EditorLayout’改写为

const EditorLayout = ()=>import(/* webpackChunkName: “editor” */ ‘src/layouts/EditorLayout’)

这样看来可以说是相当简单了。webpack编译的时候,import()函数会变为一个promise,当依赖这个组件时会发起ajax请求,请求组件的代码。

但在编译时却不太对,并没有编译出想要的editor.js。还有一些组件也想打包在一起,却出现了0.js 1.js 2.js…..

0x03 小波折

可以说百度上找到的中文博客中,内容都很垃圾,很多感觉就是将一些文档翻译了遍,可能也没有亲手操作过。于是google上搜索了”vuejs webpack code splitting doesn’t work”这样的关键字,找了几个就在某个vue的项目的issue中找到了解决方案

原来之前搭建项目时用的boilerplate项目的.babelrc用了comments: false的选项,用babel-loader载入进webpack时,webpack获取的输入中已经没有了注释,所以用作标注chunkname的注释没有了,所以所有的chunk就分散开来,变成了数字名字的代码。

所以在.babelrc中修改comments为true即可

干前端一年后

一晃时间已经到了8月份,我转行也转了一年了,我在生日的后一天到新公司报到,然后新公司今年给我过生日时,也就到了整整一年。

今天突然想起来要更新一下博客,总结一下自己这一年来的心路历程。想想自己的路该如何继续。

去年8月份到年底,可以算是第一个阶段,我以前没有做过什么前端的项目,也就是准备面试题的时候大致了解了一下包含了哪些东西。一开始起步的时候主要是为我们的产品做模版,无外乎就是写一些模版文件和对应的css,懂了大致的模版的运作,然后学会了sass之类的东西,在公司已经搭好的项目框架中填内容,也有boilerplate,所以就成为了页面仔,开始熟悉起了各种样式。至今,css仍然是我很不喜欢的一样东西,因为搞兼容时会觉得它根本不讲道理。。。

后来到了今年,做过3个模版的我,会把自己的心放在如何变得更厉害上。但是周围对我的诉求都是修改模版之类的,和设计稿不是很一致要叫我改,和设计稿一致了但觉得不太好还是要我改。有时也会心理上不舒服,觉得好像是别人对我的工作不认可,就当即怼回去,“你觉得这样好,我还觉得那样好呢。” 有时侯会搞得不太愉快,不过我还是很高兴的,起码大家的出发点都是为了公司为了共同的事业好,跟某为的情况完全不一样。不过这就让我隐隐有点担心,一直做改个style这样的工作,没什么提升啊,于是我就找机会主动担起修改店铺页面框架和建站工具的工作,也是渐渐地handle住了公司所有的前端代码库,这里面我最喜欢干的还是修改建站工具来着,因为觉得比较特别,不像普通的curd程序,做起来好玩。

公司走了两个比较有经验的程序员,所以我也是渐渐地挑起了一些梁,比如最近就在将原先的react版的建站工具改成vue版的。(起初修改似乎是reduxform经常会有些奇怪的问题搞不懂,所以换了)总的来说,我觉得框架并没有太大的区别,但我个人不是很喜欢看jsx的语法,觉得vue的模版语法看上去更舒服一点。在重写的过程中遇到了不少困难,不过好在都一一解决了。

这一年来呢,我有些基本的进步,但毕竟我还是有着收入大幅上升且增加水平的目标,这点进步根本不快。

我有的时候会觉得“这样设计又不一定好,我不想做,而且代码实现起来也不优雅”这样的固执,但是我觉得是不太好的,好不好还是应该让运营和产品经理负责,研发人员最重要的还是要执行力,一个人一个想法很难将一个多人协作的产品推行下去。比如一件东西不好,再通过快速迭代修改过来,我觉得也是没错的。

所以我想的就是,在我职业生涯的第二年,我的第一个目标就是要做得更快更快,我有很多时间现在还是在”调程序“中度过的,应该要变得更成熟一点,对自己写的东西更有信心一些。做得快——我认为在初创公司中很能代表我在这个工种上的价值,还需要摸索摸索。目前先想到这两件事:

  1.  减少写一句看一句的动作,靠写基本的单元测试之类的来保证
  2. 写每一个feature前也更加做好整个系统的规划,记录下来,待做好之后回顾与 预想之间的区别,总结一下自己哪里容易没想到。

好的,先这样吧。我认为可以。

那些年前端遇到的坑们(五)——拖动排序:react和dragula的小秘密,维持数据的顺序

最近我除了做模版也在做一些可改进的部分,也做了些更有挑战的内容。感觉收获不少,公司的建站工具属于主打产品,最近也修改了很多其中的代码,学习了一些关于react的小技能。

最近想做一个“拖动调整板块顺序”的特性,看到前辈写过一段代码是可以给导航菜单拖动排序的,准备学习一下移植到建站工具的页面板块中来。

0x00 背景说明

建站工具是一项有些的技术,我司的产品中是在建站工具项目中插入一个iframe,iframe中运行着客户网站的项目,能够和react的项目通信。

WechatIMG95

左侧预览窗口是网站前端的项目,右侧的设置栏是建站工具的项目,它们互相进行通信,通过通信的消息类型和数据回调,保持配置数据和网页前端样式的同步。

假设网页前端项目叫做Front 建站工具项目叫做Editor,这个特性的流程可以说是这么样的:

  1. 在右侧拖动一张卡片,原来顺序是m,拖动到n。
  2. Editor计算好新的板块顺序的数据,发送给Front。
  3. Front收到消息后保存了这段数据,重新渲染iframe里的页面,顺序发生改变。
  4. Front刷新页面后会给Editor发送数据,Editor根据Front的数据来更新卡片顺序(项目以这个前提运行,这段不能不运行)

卡片顺序必须缓存着,这样可以快速保存。
在这里我们选用了react-dragula插件,该插件会生成一个decorator,将一个容器以ref和这个decorator绑定,就可以使这个容器下的子元素变得可以拖动并排序。decorator本身有很多支持的特性,插件的具体用法可以在https://github.com/bevacqua/dragulahttps://github.com/bevacqua/react-dragula查看更多

0x01 问题

预想流程如下:

  1. 使用拖动插件拖动
  2. 监听drop事件,根据drag位置和drop位置计算新的顺序存进缓存
  3. 读取缓存中的内容用react中setData的方式刷新页面

问题来了,我每次拖动修改顺序后,会跳变两次,
一次是插件本身的顺序调整,
还有一次是修改数据引起的调整。

查看了数据以后,发现数据没有错,更新的数据顺序对了,但是显示的是错的,那就说明react-dragula这个插件本身会保存一个状态,这个状态可能是做过的拖动操作后形成的一个新排序和旧排序的映射。

然后怎么改都觉得不太成功,于是看了看项目的issue,看了很多,找到了https://github.com/bevacqua/dragula/issues/188

这篇文章的标题写的是Option to not move or clone,正是我所想的“保留数据,但是摒弃新排序和旧排序的映射”,但是作者似乎并没有加这个option,但我从下面找到了react的解决办法,每次drop时重新计算顺序,再改变插件中的状态。不过评论说,在数据量多的时候也许有性能问题,我们的板块最多十来个,所以我就使用了这套方案。

https://github.com/bevacqua/dragula/issues/188#issuecomment-206762598

后面我自己的解决和业务太相关就不说了。
也算是一次比较愉快地通过看issue解决问题地经历~分享一下。