Author: freewind
Compared to the original project warehouse:
GitHub Address: Https://github.com/Bytom/bytom
Gitee Address: Https://gitee.com/BytomBlockc ...
In the last article, we have one small problem left unresolved, that is, how the front end shows the details of a transaction.
First look at the corresponding picture:
This picture is too long, divided into two, can actually be regarded as one.
So how does this page come from? After displaying the transaction summary information in the previous list, you can click the "View Details" link in the upper right corner of the summary information to open it.
So let's look at the details of how this transaction is displayed in this article.
Since it is divided into front and rear ends, we will divide it into two small problems as before:
- How the frontend sends the request to the background and displays the data
- Back end is how to get the corresponding data sent to the foreground
It is necessary to note that this table contains a lot of information, but we do not intend to explain it in this article. Because you can understand a look can be understood, do not understand the need for accurate understanding of the core of the original before we can explain clearly, and this one until we later specialized research.
How the frontend sends the request to the background and displays the data
Let's start by looking at the route path that shows the transaction detail page. When we put the mouse on the "View Details" in the upper right corner of the trading summary page, we find that the URL looks like this:
http://localhost:9888/dashboard/transactions/2d94709749dc59f69cad4d6aea666586d9f7e86b96c9ee81d06f66d4afb5d6dd
Which http://localhost:9888/dashboard/
can be seen as the root path of the application, then the route path should be /transactions/2d94709749dc59f69cad4d6aea666586d9f7e86b96c9ee81d06f66d4afb5d6dd
, the subsequent length is obviously an ID, so we should look in the code similar to /transactions/:id
such a string, oh, unfortunately did not find ...
That can only start from scratch, first find the definition of the front-end route:
Src/routes.js#l15-l35
// ...import { routes as transactions } from 'features/transactions'// ...const makeRoutes = (store) => ({ path: '/', component: Container, childRoutes: [ // ... transactions(store), // ... ]})
Which transactions
is what we need, and it corresponds to 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, // ... )}
This function will transactions
generate a lot of related routing paths. When we pass in some components, such as list display List
, new New
, show details and Show
so on, we makeRoutes
will add the associated path according to the predefined path rules. Let's take a look at 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 LOADPAG E = () = {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, Replac E) = = {LoadPage (nextstate, replace)}, OnChange: (_, nextstate, replace) = {LoadPage (nextstate, Replace)}}, Childroutes:childroutes}}
This code looks familiar because we've seen it before when we looked at the list of balances and trades. And what we're concerned about today is the Show
code labeled 1th Place.
As you can see, when you pass in a Show
component, you need to generate the associated route path for it. Specifically, childRouters
add a path
to :id
, and its own route path is defined in the 2nd place, the default is, type + 's'
and for this example, type
the value is transaction
, so the Show
corresponding full path is /transactions/:id
, That's what we need.
Back to the 1th code, you can see that the Show
component is coming in from the outside, and from the previous function you can see that it corresponds to src/features/transactions/components/Show.jsx
.
Let's go inside and look at this Show.jsx
, first, the function that defines the HTML component render
:
Src/features/transactions/components/show.jsx#l16-l96
class Show extends BaseShow { render() { // 1. const item = this.props.item const lang = this.props.lang const btmAmountUnit = this.props.btmAmountUnit let view if (item) { // .. view = <div> <PageTitle title={title} /> <PageContent> // ... <KeyValueTable title={lang === 'zh' ? '详情' : 'Details'} items={[ // ... ]} /> {item.inputs.map((input, index) => <KeyValueTable // ... /> )} {item.outputs.map((output, index) => <KeyValueTable // ... /> )} </PageContent> </div> } return this.renderIfFound(view) }}
The code was much simplified by me, mostly omitting the computation of a lot of data and the parameters of some display components. I divided the code into 2 sections:
- The 1th place to note is similar to the
const item = this.props.item
code, here is the item
data we want to show, corresponding to this article is an transaction
object, it is this.props
obtained from the, so we can infer in this file (or a referenced file), there will be a connect
method, Plug the data in the store. Let's go and have a look. The next two lines are like that.
- The 2nd code is basically the definition of Page view, you can see that the main use of the other custom components
KeyValueTable
. Code we will not follow the previous page effect we can imagine it is the form of some key-value data display.
Then we continue to look for connect
, soon after the same page, found the following definition:
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)
I only left what I needed to pay attention to mapStateToProps
. As you can see, the assignments of several variables we see in the 1th place here are defined, most importantly item
, from the current state of the store state
transaction
items
.
So state.transaction
what is it? I began to think that it is our back from the background of some data, using transaction
the name in the store, the results are not searched, and finally found that the original is not.
The reality is that where we define reducer, there is one makeRootReducer
:
Src/reducers.js#l1-l62
// ...import { reducers as transaction } from 'features/transactions'// ...const makeRootReducer = () => (state, action) => { // ... return combineReducers({ // ... transaction, // ... })(state, action)}
Originally it was built here. First of all, { transaction }
this ES6 grammar, the usual wording, is:
{ transaction: transaction}
In addition, combineReducers
this method is used to merge multiple reducer (perhaps because the store is too big, so split it into multiple reducer management, each reducer only need to deal with the part of their interest), and after merging, the store will become like this:
{ "transaction": { ... }, // ...}
So the front state.transaction
is referring to here { ... }
.
So continue, in the previous code, you can state.transaction.items[ownProps.params.id]
see, there is state.transaction
a items
property, it holds a back to the table to /list-transactions
retrieve a transaction array, when it was added to it?
This problem baffled me, I spent a few hours searching over the original front and back of the warehouse, have not found, finally had to exert the chrome Redux Devtools Dafa, found in the beginning, there are items
:
On the figure there are two red boxes, the left side of the expression I now choose is the initial state, the right side of the beginning of the show has transaction
been items
, so suddenly, this is not the same as the previous reason! So soon the definition was found:
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 },})
Sure enough, it's also a combineReducers
combination of several reducer, so the store will have several keys here, including, and what items
we don't care about queries
generated
.
It took the whole afternoon to figure out the piece. It seems that for the analysis of dynamic language, it is important to open the brain hole, can not be preset reasons, in addition to use a variety of debugging tools, from different angles to view the data. But for Redux's Chrome plugin, I don't know how long it will take.
I personally prefer the static type of language, for JavaScript this, unless million can hide to hide, the main reason is that the code references to each other too few clues, many times must look at the document, code and even to guess, can not take advantage of the editor to provide the jump function.
After knowing state.transaction.items
the origin, the following things will say. We are from the transaction of the state.transaction.items[ownProps.params.id]
current needs, then state.transaction.items
when is the time to put in the data?
Let's go back to the front 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 }}
In the 1th place above, for indexRoute
, there is a onChange
trigger. It means that when the path of the route has changed and the new path belongs to the path (or child path) of the current index route, subsequent functions will be triggered. The definition in the later function is loadPage
in the 2nd code, and it actions[type].fetchAll()
dispatch the resulting action. As type
is the case in this article transaction
, through step-by-step tracking (a little bit of trouble here, but we have gone through the previous article), we found that the actions[type].fetchAll
corresponding 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, // ... }}
If we are still impressed with this piece of code, we will know that it will eventually go to the backend and /list-transactions
call after the data is received dispatch("RECEIVED_TRANSACTION_ITEMS")
, and it will be processed by this 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}
Explain the three code in this function in turn:
- The 1th place is to create a new empty object
newObjects
, which will be replaced at the end state.transaction.items
and will be assigned a value in it later
- The 2nd place is to do some processing of the incoming data, and if the type is
transaction
, it will raise some of the attributes in each element of the array to the root for ease of use
- The 3rd place is to put each element into the
newObjects
id
key, the object itself is value
After these processing, we can use to state.transaction.items[ownProps.params.id]
get the appropriate transaction object, and by the Show.jsx
display.
This piece of the front is basically clear. We continue to look at the back end
Back end is how to get the corresponding data sent to the foreground
As we said earlier, based on previous experience, we can push the export front end to access /list-transactions
this interface on the backend. We are delighted to find that this interface has been studied in the previous article and can be skipped completely here.
So far, we have finally figured out the basic process of "How to create a deal". While there are a lot of details, and the knowledge that touches the core is ignored, it feels like there is more to it than the internal workings of the original.
Perhaps the knowledge accumulated now is about the same as the core of the original. In the next article, I will try to understand and analyze the core of the original, and in the course of learning, it may take a different approach to the current exploration process decomposition problem. In addition, it may take a lot of time, so the next article will come later. Of course, if the failure, indicating that my current accumulated knowledge is not enough, I also need to go back to the current practice, find ways to peel more than the original shell from different places, and then try again.