文章目录
- 前言
- BlockList区块
- 1. 完整页面效果展示
- 2. 功能分析
- 3. 代码+详细注释
- 4. 使用方式
- TranctionList交易模块
- 总结
前言
今天,我们继续完善首页剩余模块的响应式布局交互。通过关注点分离的方法,逐步切割模块至最小单元,并结合React的hook钩子函数,实现按需渲染,避免了渲染浪费,提升页面性能,尤其在实时轮询接口时至关重要。
页面带有banner模块、搜索模块、搜索下拉结果列表、切换语言模块、区块模块列表、交易模块列表,并且采用实时查询。
BlockList区块
1. 完整页面效果展示
(1)PC端效果
(2)移动端效果
2. 功能分析
(1)模块由四部分组成,Title标题块、List列表块、Item单位循环块、More跳转更多块组成
(2)结合使用react-query库,实现接口定时轮训
(3)使用memo钩子函数,通过条件比较,看是否重新渲染
(4)使用全局封装useParsedDate方法转换时间戳,BigNumber库转换数字
(5)使用HighLightLink公共组件实现跳转,以及复制功能
(6)使用国际化语言
3. 代码+详细注释
(1)最小项Item组件封装
// @/pages/Home/BlockList/Item/index.tsx
/**
* 该组件用于显示循环体单个区块信息
* @param block: 区块信息
* @param isDelayBlock: 是否为延时区块,延时区块显示不同样式
*/
import { FC, memo } from "react";
import { useTranslation } from "react-i18next";
import BigNumber from "bignumber.js";
import { Block } from "@/models/Block";
import { HighLightLink } from "@/components/HighLightLink";
import { shannonToCkb } from "@/utils";
import { useParsedDate } from "@/hooks/index";
import classNames from "classnames";
import { BlockCard, BlockCardLeft, BlockCardRight, BlockCardCenter } from "./styled";
const _BlockCardItem: FC<{ block: Block; isDelayBlock?: boolean }> = ({ block, isDelayBlock }) => {
const { t } = useTranslation();
const liveCellChanges = Number(block.liveCellChanges);
const [int, dec] = new BigNumber(shannonToCkb(block.reward)).toFormat(2, BigNumber.ROUND_FLOOR).split(".");
const parsedBlockCreateAt = useParsedDate(block.timestamp);
return (
<BlockCard>
{/* 左侧:区块编号和时间 */}
<BlockCardLeft>
<div className={classNames("block-card-number")}>
<span>#</span>
<HighLightLink value={block.number.toString()} to={`/block/${block.number}`} />
</div>
<span className="block-card-time">{parsedBlockCreateAt}</span>
</BlockCardLeft>
{/* 中间:矿工和奖励 */}
<BlockCardCenter>
<div className={classNames("block-card-miner")}>
<span className="block-card-miner">{t("home.miner")}</span>
<div>{block.minerHash}</div>
</div>
<div className={classNames("block-card-reward")}>
<span>{`${t("home.reward")}`}</span>
<div className={classNames("reward")}>
<span data-role="int">{int}</span>
{dec ? <span data-role="dec" className="monospace">{`.${dec}`}</span> : null}
{isDelayBlock ? <span data-role="suffix">+</span> : null}
</div>
</div>
</BlockCardCenter>
{/* 右侧:交易数和单元数 */}
<BlockCardRight>
<span className={classNames("block-card-trac-count")}>{`4 TXs`}</span>
<span className={classNames("block-card-cells")}>{`${liveCellChanges >= 0 ? "+" : "-"}6 ${t("home.cells")}`}</span>
</BlockCardRight>
</BlockCard>
);
};
// 使用memo优化,通过两个条件的比较,当前后两条数据不一致时,重新渲染单个循环体
export const BlockCardItem = memo(_BlockCardItem, (a, b) => a.block.number === b.block.number && a.isDelayBlock === b.isDelayBlock);
--------------------------------------------------------------------------------------------------------------
// @/pages/Home/BlockList/Item/styled.tsx
import styled from "styled-components";
export const BlockCard = styled.div`
display: flex;
align-items: center;
padding: 13px;
background: #fff;
`;
export const BlockCardLeft = styled.div`
flex: 2.2;
min-width: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
.block-card-number {
display: flex;
align-items: center;
font-size: 13px;
span {
margin-right: 3px;
}
}
.block-card-time {
color: var(--cd-gray-light-3);
font-size: 12px;
margin-top: 10px;
}
`;
export const BlockCardCenter = styled.div`
flex: 3.8;
min-width: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
.block-card-miner {
display: flex;
align-items: center;
font-size: 13px;
margin-right: 10px;
span {
margin-right: 3px;
}
}
.block-card-reward {
display: flex;
align-items: center;
color: var(--cd-gray-light-3);
margin-top: 10px;
.reward {
display: flex;
align-items: center;
margin-left: 10px;
span[data-role="int"] {
font-size: 14px;
}
span[data-role="dec"] {
font-size: 9px;
padding-right: 2px;
}
span[data-role="suffix"] {
font-size: 12px;
}
}
}
`;
export const BlockCardRight = styled.div`
flex: 1.2;
min-width: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
.block-card-trac-count {
display: flex;
align-items: center;
font-size: 13px;
}
.block-card-cells {
font-size: 12px;
color: var(--cd-gray-light-3);
margin-top: 10px;
}
`;
(2)List模块,引入Item组件循环数据
const List: FC = () => {
return blocks.length > 0 ? (
<>
{blocks.map((item, index) => (
<div key={item.number}>
<BlockCardItem block={item} isDelayBlock={index < DELAY_BLOCK_NUMBER} />
{blocks.length - 1 !== index && <div className="blockCardSeparate" />}
</div>
))}
</>
) : (
<Loading />
);
};
(3)BlockList父组件
import { FC, memo } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { BlockCardItem } from "./Item";
import Loading from "@/components/Loading";
import { Block } from "@/models/Block";
import { DELAY_BLOCK_NUMBER } from "@/global/constants/common";
import { BlockListContainer, BlockListTitle } from "./styled";
import { MoreButton } from "@/pages/Home/styled";
const BlockList: FC<{ blocks: Block[] }> = memo(({ blocks }) => {
const {
t,
i18n: { language },
} = useTranslation();
const navigate = useNavigate();
// 区块列表
const List: FC = () => {
return blocks.length > 0 ? (
<>
{blocks.map((item, index) => (
<div key={item.number}>
{/* 单个区块 */}
<BlockCardItem block={item} isDelayBlock={index < DELAY_BLOCK_NUMBER} />
{blocks.length - 1 !== index && <div className="blockCardSeparate" />}
</div>
))}
</>
) : (
<Loading />
);
};
return (
// 区块列表容器
<BlockListContainer>
{/* 区块列表标题 */}
<BlockListTitle>{t("home.latest_blocks")}</BlockListTitle>
{/* 区块列表 */}
<List />
{/* 查看更多按钮 */}
<MoreButton
onClick={() => {
navigate(`/${language}/block/list`);
}}
>
<span>{t("home.more")}</span>
</MoreButton>
</BlockListContainer>
);
});
export default BlockList;
--------------------------------------------------------------------------------------------------------------
import styled from "styled-components";
export const BlockListContainer = styled.div`
flex: 1;
width: 100%;
min-width: 0;
margin: 0 20px 40px 0;
border-radius: 6px;
overflow: hidden;
bacnground: #fff;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
`;
export const BlockListTitle = styled.div`
width: 100%;
height: 40px;
line-height: 40px;
font-size: 16px;
font-weight: 600;
color: #fff;
padding-left: 12px;
background: var(--cd-primary-color);
`;
4. 使用方式
// 引入
import BlockList from "./BlockList";
// 数据模拟,下一节对接真实接口,轮训处理
const blocks = [
{
miner_hash: "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0tpsqq08mkay9ewrfrdwlcghv62qw704s93hhsj",
number: "13418010",
timestamp: "1720282004389",
reward: "57350724258",
transactions_count: "1",
live_cell_changes: "1",
},
// ...
];
// 使用
<BlockList blocks={blocks} />
TranctionList交易模块
这块其实与BlockList模块布局相似,都差不多的,大家可以尝试着拆分块的方式,尝试开发
总结
下一篇讲【首页结合react-query库实现数据对接】。关注本栏目,将实时更新。