前端工程师转型财经分析大V,就差这个了:Tagged Templates
2018/5/4 15:56:40 文因互联

     其中非经常收益、净利润扣非等变量是从一个财经常用变量表里引入的,底下模板中用到哪个的话得先在上面引入。

     而「充满智慧的结论1」是通过对经济学的多年学习,加上对市场的深刻理解,还有前端工程师一分钟的语法指导得来的。大概长这样:

    

     小姐姐表示这还蛮好理解的,因为两个花括号里的字符串就是会填到文本里的内容,而前面的 if else 也不难理解,别的部分都是一模一样的,不需要深入理解也没事。因此她很快就写出了一大堆逻辑和模板,准备要开始发微博了。

     但作为提供那「一点魔法」的前端,还是得深入理解 template 和 logic 这两个标签里到底发生了啥。

     模板标签简介

     经常使用 styled-components 或者 graphql-tag 的人,可能都已经熟悉模板字符串标签的工作原理。此处仅用《 The magic behind ? styled-components 》( https://mxstbr.blog/2016/11/styled-components-magic-explained/ ) 中的例子简单概括一下。

     以这样一个函数为例:

    

     我们可以直接把它当做模板字符串标签来使用:

     这时候模板中的字面量(「I like 」和「.」)会被传到 logArgs 的第一个参数 templates 里,而把字面量隔开的那些模板变量会被收集进第二个参数 args 里:

    

     那么如果我们想把字面量和变量拼起来,就得像拉拉链一样把它们拉到一起,做这种事用 reduce 最方便:

    

     这样,把 result 返回之后,调用模板标签就能拿到 I like pizza. 了。

     所以这个项目非常简单,用 create-react-app 新建一个项目,往 app.js 里加一个模板字符串就搞定了。

     以上就是股神引擎的全部内容。

     收集数据依赖

     当然,还有一个也有一点重要的问题要解决,就是「净利润扣非」其实等于「净利润 - 非经常收益」,而非经常收益又是「营业外收入 - 营业外支出」,其中净利润、营业外收入和营业外支出三个基本数据是数据提取组能从财报 PDF 中提取得到的

     但「净利润扣非」不是,所以要得到「净利润扣非」这个导出值,就得向后端一次性请求所有这些基本数据,在前端进行计算后返回再填入模板。

     构造公式图

     注意到所有公式中的变量其实组成了一个有向无环图(DAG),只要收集所有叶子节点的名字,就可以一次性向后端请求它们对应的值了。后端返回数据后,把叶子节点的值填入 DAG 中,就能一级级算出根部的净利润扣非的值了。

    

     因此我们把公式表示成这样的数据结构:

    

     然后在 template 模板字符串标签里把这个 JSON 中的每一项都变成一个节点,最终把这些节点根据公式连接起来就变成一个 DAG 啦:

    

     连接节点的过程就是简单地遍历 formulas.json 把每个公式中的右值对应的 DAG Node 用addChild 塞进左值对应的 DAG Node。

     用 VM 做计算

     而当节点连接完毕,值也填进了每个叶子节点,就可以用 value 这个 getter 来递归计算父节点的值。其中递归步骤特别简单,就是一句:

    

     vm 可以看做是 NodeJS 上一个沙箱化的 eval,它的第二个参数接受一个对象,键会变成沙箱中的全局变量名,值就是这些全局变量的值。所以此处 vm 会直接根据公式算出结果。

     Dataloader

     那么怎么向后端请求叶子节点所需的值呢?如果模板中只用了一个变量,那么这个变量所依赖的基本变量的名字列表可以用relatedName getter 来递归地拿到。

     但模板中可能会涉及到十几个变量,他们很可能会交叉依赖共同的基本变量,所以我们需要先去重,然后再一次性向后端请求所有基本变量。

     为什么一直强调「一次性qu」

     因为模板是可以嵌入逻辑模板的,逻辑模板中也可以使用变量,然后返回一个结果。但我们不希望每个逻辑模板自己会产生一个异步的数据请求,金融支持小姐姐想要一次性看到结果,还有后端目前的设计中一次性取数据更快,所以我们期待整个模板加上所有逻辑模板里的变量依赖收集完后一次性向后端请求。

     一见「一次性向后端请求」,立刻想到 GraphQL,立刻想到 DataLoader。前端工程师的想象惟在这一层能够如此跃进。

     GraphQL 是 Facebook 提出的为前端组件一次性加载所有所需数据的方案,而 DataLoader 一开始是用于合并 GraphQL 服务端向数据库的请求的,其原理是在内部维护一个缓存数组,每个需要数据的函数去调用 DataLoader 的时候,其实是把请求参数缓存在数组里,然后在 process``.nextTick 中就能拿到上个事件循环中所有数据请求的请求参数了,此时再把它们拼接起来,一次性请求掉。

     比如下面这个用于去抖加载所有变量值的 DataLoader,DataLoader 接受一个函数,它会把它在上个 tick 收集到的所有请求参数传给这个函数,此处我们就能拿到所有地方调用这个 variableValueLoader 时传入的参数,它们被合并成了数组 variableNames:

    

     就像用了开塞露一样,所有的变量一次性涌出,无比地顺滑!

     当然这也带来了一个问题,就是我们的每个变量都变成了一个函数调用:

    

     这也的话写模板的时候就得这样写 ↓,多一个莫名其妙的 v,而且变量名还得是字符串,得多打一对引号很累很伤手:

    

     解决了数据获取和计算的问题,我们还需要实现当初的诺言:「这个模板引擎用起来很简单的,你把要填在那的变量填在 import 后面,然后填进模板就好了」,所以我打算这样预先把每个变量都用上面的函数调用一下,帮小姐姐保养她的玉手:

    

     生成可引用的变量

     然而公式中的变量实在是有点多,因为它是文因互联代代相传的传家公式表,像一个种马家族绵延五百年产生的族谱一样长:

    

     (图:经马赛克处理,公式表的二十分之一,显示在 VsCode 的小地图里)

     一个个搞起来太累了,而且手写这些 export const 的话还得两头维护,没有一个 single source of truth。

     这时候一句广告语响了起来:「工作劳累,身心疲惫,何不趁这春光明媚,把 codegen 学会。」

     babel-plugin-codegen 和 babel-plugin-preval 是前端在编译期运行业务代码的两个途径,一般可以使用它们的宏版本, codegen.macro 和 preval.macro 。用它们可以直接在模板字符串里写代码,并返回一个值,前者会用返回的字符串直接生成代码,后者则能把返回的值赋给某个运行期变量等。

     此处我们简单使用 codegen.macro,在模板字符串里引入 formulas.json,对于其中的每一个键,都生成一个 export const:

    

     在编译后,这段代码将展开成巨长的 export 列表。

     然后在别的地方就能直接 import 处理后的变量了:

    

     可惜由于 babel 和 ESModule 的限制,目前还没法实现一次性自动 import 所有的变量,所以还得手写 import,当然,这种限制也是有其道理的。

     简化的逻辑

     在我们的模板中除了可以嵌入变量以外,还可以嵌入逻辑,逻辑就是任意的一段 JS 表达式,表达式的值会插入到模板中。

     目前需要写的模板都比较简单,只需要简单条件判断的表达能力,所以一开始我们推荐使用三目操作符(? : ),但金融支持小姐姐说这是乱码看不懂。

     那就只好换一种表达式写法了,在 ECMAScript 中表达式就那么几种,其中比较易读的应该就是 do-expression 了:

    

     我们用 @babel/standalone 来在运行时转换 do-expression,再交给 vm 来执行。

    

     其中 resultCode 来自

    

     可以看到这段代码也是一个 reduce 的过程,把字面量数组 strings 和变量数组 resultValuesOfVariables 像拉拉链一样拼在一起。

     只不过在 reduce 过程的开头,我们放上了一个 ""+ ,这是什么意思呢?

     就像我们也偶尔会看到 +function(){}() ,JS 里加号的含义就像中文里的「搞」一样多,此处 ""+ 的意思就是「搞后面的东西」,也就是「把后面的东西当做表达式」,不然 babel 会认为这是一个 do…while statement,而认不出它是一个 do-expression,会报错:

    

     当然,do-expression 的语法还不稳定,在其 Github 仓库的 issue 里还进行着激烈的讨论,此处也仅是在小项目中为了简化判断逻辑的输入,因地制宜地使用了一下。

     至此,一个收集模板中的所有变量加上逻辑中用到的所有变量,一次性请求数据,并根据公式计算结果填入模板的小项目就可以投入使用了。金融支持小姐姐也在不知不觉中用上了 ES7 。

     但是小姐姐还有别的需求

     文因互联有自己的金融知识图谱,里面很多数据还需要找到有创意的使用方式和工程实现,在前端呈现出来。

     如果你熟悉前端目前的技术栈,不但能快速阅读已有项目代码,了解涉及到的 npm 包,也能在现有数据上提出有创意的工程方案,欢迎来找金融支持小姐姐听新的好玩的需求

     简历请投递:hr@memect.co 绽放你的工程能力吧!

     职位细节可以点击【阅读原文】查看哟。

    http://weixin.100md.com
返回 文因互联 返回首页 返回百拇医药