实践总结|前端架构设计的一点考究(上)

简介: 本文总结了作者在日常/大促业务的“敏捷”开发过程中产生的疑惑,并尝试做出思考得到一些解决思路和方案。在前端开发和实践过程中,梳理了一些简单设计方案可以缓解当时 “头疼” 的几个敏捷迭代问题,并实践在项目迭代中。




image.png

背景


1.1 为什么会有这一篇文章?


在日常/大促业务的“敏捷”开发过程中逐渐产生的几个疑惑,尝试地做出思考并想得到一些解决思路和方案。

总的来说,在前端开发和实践过程中,梳理了一些简单设计方案可以缓解当时让我 “头疼” 的几个敏捷迭代问题,并实践在项目迭代中。


1.2 因此个人对这篇文章有三个小目的:


  1. 梳理清楚个人真正疑惑开发迭代的问题在哪,解决的核心是什么,温故而知新。
  2. 提供前端架构设计的思考&方案,来缓解日常/大促敏捷迭代问题,希望可以得到一些拍砖~
  3. 能让项目协同的同学能初步理解个人对于前端结构设计,方便他人理解这样搞的原因背景,快速磨平协同上的一些理解和开发成本 💰。



image.png

疑惑

先抛出主要的几点问题,在业务迭代过程中你为什么会疑惑(WHY),疑惑什么问题(WHAT),会以什么方式解决(HOW)?

  1. 在业务需求的敏捷迭代中,单看逻辑和视觉的变更都不难,为何结合起来迭代如此花费成本?
  2. 敏捷业务迭代中,我们能找出什么是敏捷在变,什么是敏捷不变
  3. 面向视图开发,还是面向数据开发?
  4. React 定位“是一个用于渲染用户界面 (UI) 的 JavaScript 库”,那么 UI 和逻辑怎么更好地设计结合?

image.png

示例


为了更加有体感,以一个常规的业务例子,简单描述下个人开发的规范和经验的粗浅历程。

先假设一个业务需求:核心关于【账户信息】


3.1 需求一:金额账户

需求一:一开始需求很简单 —— 展示账户信息解决一:咔咔写一个组件和 CSS 样式,在组件中定义数值并获取接口数据更新。1.Account 组件

import { useEffect, useState } from 'react';
import styles from './index.module.less';

const Account = () => {
  // 账户金额
  const [account, setAccount] = useState(0);

  useEffect(() => {
    // 模拟接口数据
    setTimeout(() => {
      setAccount(12.34);
    }, 1000)
  }, [])

  return (
    <div className={styles.stickyAccountWrap}>
      <div className={styles.stickyAccount}>
        <div className={styles.stickyAccountGoldPocketPic} />
        <div className={styles.stickyAccountTitleContainer}>
          <div className={styles.stickyAccountTitle}>
            <div>{account}</div>
            <div className={styles.unit}>元</div>
          </div>
        </div>
        <div className={styles.withdraw} />
      </div>
    </div>
  );
};

export default Account;


2.相关样式

.stickyAccountWrap {
  width: 750rpx;
  height: 104rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  left: 0;
  top: 0;

  .stickyAccount {
    width: 308rpx;
    height: 70rpx;
    background: center / contain no-repeat
      url(https://gw.alicdn.com/imgextra/i4/O1CN01harLZI1kECtyvhAPh_!!6000000004651-2-tps-308-70.png);
    display: flex;
    align-items: center;
    justify-content: center;
    .stickyAccountGoldPocketPic {
      width: 37rpx;
      height: 46rpx;
      background: center / contain no-repeat url("https://gw.alicdn.com/imgextra/i4/O1CN01RQ3Gzj1ZJjv6MlKoD_!!6000000003174-2-tps-74-92.png");
    }

    .stickyAccountTitleContainer {
      margin-left: 10rpx;
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      height: 100%;
      .stickyAccountTitle {
        margin-top: 6rpx;
        font-family: "Alibaba Sans 102";
        font-size: 42rpx;
        font-weight: bold;
        display: flex;
        align-items: baseline;
        color: #bc2b15;
        height: 60rpx;
        .unit {
          font-size: 22rpx;
          margin-left: 2rpx;
        }
      }
    }

    .withdraw {
      margin-left: 10rpx;
      background: center / contain no-repeat
        url("https://img.alicdn.com/imgextra/i4/O1CN01teiAeS1tZZvwjzqx9_!!6000000005916-2-tps-129-63.png");
      width: 86rpx;
      height: 42rpx;
    }
  }
}

实现一: 

总结:基操~ 业务仍在高速迭代中...  很快需求来了~ 🚄 ✈️ ✈️ ✈️ ✈️

3.2 需求二:互动效果


需求二:业务希望权益氛围感增强,在金额变化的同时,有金币飞入红包的氛围效果解决二:简单 😁 ~ 在组件内写一个金币组件 + 飞入状态控制即可1.金币飞入通用组件


import { CSSProperties, FC, useRef, useEffect, useCallback } from 'react';
import anime from 'animejs';
import styles from './index.module.less';

interface ICoinsFly {
  style?: CSSProperties;
  onEnd: () => void;
}

/**
 * 金币飞动画组件
 */
const CoinsFly: FC<ICoinsFly> = (props) => {
  const { style, onEnd } = props;
  const wrapRef = useRef<HTMLDivElement>(null);

  const rpx2px = useCallback((rpxNum: number) => (rpxNum / 750) * window.screen.width, []);

  useEffect(() => {
    // 金币动画
    anime({
      targets: wrapRef.current?.childNodes,
      delay: anime.stagger(90),
      translateY: [
        { value: 0 },
        {
          value: -rpx2px(334),
          easing: 'linear',
        },
      ],
      translateX: [
        { value: 0 },
        {
          value: -rpx2px(98),
          easing: 'cubicBezier(.05,.9,.8,1.5)',
        },
      ],
      scale: [
        { value: 1 },
        {
          value: 0.5,
          easing: 'linear',
        },
      ],
      opacity: [
        { value: 1 },
        {
          value: 0,
          easing: 'cubicBezier(1,0,1,0)',
        },
      ],
      duration: 900,
      complete: () => {
        onEnd();
      },
    });
  }, []);

  return (
    <div className={styles.container} style={style} ref={wrapRef}>
      {[1, 2, 3, 4, 5, 6, 7, 8].map((item) => (
        <div key={item} className={styles.coin} />
      ))}
    </div>
  );
};

export default CoinsFly;

.container {
  position: absolute;
  top: 100rpx;
  left: 100rpx;
  background-color: rgba(255, 255, 255, 0.6);
  .coin {
    width: 106rpx;
    height: 106rpx;
    background-image: url("https://gw.alicdn.com/imgextra/i4/O1CN01hVWasj25i4dZdV9sS_!!6000000007559-2-tps-160-160.png");
    background-position: center;
    background-size: contain;
    background-repeat: no-repeat;
    position: absolute;
    top: 0;
    left: 0;
  }
}


2.账户组件引入金币飞入组件 && 状态控制



import { useEffect, useState } from 'react';
import CoinsFly from '../../components/CoinsFly';
import styles from './demo1.module.less';

const Account = () => {
  // 账户金额
  const [account, setAccount] = useState(0);
  // 金币飞入动画
  const [showCoinsFly, setShowCoinsFly] = useState(false);


  useEffect(() => {
    // 模拟接口数据
    setTimeout(() => {
      setAccount(12.34);
      setShowCoinsFly(true);
    }, 1000)
  }, [])

  return (
    <div className={styles.stickyAccountWrap}>
      <div className={styles.stickyAccount}>
        <div className={styles.stickyAccountGoldPocketPic} />
        <div className={styles.stickyAccountTitleContainer}>
          <div className={styles.stickyAccountTitle}>
            <div>{account}</div>
            <div className={styles.unit}>元</div>
          </div>
        </div>
        <div className={styles.withdraw} />
      </div>
      {showCoinsFly && (
        <CoinsFly
          style={{
            top: '322rpx',
            left: '316rpx',
            zIndex: 1,
          }}
          onEnd={() => {
            setShowCoinsFly(false);
          }}
        />
      )}
    </div>
  );
};

export default Account;

实现二: 



总结:🎈🎈简简单单搞定~   代码写得清晰明了~  还沉淀了一个金币飞入的组件  当然很快需求又来了 ✈️


3.3 需求三:权益承接


需求三:业务方提出希望有一个弹窗承接,也就是点击提现按钮 => 提现成功弹窗收下 => 金币飞入再金额刷新解决三:1.写一个提现成功弹窗


import styles from './index.module.less';

export interface DialogData {
  a: string;
  /** 标题 */
  b: string;
  /** 金额 */
  c: string;
  d: string;
  e: string;
}

// 定义Props类型
interface IPopupProps {
  onClose?: () => void;
  /** 弹窗信息 */
  data: DialogData;
}

// 提现弹窗
const WithdrawDialog = (props: IPopupProps) => {
  const { onClose, data } = props;
  const {
    a,
    b,
    c,
    d
  } = data || {};

  // 关闭弹窗
  const handleClose = () => {
    typeof onClose === 'function' && onClose();
  };

  return (
    <div className={styles.popup}>
      <div className={styles.content}>
        {/* 头部提示 */}
        <div className={styles.header}>
          <div className={styles.icon} />
          <div className={styles.title}>{a}</div>
        </div>
        <div className={styles.body}>
          {/* 金额 */}
          <div className={styles.amountCon}>
            <div className={styles.amount}>{b || ''}</div>
            <div className={styles.unit}>元</div>
          </div>
          <div className={styles.dividing} />
          {/* 账户内容 */}
          <div className={styles.userContent}>
            <div className={styles.userItem}>
              <div className={styles.title}>提现账户</div>
              <div className={styles.userText}>{c || ''}</div>
            </div>
            <div className={styles.userItem}>
              <div className={styles.title}>打款方式</div>
              <div className={styles.userText}>{d || ''}</div>
            </div>
          </div>

          {/* 按钮 */}
          <div
            className={styles.btn}
            onClick={() => handleClose()}
          >开心收下</div>
        </div>
      </div>
    </div >
  );
};

export default WithdrawDialog;




.popup {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  .hidden {
    display: none;
  }

  .content {
    position: relative;
    background: center / contain no-repeat url("https://gw.alicdn.com/imgextra/i3/O1CN01vlcfgm1xFCpji3rv7_!!6000000006413-2-tps-596-786.png");
    border-radius: 54rpx;
    width: 590rpx;
    height: 780rpx;
    display: flex;
    flex-direction: column;

    .header {
      display: flex;
      flex-direction: column;
      align-items: center;
      margin-top: 66rpx;
      .icon {
        width: 90rpx;
        height: 90rpx;
        background: center / contain no-repeat
          url("https://gw.alicdn.com/imgextra/i4/O1CN01KSkat11aHHShz5JqV_!!6000000003304-2-tps-90-90.png");
      }
      .title {
        font-weight: 700;
        margin-top: 30rpx;
        font-family: PingFangSC-Medium;
        font-size: 32rpx;
        color: #1677ff;
      }
    }

    .body {
      display: flex;
      flex-direction: column;
      align-items: center;
      margin-top: 40rpx;
      .amountCon {
        display: flex;
        align-items: baseline;
        color: #ff0746;
        
        .amount {
          font-family: AlibabaSans102-Bd;
          font-size: 120rpx;
        }
        .unit {
          position: relative;
          top: -4rpx;
          font-size: 60rpx;
        }
      }
      .dividing {
        margin-top: 40rpx;
        width: 506rpx;
        height: 2rpx;
        background-color: #ccc;
      }
      .userContent {
        margin-top: 22rpx;
        width: 506rpx;
        height: 100%;
        .userItem {
          margin-top: 20rpx;
          width: 100%;
          display: flex;
          justify-content: space-between;
          .title {
            font-family: PingFangSC-Regular;
            font-size: 26rpx;
            color: #666;
          }
          .userText {
            font-family: PingFangSC-Medium;
            font-size: 26rpx;
            color: #111;
          }
        }
      }
    }

    .errCon {
      margin-top: 38rpx;
      display: flex;
      flex-direction: column;
      align-items: center;
      .title {
        font-weight: 700;
        margin-bottom: 14rpx;
        font-family: PingFangSC-Semibold;
        font-size: 48rpx;
        color: #111;
      }
      .des {
        font-size: 32rpx;
        line-height: 44rpx;
        color: #363636;
        .redText {
          color: #ff0d40;
        }
      }
      .img {
        margin-top: 18rpx;
        width: 300rpx;
        height: 384rpx;
        background: center / contain no-repeat url("https://gw.alicdn.com/imgextra/i3/O1CN01uMPIk91nUBd1MjN9v_!!6000000005092-2-tps-300-384.png");
      }
    }
  }
}

.btn {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 42rpx;
  width: 518rpx;
  height: 96rpx;
  background-image: linear-gradient(100deg, #f54ea1 0%, #ff0040 100%);
  border-radius: 52rpx;
  text-align: center;
  line-height: 96rpx;
  color: #fff;
  font-family: PingFangSC-Medium;
  font-size: 38rpx;
}

Account 组件除了账户信息还添加了弹窗的信息内容,为了 Account 组件干净以及后续更好迭代,进行业务 Hook 逻辑抽象。

2.抽成账户刷新&金币的状态逻辑 Hooks


import { useCallback, useEffect, useState } from 'react';
import { DialogData } from '../../components/WithdrawDialog';

const useAccount = () => {
  // 账户金额
  const [account, setAccount] = useState(0);
  // 金币飞入动画
  const [showCoinsFly, setShowCoinsFly] = useState(false);
  // 弹窗展示
  const [showDialog, setShowDialog] = useState(false);
  const [dialogData, setDialogData] = useState<DialogData>();

  /** 模拟接口 => 刷新账户信息 */
  const refreshAccount = useCallback((account) => {
    setTimeout(() => {
      setAccount(account);
      setShowCoinsFly(true);
    }, 500)
  }, [])

  useEffect(() => {
    // 模拟初始化数据 => 接口数据
    refreshAccount(12.34)
  }, [])

  return {
    account,
    refreshAccount,
    showCoinsFly,
    setShowCoinsFly,
    showDialog,
    setShowDialog,
    dialogData,
    setDialogData
  }
}

export default useAccount;

3.抽象重构 Account 账户组件,引入提现成功弹窗


import CoinsFly from '../../components/CoinsFly';
import WithdrawDialog from '../../components/WithdrawDialog';
import useAccount from './useAccount';
import styles from './index.module.less';

const Account = () => {
  // 账户业务逻辑
  const {
    account,
    refreshAccount,
    showCoinsFly,
    setShowCoinsFly,
    showDialog,
    setShowDialog,
    dialogData,
    setDialogData
  } = useAccount()

  return (
    <div className={styles.stickyAccountWrap}>
      <div className={styles.stickyAccount}>
        <div className={styles.stickyAccountGoldPocketPic} />
        <div className={styles.stickyAccountTitleContainer}>
          <div className={styles.stickyAccountTitle}>
            <div>{account}</div>
            <div className={styles.unit}>元</div>
          </div>
        </div>
        <div
          className={styles.withdraw}
          onClick={() => {
            setDialogData({
              a: "3000",
              b: "123456789123456789",
              c: "xxxx打款",
              d: "提现成功,预计2小时到账",
              e: "0.3",
            })
            setShowDialog(true);
          }}
        />
      </div>

      {/* 金币飞入 */}
      {showCoinsFly && (
        <CoinsFly
          style={{
            top: '322rpx',
            left: '316rpx',
            zIndex: 1,
          }}
          onEnd={() => {
            setShowCoinsFly(false);
          }}
        />
      )}

      {/* 提现弹窗 */}
      {
        showDialog &&
        <WithdrawDialog
          data={dialogData}
          onClose={() => {
            refreshAccount(12.04);
            setShowDialog(false);
          }}
        />
      }
    </div>
  );
};

export default Account;

实现三:


总结:

  • 遵循着解耦以及内聚最小化的原则,将控制账户抽象为 hooks,后续可以在其他视图组件使用。
  • 这里其实稍微暴露了让我难受的一点,因为视图需要与状态和方法做逻辑交互,一来二去 hooks 要将近乎所有的状态方法都抛出...

实际上开发也可以将 Account 和 Dialog 单独做状态和逻辑的封装 hook

然而需求不会仅仅局限于 Account 账户组件中,那么需求来啦 ✈️ ✈️ ✈️ ✈️


实践总结|前端架构设计的一点考究(中)

目录
相关文章
|
3天前
|
监控 数据管理 开发者
构建高效微服务架构:后端开发的现代实践
【5月更文挑战第30天】 在当今软件开发领域,微服务架构已成为提高系统可维护性、扩展性和开发效率的关键方案。本文深入探讨了构建高效微服务架构的策略,包括服务划分原则、通信机制、数据管理以及持续集成与部署的最佳实践。通过分析具体案例和最新技术趋势,文章旨在为后端开发者提供一套全面的指导,帮助他们在不断变化的技术环境中保持竞争力。
|
3天前
|
Kubernetes API 持续交付
构建高效微服务架构:从理论到实践
【5月更文挑战第30天】 随着现代软件开发的演进,微服务架构已成为企业追求敏捷开发、持续交付和系统可扩展性的重要解决方案。本文将深入探讨如何构建一个高效的微服务系统,涵盖关键设计理念、技术栈选择、安全性考虑及持续维护策略。我们将通过具体案例分析,展示如何在实践中应用这些原则,以及如何应对常见的挑战,从而帮助读者构建出既健壮又灵活的后端系统。
|
2天前
|
敏捷开发 负载均衡 监控
探索微服务架构下的API网关设计与实践
【5月更文挑战第31天】本文将深入剖析微服务架构中的关键组件——API网关,探讨其设计理念、核心功能以及在实际项目中的应用。我们将从API网关的基本概念出发,逐步展开对其路由、负载均衡、认证授权、监控日志等方面的详细讨论,并结合实际案例,分析如何高效地实现和管理一个稳定的API网关。
|
2天前
|
缓存 监控 安全
微服务架构下的API网关设计与实践
【5月更文挑战第31天】本文深入探讨了在微服务架构中,API网关的核心作用与设计策略。通过分析网关的职责、选型标准及实现细节,文章为读者提供了一套完整的API网关解决方案。同时,结合具体案例,展示了如何在实际应用中有效部署和优化API网关,确保系统的高可用性和可扩展性。
|
2天前
|
运维 监控 Docker
构建高效微服务架构:从理论到实践构建高效自动化运维体系:Ansible与Docker的完美融合
【5月更文挑战第31天】 在当今软件开发的世界中,微服务架构已经成为了实现可伸缩、灵活且容错的系统的关键策略。本文将深入探讨如何从零开始构建一个高效的微服务系统,涵盖从概念理解、设计原则到具体实施步骤。我们将重点讨论微服务设计的最佳实践、常用的技术栈选择、以及如何克服常见的挑战,包括服务划分、数据一致性、服务发现和网络通信等。通过实际案例分析,本文旨在为开发者提供一套实用的指南,帮助他们构建出既健壮又易于维护的微服务系统。
|
2天前
|
敏捷开发 Java 持续交付
构建高效微服务架构:从理论到实践
【5月更文挑战第31天】 随着现代软件开发的复杂性日益增加,微服务架构已成为组织应对快速变化市场需求、实现敏捷开发和部署的关键解决方案。本文深入探讨了微服务架构的设计原则、技术选型以及实施策略,旨在为开发者提供一个清晰、高效的微服务构建蓝图。通过分析微服务的独立性、弹性和可扩展性等核心特性,结合具体案例,本文指导读者如何在实际项目中实现微服务的最佳实践,同时指出常见陷阱并提供规避策略,帮助团队提升开发效率,确保系统的稳定性与可靠性。
|
2天前
|
API 持续交付 开发者
构建高效微服务架构:策略与实践
【5月更文挑战第31天】 在现代软件开发领域,微服务架构已成为实现可扩展、灵活且容错的系统的首选模式。本文将深入探讨如何构建一个高效的微服务系统,从理论基础到具体实施步骤,再到性能优化和常见问题解决,为开发者提供一套全面的技术指导。我们将通过分析微服务的核心概念,展示如何利用容器化技术、API网关和持续集成/持续部署(CI/CD)流程来构建和维护健康的微服务生态系统。
|
3天前
|
消息中间件 监控 架构师
构建高效微服务架构:从理论到实践
【5月更文挑战第30天】 在当今快速迭代和竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性和独立部署能力受到企业的青睐。然而,随着服务的增多,确保系统的高效性和稳定性成为开发团队必须面对的挑战。本文将深入探讨构建高效微服务架构的关键策略,包括服务划分、通信机制、数据一致性和容错处理,并通过具体实例分析如何在不牺牲系统性能的前提下实现服务的解耦与自治。文章旨在为开发人员和架构师提供一套实用的方法论,帮助他们在设计和维护微服务系统时做出明智的决策。
|
4天前
|
项目管理 微服务
拥抱不确定性:技术实践中的敏捷思维构建高效微服务架构:后端开发的新趋势
【5月更文挑战第29天】 在快速变化的技术世界中,不确定性已成为常态。本文探讨了如何在技术实践中运用敏捷思维来应对不确定性,提出了一套实用的策略和心态调整方法。通过案例分析,展示了在项目开发、系统设计以及团队协作中如何有效地应用敏捷原则,以适应需求变动、技术演进和市场波动。文章强调了持续学习、灵活适应和以人为本的管理对于维持技术实践敏捷性的重要性,旨在为技术人员提供一种面对不断变化环境的心智工具箱。
|
4天前
|
负载均衡 监控 Kubernetes
构建高效微服务架构:API网关与服务发现的融合实践
【5月更文挑战第29天】 在当今的软件开发领域,微服务架构已成为一种流行的设计模式,其通过将应用程序拆分为一系列小型、自治的服务来提供灵活性和可扩展性。然而,随着服务数量的增加,确保通信效率和管理便捷性成为了关键挑战。本文聚焦于如何通过API网关和服务发现机制的有效整合,优化微服务间的交互,提高系统整体性能和可靠性。我们将探讨API网关在请求路由、负载均衡、安全性增强方面的作用,同时分析服务发现对于实现服务间动态通信的重要性,并展示两者如何协同工作以支持复杂的后端系统需求。