剥开比原看代码17:比原是如何显示交易的详细信息的?
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
在上上篇文章里,我们还剩下一个小问题没有解决,即前端是如何显示一个交易的详细信息的。
先看对应的图片:
这个图片由于太长,分成了两个,实际上可以看作一个。
那么这个页面是怎么来的呢?这是在前面以列表的方式显示交易摘要信息后,可以点击摘要信息右上角的“查看详情”链接打开。
那我们在本文看一下,比原是如何显示这个交易的详细信息的。
由于它分成了前后两端,那么我们跟以前一样,把它再分成两个小问题:
- 前端是怎么向后台发送请求,并显示数据的
- 后端是如何拿到相应的数据发送给前台的
需要说明的是,这个表格中包含了很多信息,但是我们在本文并不打算去解释。因为能看懂的一看就能明白,看不懂的就需要准确的了解了比原的核心之后才能解释清楚,而这一块等到我们晚点再专门研究。
前端是怎么向后台发送请求,并显示数据的
首先我们看一下显示交易详细信息页面的路由path是多少。当我们把鼠标放在交易摘要页面右上角的“查看详情”时,会发现url类似于:
http://localhost:9888/dashboard/transactions/2d94709749dc59f69cad4d6aea666586d9f7e86b96c9ee81d06f66d4afb5d6dd
其中http://localhost:9888/dashboard/
可以看作是这个应用的根路径,那么路由path应该就是/transactions/2d94709749dc59f69cad4d6aea666586d9f7e86b96c9ee81d06f66d4afb5d6dd
,后面那么长的显然是一个id,所以我们应该到代码中寻找类似于/transactions/:id
这样的字符串,哦,遗憾的是没有找到。。。
那只能从头开始了,先找到前端路由的定义:
src/routes.js#L15-L35
// ...
import { routes as transactions } from 'features/transactions'// ...const makeRoutes = (store) => ({path: '/',component: Container,childRoutes: [// ...transactions(store),// ...]
})
其中的transactions
就是我们需要的,而它对应了features/transactions/routes.js
:
src/features/transactions/routes.js#L1-L21
import { List, New, AssetShow, AssetUpdate } from './components'
import { makeRoutes } from 'features/shared'export default (store) => {return makeRoutes(store,'transaction',List,New,Show,// ...)
}
这个函数将会为transactions
生成很多相关的路由路径。当我们把一些组件,比如列表显示List
,新建New
,显示详情Show
等等传进去之后,makeRoutes
就会按照预先定义好的路径规则去添加相关的path。我们看一下makeRoutes
:
src/features/shared/routes.js#L1-L44
import { RoutingContainer } from 'features/shared/components'
import { humanize } from 'utility/string'
import actions from 'actions'const makeRoutes = (store, type, List, New, Show, options = {}) => {const loadPage = () => {store.dispatch(actions[type].fetchAll())}const childRoutes = []if (New) {childRoutes.push({path: 'create',component: New})}if (options.childRoutes) {childRoutes.push(...options.childRoutes)}// 1. if (Show) {childRoutes.push({path: ':id',component: Show})}return {// 2. path: options.path || type + 's',component: RoutingContainer,name: options.name || humanize(type + 's'),name_zh: options.name_zh,indexRoute: {component: List,onEnter: (nextState, replace) => {loadPage(nextState, replace)},onChange: (_, nextState, replace) => { loadPage(nextState, replace) }},childRoutes: childRoutes}
}
这段代码看起来眼熟,因为我们在之前研究余额和交易的列表显示的时候,都见过它。而我们今天关注的是Show
,即标记为第1处的代码。
可以看到,当传进来了Show
组件时,就需要为其生成相关的路由path。具体是在childRouters
中添加一个path
为:id
,而它本身的路由path是在第2处定义的,默认为type + 's'
,而对于本例来说,type
的值就是transaction
,所以Show
所对应的完整path就是/transactions/:id
,正是我们所需要的。
再回到第1处代码,可以看到Show
组件是从外部传进来的,从前面的函数可以看到它对应的是src/features/transactions/components/Show.jsx
。
我们进去看一下这个Show.jsx
,首先是定义html组件的函数render
:
src/features/transactions/components/Show.jsx#L16-L96
class Show extends BaseShow {render() {// 1.const item = this.props.itemconst lang = this.props.langconst btmAmountUnit = this.props.btmAmountUnitlet viewif (item) {// ..view = <div><PageTitle title={title} /><PageContent>// ...<KeyValueTabletitle={lang === 'zh' ? '详情' : 'Details'}items={[// ...]}/>{item.inputs.map((input, index) =><KeyValueTable// .../>)}{item.outputs.map((output, index) =><KeyValueTable// .../>)}</PageContent></div>}return this.renderIfFound(view)}
}
代码被我进行了大量的简化,主要是省略了很多数据的计算和一些显示组件的参数。我把代码分成了2部分:
- 第1处需要注意的是类似于
const item = this.props.item
这样的代码,这里的item
就是我们要展示的数据,对应本文就是一个transaction
对象,它是从this.props
中拿到的,所以我们可以推断在这个文件(或者引用的某个文件)中,会有一个connect
方法,把store里的数据塞过来。一会儿我们去看看。后面两行类似就不说了。 - 第2处代码主要就是页面view的定义了,可以看到里面主要是用到了另一个自定义组件
KeyValueTable
。代码我们就不跟过去了,参照前面的页面效果我们可以想像出来它就是以表格的形式把一些key-value数据显示出来。
那我们继续去寻找connect
,很快就在同一个页面的后面,找到了如下的定义:
src/features/transactions/components/Show.jsx#L100-L117
import { actions } from 'features/transactions'
import { connect } from 'react-redux'const mapStateToProps = (state, ownProps) => ({item: state.transaction.items[ownProps.params.id],lang: state.core.lang,btmAmountUnit: state.core.btmAmountUnit,highestBlock: state.core.coreData && state.core.coreData.highestBlock
})// ...export default connect(mapStateToProps,// ...
)(Show)
我只留下了需要关注的mapStateToProps
。可以看到,我们在前面第1处中看到的几个变量的赋值,在这里都有定义,其中最重要的item
,是从store的当前状态state
中的transaction
中的items
中取出来的。
那么state.transaction
是什么呢?我开始以为它是我们从后台取回来的一些数据,使用transaction
这个名字放到了store里,结果怎么都搜不到,最后终于发现原来不是的。
实际情况是,在我们定义reducer的地方,有一个makeRootReducer
:
src/reducers.js#L1-L62
// ...
import { reducers as transaction } from 'features/transactions'
// ...const makeRootReducer = () => (state, action) => {// ...return combineReducers({// ...transaction,// ...})(state, action)
}
原来它是在这里构建出来的。首先{ transaction }
这种ES6的语法,换成平常的写法,就是:
{transaction: transaction
}
另外,combineReducers
这个方法,是用来把多个reducer合并起来(可能是因为store太大,所以把它拆分成多个reducer管理,每个reducer只需要处理自己感兴趣的部分),并且合并以后,这个store就会变成大概这样:
{"transaction": { ... },// ...
}
所以前面的state.transaction
就是指的这里的{ ... }
。
那么继续,在前面的代码中,可以从state.transaction.items[ownProps.params.id]
看到,state.transaction
还有一个items
的属性,它持有的是向后台/list-transactions
取回的一个transaction数组,它又是什么时候加上去的呢?
这个问题难倒了我,我花了几个小时搜遍了比原的前后端仓库,都没找到,最后只好使出了Chrome的Redux DevTools大法,发现在一开始的时候,items
就存在了:
在图上有两个红框,左边的表示我现在选择的是初始状态,右边显示最开始transaction
就已经有了items
,于是恍然大悟,这不跟前面是一样的道理嘛!于是很快找到了定义:
src/features/transactions/reducers.js#L7-L16
export default combineReducers({items: reducers.itemsReducer(type),queries: reducers.queriesReducer(type),generated: (state = [], action) => {if (action.type == 'GENERATED_TX_HEX') {return [action.generated, ...state].slice(0, maxGeneratedHistory)}return state},
})
果然,这里也是用combineReducers
把几个reducer组合在了一起,所以store里就会有这里的几个key,包括items
,以及我们不关心的queries
和generated
。
花了一下午,终于把这块弄清楚了。看来对于分析动态语言,一定要脑洞大开,不能预设原因,另外要利用各种调试工具,从不同的角度去查看数据。要不是Redux的Chrome插件,我不知道还要卡多久。
我个人更喜欢静态类型的语言,对于JavaScript这种,除非万不得以,能躲就躲,主要原因就是代码中互相引用的线索太少了,很多时候必须看文档、代码甚至去猜,无法利用编辑器提供的跳转功能。
知道了state.transaction.items
的来历以后,后面的事情就好说了。我们是从state.transaction.items[ownProps.params.id]
拿到了当前需要的transaction,那么state.transaction.items
里又是什么时候放进去数据的呢?
让我们再回到前面的makeRoutes
:
src/features/shared/routes.js#L1-L44
// ...
import actions from 'actions'const makeRoutes = (store, type, List, New, Show, options = {}) => {// 2.const loadPage = () => {store.dispatch(actions[type].fetchAll())}// ...return {path: options.path || type + 's',component: RoutingContainer,name: options.name || humanize(type + 's'),name_zh: options.name_zh,indexRoute: {component: List,onEnter: (nextState, replace) => {loadPage(nextState, replace)},// 1. onChange: (_, nextState, replace) => { loadPage(nextState, replace) }},childRoutes: childRoutes}
}
在上面的第1处,对于indexRoute
,有一个onChange
的触发器。它的意思是,当路由的path改变了,并且新的path属于当前的这个index路由的path(或者子path),后面的函数将会触发。而后面函数中的loadPage
的定义在第2处代码,它又会将actions[type].fetchAll()
生成的action进行dispatch。由于type
在本文中是transaction
,通过一步步追踪(这里稍有点麻烦,不过我们在之前的文章中已经走过),我们发现actions[type].fetchAll
对应了src/features/shared/actions/list.js
:
src/features/shared/actions/list.js#L4-L147
export default function(type, options = {}) {const listPath = options.listPath || `/${type}s`const clientApi = () => options.clientApi ? options.clientApi() : chainClient()[`${type}s`]// ...const fetchAll = () => {// ...}// ...return {// ...fetchAll,// ...}
}
如果我们还对这一段代码有印象的话,就会知道它最后将会去访问后台的/list-transactions
,并在拿到数据后调用dispatch("RECEIVED_TRANSACTION_ITEMS")
,而它将会被下面的这个reducer处理:
src/features/shared/reducers.js#L6-L28
export const itemsReducer = (type, idFunc = defaultIdFunc) => (state = {}, action) => {if (action.type == `RECEIVED_${type.toUpperCase()}_ITEMS`) {// 1.const newObjects = {}// 2.const data = type.toUpperCase() !== 'TRANSACTION' ? action.param.data : action.param.data.map(data => ({...data,id: data.txId,timestamp: data.blockTime,blockId: data.blockHash,position: data.blockIndex}));// 3. (data || []).forEach(item => {if (!item.id) { item.id = idFunc(item) }newObjects[idFunc(item)] = item})return newObjects}// ...return state
}
依次讲解这个函数中的三处代码:
- 第1处是创建了一个新的空对象
newObjects
,它将在最后替代state.transaction.items
,后面会向它里面赋值 - 第2处是对传进来的数据进行一些处理,如果type是
transaction
的话,会把数组中每个元素中的某些属性提升到根下,方便使用 - 第3处就是把各个元素放到
newObjects
中,id
为key,对象本身为value
经过这些处理以后,我们才能使用state.transaction.items[ownProps.params.id]
拿到合适的transaction对象,并且由Show.jsx
显示。
前端这块基本上弄清楚了。我们继续看后端
后端是如何拿到相应的数据发送给前台的
前面我们说过,根据以往的经验,我们可以推导出前端会访问后端的/list-transactions
这个接口。我们欣喜的发现,这个接口我们正好在前一篇文章中研究过,这里就可以完全跳过了。
到今天为止,我们终于把“比原是如何创建一个交易的”这件事的基本流程弄清楚了。虽然还有很多细节,以及触及到核心的知道都被忽略了,但是感觉自己对于比原内部的运作似乎又多了一些。
也许现在积累的知识差不多了,该向比原的核心进发了。在下一篇,我将会尝试理解和分析比原的核心,在学习的过程中,可能会采用跟目前探索流程分解问题不同的方式。另外,可能前期会花不少时间,所以下一篇出来得会晚一些。当然,如果失败了,说明我目前积累的知识还是不够,我还需要再回到当前的做法,想办法再从不同的地方多剥一些比原的外壳,然后再尝试。
剥开比原看代码17:比原是如何显示交易的详细信息的?相关推荐
- 剥开比原看代码(十七):比原是如何显示交易的详细信息的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码15:比原是如何转帐的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... ...
- taobao.trade.fullinfo.get( 获取单笔交易的详细信息API接口),淘宝店铺订单明文接口代码分享
采用官方taobao.trade.fullinfo.get( 获取单笔交易的详细信息 )接口拿到店铺订单详情,包括:买家信息,交易创造时间,商品交易数据,商品订单号,付款时间,付款金额,收货人手机号码 ...
- 剥开比原看代码09:通过dashboard创建密钥时,前端的数据是如何传到后端的?
2019独角兽企业重金招聘Python工程师标准>>> 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Git ...
- 剥开比原看代码08:比原的Dashboard是怎么做出来的?
2019独角兽企业重金招聘Python工程师标准>>> 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Git ...
- 剥开比原看代码(八):比原的Dashboard是怎么做出来的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码07:比原节点收到“请求区块数据”的信息后如何应答?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- Source Insight 3.5 Source Insight4.0 看代码神器 免费版 百度网盘下载,附详细安装教程。
链接:https://pan.baidu.com/s/1IIALTQHovV9Zp0xetRfYYQ 密码:0mtk 一.Source Insight 3.5 安装教程: 1.下载文件中附有 Sour ...
- Java多线程系列--“JUC原子类”03之 AtomicLongArray原子类
概要 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似.本章以AtomicLongArray对数 ...
最新文章
- demo flink写入kafka_Flink结合Kafka实时写入Iceberg实践笔记
- 关于 quick-cocos 状态机
- c语言小数加分,求救:c语言课程设计 员工工资管理程序 有加分的。谢谢
- 算法题目——最长连续序列
- redisb并发访问慢出现的问题
- Redis分布式基础主从同步
- NVisionXR_iOS教程五 —— 添加灯光渲染
- ubuntu 系统网络突然网络已禁用
- 【iPhone】解决照片无法编辑或删除的问题
- Adb connection Error:远程主机强迫关闭了一个现有的连接。
- python、anaconda、jupyetr notebook的安装与配置
- windows怎样连接到linux桌面,Windows远程桌面连接Ubuntu 14.04
- ASIC设计中的分频时钟
- 可配置组件库Fusion Design 了解一下
- 十三款著名跨平台游戏引擎(原文翻译部分转自游侠网)
- 利用 OpenCC 工具进行文字的简繁转换
- python大鱼吃小鱼_写简单游戏,学编程语言-python篇:大鱼吃小鱼
- PCIe转PXI 套件 PXI远程控制器
- MATLAB算法实战应用案例精讲-【人工智能】SLAM(概念篇)
- 查看网页源代码后如何返回