React+TS前台项目实战(二十八)-- 首页响应式构建之剩余模块布局


前言

今天,我们继续完善首页剩余模块的响应式布局交互。通过关注点分离的方法,逐步切割模块至最小单元,并结合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库实现数据对接】。关注本栏目,将实时更新。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值