资讯专栏INFORMATION COLUMN

如何写出简洁、优雅、可维护的组件。

MingjunYang / 665人阅读

摘要:在里,通过的服务和依赖注入可以很轻松的实现,这里是我集中功能的服务文件文件功能类集合获取短信验证码这些功能需要用到的方法需要的地方只要注入这个服务就可以获取想要的功能。写成组件会有样式的限制,而这样写没有。

最近开始维护项目,然后我发现每天的时间常常是花在修改几个小功能上,改了问题有出了另一个问题,思考哪里不对?,然后这么一天就过去了。。然后我就开始思考标题好让我的时间不总是花在修改上,下面的是我最近的一些总结。

功能分离

这个算是面向对象里的思想,在组件里,有很多功能是独立的,比如最常见的发送验证码,确认密码等。把这些逻辑封装成一个或几个函数写在组件里的话,这在组件很小的时候没有什么影响,但是当组件功能比较复杂的时候,就会有些问题:

组件逻辑区域会变的很大,各种方法混杂很难一眼辨识

因为定义功能需要的变量和方法不在一起,导致修改麻烦

功能分离就是把这些功能抽离出来,写出一个类,然后在组件里引入。

下面是一个简单的弹窗控制的功能的类和这个类的使用:

export class DialogCtrl {
  isVisible = false;

  open () {
    this.isVisible = true;
  }
  close () {
    this.isVisible = false;
  }
}

然后在需要的组件里引入并实例化:

DialogCtrl = new this.CommonService.DialogCtrl();  // 是否打开弹窗

在html里可以直接这样用:

这个nz-modal是一个弹窗,在组件里我们只有一个变量的声明,如此简洁!而在html里DialogCtrl.isVisible,DialogCtrl.close()的形式也很容易理解它的作用和出处。

这样做的另一个好处是利于实现复用。对于可以复用的功能,比如上面发送验证码的逻辑,可以建一个全局的服务来提供。在angular里,通过angular的服务和依赖注入可以很轻松的实现,这里是我集中功能的common.service.ts服务文件:

common.servide.ts文件:

@Injectable()
export class CommonService {
  // 功能类集合
  public DialogCtrl = DialogCtrl;
  public MessageCodeCtrl = MessageCodeCtrl;
  public CheckPasswordCtrl = CheckPasswordCtrl;

  constructor(
    private http: HttpClient
  ) { }

  /* 获取短信验证码(这些功能需要用到的方法)
  -------------------------- */
  public getVerificationCode (phoneNum: string): Observable {
    return this.http.get("/account/short_message?phoneNumber=" + phoneNum);
  }
}

需要的地方只要注入这个服务就可以获取想要的功能。相比较直接建立一个组件来实现,我觉得这样写有一些优势:

灵活性更高。写成组件会有样式的限制,而这样写没有。

更简洁。写成组件,与之沟通只能通过子父组件的传入变量,监听子组件事件的方法,你使用的组件不可避免的会多出这些变量和方法。

状态管理

不知道大伙儿有没有这样的感觉,自己写新项目的时候觉得逻辑清晰,代码简练,功能也都实现了,但是过一段时间去看或者要改自己的代码的时候...哇,看不懂。

前端复杂的地方源于数不清的状态,于是我为那些有复杂状态的组件建立一个集中管理状态的对象(这里我取名为Impure):

  /* 变量定义 -- 状态
  -------------------------- */
  registerForm: FormGroup;  // 注册账号表单
  registerInfoForm: FormGroup; // 公司信息表单
  isSubmitting = false; // 表单是否正在提交
  nowForm = "registerForm";  // 当前正在操作的表单
  MessageCodeCtrl = new this.CommonService.MessageCodeCtrl(this.Msg, this.CommonService); // 验证码控制

  /* 变量定义 -- 定值
  -------------------------- */
  registerFormSubmitAttr = ["login", "password", "shortMessageCode", "roles", "langKey"];
  registerInfoFormFormSubmitAttr = ["simName", "contacter", "officeTel", "uid"];

  /* 改变状态事件
  -------------------------- */
  Impure = {

    // 表单初始化
    RegisterFormInit: () => this.registerForm = this.registerFormInit(),
    RegisterInfoFormInit: () => this.registerInfoForm = this.registerInfoFormInit(),

    // 验证码不合法
    MessageCodeInvalid: {
      notSend: () => this.Msg.error("您还未发送验证码"),
      notRight: () => this.Msg.error("验证码错误")
    },

    // 表单提交
    FormSubmit: {
      invalid: () => this.Msg.error("表单填写有误"),
      before: () => this.isSubmitting = true,
      registerOk: () => {
        this.Msg.success("账号注册成功");
        this.nowForm = "registerInfoForm";
      },
      registerInfoOk: () => {
        this.Msg.success("保存信息成功!请耐心等待管理员审核");
        this.Router.navigate(["/login"]);
      },
      fail: () => this.Msg.error("提交失败,请重试"),
      after: () => this.isSubmitting = false
    }
  };

这是一个简单的有两个表单的注册组件,因为两个表单html耦合度很高,所以写在了一起。

在组件内将变量分为状态和定值的两类,声明了一个Impure对象来集中管理这些状态,原则上这个组件里所有状态的改变都写在Impure里,而将事件触发的判断条件,数据处理写在Impure外面。

可以对比下这两个使用Impure和不使用Impure的表单提交方法:

  /* 注册账号表单提交(Impure)
  -------------------------- */
  async register (form) {
    const _ = this.Fp._; // ramda库,用于数据处理
    const { MessageCodeInvalid, FormSubmit } = this.Impure;

    // 表单不合法
    if (form.invalid) { FormSubmit.invalid(); return; }

    // 验证码不合法
    if (!this.MessageCodeCtrl.code) { MessageCodeInvalid.notSend(); return; }
    if (this.MessageCodeCtrl.code !== form.controls.shortMessageCode.value) { MessageCodeInvalid.notRight(); return; }

    // 表单提交
    FormSubmit.before();
    const data = _.compose(_.pick(this.registerFormSubmitAttr), _.map(_.prop("value")))(form.controls); // 数据处理
    const res = await this.AccountService.producerRegisterFirst(data).toPromise();
    if (!res) { FormSubmit.registerOk(); } else { FormSubmit.fail(); }
    FormSubmit.after();
  }

  /* 公司信息表单提交(非Impure)
  -------------------------- */
  async registerInfo ({ simName, contacter, officeTel }) {
    // 表单不合法
    if (this.registerInfoForm.invalid) { this.Msg.error("表单填写有误"); return; }

    // 表单提交
    this.isSubmitting = true;
    const data = { // 数据处理
      simName: simName.value,
      contacter: contacter.value,
      officeTel: officeTel.value,
      uid: this.registerForm.controls.phone.value
    };
    const res = await this.AccountService.producerRegisterSecond(data).toPromise();
    if (!res) {
      this.Msg.success("保存信息成功!请耐心等待管理员审核");
      this.Router.navigate(["/login"]);
    } else {
      this.Msg.error("提交失败,请重试");
    }
    this.isSubmitting = false;
  }

使用Impure管理状态后,逻辑清晰,在提交表单时你只需要关注事件发生的条件就可以了,而第二个条件和状态写在一起会很混乱,不能一眼清楚这个状态改变发生在什么时候,特别是你一段时间再来看的时候。

其实这里的数据处理(这里面的data)应该多带带拿出来写一个方法的,我只是来顶一下用纯函数来处理数据的优点,这里的_是用了ramda这个库。相比较第二个处理方式,第一种方式更加优雅,简洁,很容易看出数据的源头是什么(这里是form.controls),多带带抽离成数据处理函数也有很高的复用性。

假如某一天你要改下这里两个表格的成功后的状态,不再需要到这两个长长的提交函数里找到它们然后一个一个改,只要在Impure里面改就可以了,你甚至不需要看那两个提交的方法。

这样子,一个组件可以大致分为状态,状态管理(impure),改变状态的事件(状态改变的判断条件),和数据处理(纯函数)四部分,各司其职,很好维护。

结语

这些适合我但不一定适合所有人,每个人都有自己的风格,各位看官感受下就好。以后我有其它方面的总结也会拿出来分享。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88579.html

相关文章

  • 代码整洁之道

    摘要:代码写得是否整洁是客观的,是的人或后期维护的人觉得好才是真的好。三代码设计原则要想写出优雅整洁的代码,就要遵循特定的设计原则。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 最近在做一些项目重构的工作,看了不少脏乱差的代码,身心疲惫。本文将讨论如何编写整洁的代码,不求高效运行,只求...

    stefan 评论0 收藏0
  • 我是如何将业务代码写优雅

    摘要:高内聚低耦合高内聚低耦合一直是软件设计领域里亘古不变的话题,重构的目标是提高代码的内聚性,降低各功能间的耦合程度,降低后期维护成本,特别是写业务代码,这一点相当重要。0x00 前言 我是一名来自蚂蚁金服-保险事业群的前端工程师,在一线大厂的业务部门写代码,非常辛苦但也非常充实。业务代码不同于框架代码、个人项目或者开源项目,它的特点在于逻辑复杂、前后依赖多、可复用性差、迭代周期短,今天辛辛苦苦...

    voyagelab 评论0 收藏0
  • 阿里云前端周刊 - 第 31 期

    摘要:发布按照官方发布计划,的发布意味着进入阶段,彻底退出舞台,的还有半年结束。为了应对这个挑战,美团点评境外度假前端研发团队自年月起启动了面向端用户的赫尔墨斯项目。前端技术越来越复杂,有不低的技术门槛。 推荐 1. 利用 Dawn 工程化工具实践 MobX 数据流管理方案 https://zhuanlan.zhihu.com/p/... 项目在最初应用 MobX 时,对较为复杂的多人协作项...

    madthumb 评论0 收藏0
  • 程序员笔记——如何编写优雅Dockerfile

    摘要:导读要从容器化开始,而容器又需要从开始,本文将介绍如何写出一个优雅的文件。只要记住以上三点就能写出不错的。执行完成项目的构建。 导读 Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件。 文章主要内容包括: Docker容器 Dockerfile 使用多阶构建 感谢公司提供大量机器资源及时间让我们可以实践...

    曹金海 评论0 收藏0
  • JavaScript函数式编程,真香之组合(一)

    摘要:组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。其实我们函数式编程里面的组合也是类似,函数组合就是一种将已被分解的简单任务组织成复杂的整体过程。在函数式编程的世界中,有这样一种很流行的编程风格。 JavaScript函数式编程,真香之认识函数式编程(一) 该系列文章不是针对前端新手,需要有一定的编程经验,而且了解 JavaScript 里面作用域,闭...

    mengbo 评论0 收藏0

发表评论

0条评论

MingjunYang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<