React---SOLID五种代码干净原则,提升代码可读性与可维护性。_ATWLee的博客-程序员宅基地

技术标签: TypeScript  OTHER  react.js  前端  React技术栈相关  javascript  

为了提升代码的可读性,可维护,可测试。保证代码的整洁干净(clear code)是很重要的一件事

SOLID五个原则实现你的ClearCode

1.Single Responsibility

单一责任:每一个类(组件)只负责做一件事
SRP: every class should have only one responsibility

错误示例
import {
     useEffect, useMemo, useState } from "react";

// 产品的类型,name,rate........
type ProductType = {
    
  name: string;
  rate: number;
  [key: string]: any;
};

export default function Index() {
    
  const [products, setProducts] = useState<ProductType[]>([]); // 产品的数据
  const [filterRate, setFilterRate] = useState(1); // 过滤产品的等级数

  const fetchProducts = async () => {
    
    // 发一个请求,然后setProducts一下
    const productsData = [
      {
    
        name: "产品1",
        rate: 5,
      },
      {
    
        name: "产品2",
        rate: 4,
      },
    ];
    setProducts(productsData);
  };

  // 获取所有的产品
  useEffect(() => {
    
    fetchProducts();
  }, []);

  // 设置过滤的等级
  const handleRating = (rate: number) => {
    
    setFilterRate(rate);
  };

  // 过滤出来的产品
  const filterProducts = useMemo(
    () => products.filter((product) => product.rate >= filterRate),
    [products, filterRate]
  );

  return (
    <div>
      {
    
        <div>
          这里是一个选择星星等级的dom,有个点击方法,调handleRating,选择过滤的等级
        </div>        
      }
      {
    filterProducts.map((product) => {
    
        return <div>这里有三百行代码来渲染产品相关的内容</div>;
      })}
    </div>
  );
}

这种页面很正常很普遍,我们一开始都是这么去写代码,请求数据,渲染内容,逻辑也不复杂。


但是从SOLID的角度他违反了单一职责的原则,我们来分析一下。️
这个页面做了以下事情:

  1. 数据的请求,涉及到了一个useEffect,一个function,一个useState
  2. 数据的过滤,涉及到了一个useState,一个function,一个useMemo
  3. dom的渲染,一个选择等级的dom,一个渲染产品的map循环(这里也不符合React的components思想)可以把这两部分拆成更小的component
ClearCode

1.首先,把dom部分拆分成两个更小的组件

FilterRate
interface IFilterProps {
    
  filterRate: number;
  handleRating: (rate: number) => void;
}
// 这里是选择等级的组件,props接收两个参数,分别是要过滤的等级和选择等级的回调
function FilterRate(props: IFilterProps) {
    
  return (
    <div>
      这里是一个选择星级的dom,有个点击方法,调handleRating,选择过滤的等级
    </div>
  );
}

export default FilterRate;
Product
interface IProductProps {
    
  product: {
    
    rate: number;
    name: string;
    [key: string]: any;
  };
}

function Product(props: IProductProps) {
    
  return <div>这里有三百行代码来渲染产品相关的内容</div>;
}

export default Product;
修改之后Index的render️
return (
    <div>
      <FilterRate filterRate={
    filterRate} handleRating={
    handleRating} />
      {
    filterProducts.map((product) => {
    
        return <Product product={
    product} />;
      })}
    </div>
  );

2.其次,数据的请求(一个useEffect,一个function,一个useState)需要一个自定义hook来处理

useProducts
interface ProductType {
    
  name: string;
  rate: number;
  [key: string]: any;
}
function useProducts() {
    
  const [products, setProducts] = useState<ProductType[]>([]);

  const fetchProducts = async () => {
    
    // 发一个请求,然后setProducts一下
    const productsData = [
      {
    
        name: "产品1",
        rate: 5,
      },
      {
    
        name: "产品2",
        rate: 4,
      },
    ];
    setProducts(productsData);
  };

  // 获取所有的产品
  useEffect(() => {
    
    fetchProducts();
  }, []);

  return {
     products };
}

export default useProducts;
现在的index️
import {
     useMemo, useState } from "react";
import FilterRate from "./components/filterRate";
import Product from "./components/product";
import useProducts from "./hook/useProducts";

export default function Index() {
    
  const {
     products } = useProducts(); // custom hook
  const [filterRate, setFilterRate] = useState(1); // 过滤产品的级数
  // 设置过滤的星级
  const handleRating = (rate: number) => {
    
    setFilterRate(rate);
  };

  // 过滤出来的产品
  const filterProducts = useMemo(
    () => products.filter((product) => product.rate >= filterRate),
    [products, filterRate]
  );

  return (
    <div>
      <FilterRate filterRate={
    filterRate} handleRating={
    handleRating} />
      {
    filterProducts.map((product) => {
    
        return <Product product={
    product} />;
      })}
    </div>
  );
}

3.数据的过滤也需要一个custom hook

useRateFilter
function useRateFilter() {
    
  const [filterRate, setFilterRate] = useState(1); // 过滤产品的级数
  // 设置过滤的星级
  const handleRating = (rate: number) => {
    
    setFilterRate(rate);
  };
  return {
     filterRate, handleRating };
}

export default useRateFilter;
现在的index️
import {
     useMemo } from "react";
import FilterRate from "./components/filterRate";
import Product from "./components/product";
import useProducts from "./hook/useProducts";
import useRateFilter from "./hook/useRateFilter";

export default function Index() {
    
  const {
     products } = useProducts(); // custom hook
  const {
     filterRate, handleRating } = useRateFilter();

  // 过滤出来的产品
  const filterProducts = useMemo(
    () => products.filter((product) => product.rate >= filterRate),
    [products, filterRate]
  );

  return (
    <div>
      <FilterRate filterRate={
    filterRate} handleRating={
    handleRating} />
      {
    filterProducts.map((product) => {
    
        return <Product product={
    product} />;
      })}
    </div>
  );
}

4.ok,还差一个useMemo过滤
其实就是一个过滤方法,可以把它写在FilterRate组件里,也可以写在Product组件里,也可以定义个utils文件夹,在里面写。这里选择在Product组件里写。

Product
interface IProduct {
    
  rate: number;
  name: string;
  [key: string]: any;
}
interface IProductProps {
    
  product: IProduct;
}

export const filterProducts = (products: IProduct[], filterRate: number) => {
    
  return products.filter((product) => product.rate >= filterRate);
};

export const Product = (props: IProductProps) => {
    
  return <div>这里有三百行代码来渲染产品相关的内容</div>;
};
最终clearCode之后的代码️
import FilterRate from "./components/filterRate";
import {
     Product, filterProducts } from "./components/product";
import useProducts from "./hook/useProducts";
import useRateFilter from "./hook/useRateFilter";

export default function Index() {
    
  const {
     products } = useProducts();
  const {
     filterRate, handleRating } = useRateFilter();
  return (
    <div>
      <FilterRate filterRate={
    filterRate} handleRating={
    handleRating} />
      {
    filterProducts(products, filterRate).map((product) => {
    
        return <Product product={
    product} />;
      })}
    </div>
  );
}

2.Open Closed

开闭原则:软件实体应该是可拓展的而不是可修改的
OCP: software entities should be open for extension but closed for modification"

错误示例
type ButtonProps = {
    
  role: "forward" | "back";
};
export default function Index(props: ButtonProps) {
    
  const {
     role } = props;
  return (
    <div>
      <button>这是一个按钮</button>
      {
    role === "forward" && <div>button按钮后边跟一个前进的箭头</div>}
      {
    role === "back" && <div>button按钮后边跟一个后退的箭头</div>}
    </div>
  );
}

我们开发一个Button组件,按钮后边需要跟一个图标,一开始计划是有前进图标或后退图标。
但是从SOLID的角度他违反了开闭原则️
1.如果需要的不是前进后退的箭头,需要的是上箭头或者下箭头,那就要修改源代码

ClearCode
type ButtonProps = {
    
  // role: "forward" | "back";
  icon: React.ReactNode;
};
export default function Index(props: ButtonProps) {
    
  const {
     icon } = props;
  return (
    <div>
      <button>这是一个按钮</button>
      {
    /* {role === "forward" && <div>button按钮后边跟一个前进的箭头</div>}
      {role === "back" && <div>button按钮后边跟一个后退的箭头</div>} */}
      {
    icon}
    </div>
  );
}

直接传一个ReactNode,这样直接传一个icon即可。icon可以是任何图标,即满足了可拓展,又不需要修改源代码
常见的如antd的Button组件的icon属性


3.Liskov Substitution

里氏替换原则:子类型对象应该可以替代超类型对象
LSP: subtype objects should be substitutable for supertype objects

错误示例
interface IInputProps {
    
  placeholder?: string;
  isBold?: boolean;
}
// 封装一个原生input组件,有个额外的参数isBold来控制字体是否加粗
export default function Index(props: IInputProps) {
    
  const {
     isBold, placeholder = "这是封装的input组件" } = props;
  return (
    <div>
      <input
        type="text"
        placeholder={
    placeholder}
        style={
    {
     fontWeight: isBold ? "bold" : "normal" }}
      />
    </div>
  );
}

没有满足里氏替换原则,子类对象(这个Index组件)不可以替代超类对象(原生input)。换句话说,封装的这个组件只替换了input的placeholder属性。再简单点说,封装的这个组件只能改个placeholder。

ClearCode

1.首先,要继承一下超类的属性

interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
    
  isBold?: boolean;
}

2.其次,解构剩余props,替换超类对象

interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
    
  isBold?: boolean;
}
export default function Index(props: IInputProps) {
    
  const {
     isBold, placeholder = "这是封装的input组件", ...restProps } = props;
  return (
    <div>
      <input
        type="text"
        placeholder={
    placeholder}
        style={
    {
     fontWeight: isBold ? "bold" : "normal" }}
        {
    ...restProps}
      />
    </div>
  );
}

4.Interface Segregation

接口分离原则:客户端不需要依赖于它不使用的接口(组件不应依赖于它用不到的props)
ISP: clients should not depend upon interfaces that they don’t use

错误示例

让我们回到Single Responsibility中的产品组件️

import Thumbnail from "./thumbnail";
interface IProduct {
    
  rate: number;
  name: string;
  imageUrl: string;
  [key: string]: any;
}
export interface IProductProps {
    
  product: IProduct;
}
export const Product = (props: IProductProps) => {
    
  const {
     product } = props;
  return (
    <div>
      {
    /* 这里是个产品缩略图 */}
      <Thumbnail product={
    product} />
      这里有三百行代码来渲染产品相关的内容
    </div>
  );
};

新增的Thumbnail组件️

import type {
     IProductProps } from "./product";

interface ThumbnailProps extends IProductProps {
    }

function Thumbnail(props: ThumbnailProps) {
    
  return (
    <div>
      <img src={
    props.product.imageUrl} alt="" />
    </div>
  );
}

export default Thumbnail;

不符合接口分离原则
1.Thumbnail只需要产品里面的imageUrl属性,但他拿到了所有的product

ClearCode
<Thumbnail imageUrl={
    product.imageUrl} />


interface ThumbnailProps {
    
  imageUrl: string;
}

function Thumbnail(props: ThumbnailProps) {
    
  return (
    <div>
      <img src={
    props.imageUrl} alt="" />
    </div>
  );
}

export default Thumbnail;

5.Dependency Inversion

依赖倒转原则:一个实体应该依赖于抽象而不是具体实现(组件应该更独立,更拓展,而不是为了具体的实现而重写)
DIP: one Entity should depend upon abstractions not concretions

错误示例
import React, {
     useState } from "react";

function IForm() {
    
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    
    e.preventDefault();
    // 发起登陆请求
    await fetch("http://请求地址", {
    
      body: JSON.stringify({
    
        email,
        password,
      }),
    });
  };

  return (
    <div>
      <form action="" onSubmit={
    handleSubmit}>
        <input
          type="email"
          name="email"
          value={
    email}
          onChange={
    (e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          name="password"
          value={
    password}
          onChange={
    (e) => setPassword(e.target.value)}
        />
      </form>
    </div>
  );
}

export default IForm;

一个正常的登陆页面,但是如果要复用的话就会有问题,复用的登陆地址不是同一个。
因此他违背了依赖倒转原则:这个组件是为了具体实现而开发的。
具体实现的是———在这个请求地址登陆的。复用的时候需要另一个地址,难道在组件里写if-else吗,肯定不行的。那可以把地址传进来呀,但是不能保证在请求又有其它的逻辑。

ClearCode
import React, {
     useState } from "react";

function IForm(props: {
     onSubmit: (email: string, password: string) => void }) {
    
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const {
     onSubmit } = props;

  const handleSubmit = async (e: React.FormEvent) => {
    
    e.preventDefault();
    onSubmit(email, password);
  };

  return (
    <div>
      <form action="" onSubmit={
    handleSubmit}>
        <input
          type="email"
          name="email"
          value={
    email}
          onChange={
    (e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          name="password"
          value={
    password}
          onChange={
    (e) => setPassword(e.target.value)}
        />
      </form>
    </div>
  );
}

export default IForm;Ï

这样就可以在调用这个组件的时候通过onSubmit来进行不同的登陆接口的处理。
这样就可以说,这个组件的登陆实现依赖于onSubmit的回调(是抽象的),而不是具体在组件内部实现(具体的登陆接口写在组件内)。简单点说,就是不把登陆接口写死在组件内。


参考视频
感谢观看!!!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/BWater_monster/article/details/128167274

智能推荐

2016 ACM/ICPC Asia Regional Qingdao Online 1001-程序员宅基地

题目大意:给一个数字n,求出大于等于n的第一个满足如下条件的数:2^a*3^b*5^c*7^d;解题思路:一般这种类型的题目思路有两种:1、提前打表,当输入n后在打好的表中进行查询;2、按照题目描述进行模拟,即从n开始,判断每一个数字是否符合条件对于本题,若采用第二种思路会TLE,因此应采用第一种思路,而第一种思路中包含两个过程,打表和查询,需要分别最这两个过程进行效率优化,否则同样会TL

TCP拥塞控制机制-程序员宅基地

当延迟出现时,为防止通讯双方不断重传,在网络中注入过多数据,所以建立了拥塞控制机制。而拥塞控制包括四个算法:慢启动,拥塞避免,快重传,快恢复。先来介绍一些背景:1.TCP有一个计时器,可以判断超时。2.快速重传指接收方收到失序报文段后立刻发出重传确认。而快速重传算法规定发送方一旦收到3个重复确认就立即重传,不用等到计时器超时。以上是背景。建立连接后首先进入慢启动算法,以1为拥塞窗口,每次传输成功将窗口大小翻倍,直到窗口大小达到阈值。达到阈值后开始拥塞避免算法,控制窗口的递增速度从翻倍的指数级减小

第十章 shell的交互功能与shell程序设计----操作系统原理和实践_神迹小卒的博客-程序员宅基地

本章主要内容Shell的启动和功能简介 shell识别的命令形式 输入输出重定向和管道 shell变量和引用符 Shell脚本程序的建立与运行 shell的语句类别 *流编辑器sed和报表生成器awk简介第十章 shell的交互功能与shell程序设计UNIX系统中的Shell具有两大功能:命令解释器: 解释用户发出的各种操作系统命令 程序设计语言: 功能强大, 可包容引用所有的操作系统命令和可执行程序。 10.1 shell 的启动和终止用户登录(......_shell程序设计

mysql 宏_Mysql++学习(五)------专用SQL结构-程序员宅基地

专用SQL结构(SSQLS) 特性可以让你很轻易的定义用来匹配SQL表的C++结构.最通俗的理解:对于SQL表中的每一个字段,SSQLS结构都有一个变量与之对应.但是,MySQL++内部也会使用SSQLS的其他方法,操作符来提供简洁的功能.要定义SSQLSes,需要使用定义在ssqls.h中的宏,这也是MySQL++唯一没有自动加入mysql++.h的头文件.sql_create假如你有如下SQL..._mysql 宏

u盘安装centos7及简单配置-程序员宅基地

https://blog.csdn.net/lianshaohua/article/details/88381039这里有详细方案,很有效注意点:1.在配置中:安装位置进去后如果本机有多个盘要选中各个盘(插入的u盘不用选)才能进入手动分区

Eclipse Jetty插件安装(深入版)-程序员宅基地

1.安装前需要准备的jar包: - jetty-distribution-9.2.26.v20180806.zip - axis2-1.7.8-war.zip - axis2-1.7.8-bin.zip

随便推点

脉冲神经网络的背景介绍-程序员宅基地

脉冲神经网络(Pulse Neural Network,PNN)是一种新型的神经网络,它的核心思想是利用脉冲函数来模拟神经元的激活过程。与传统的神经网络不同,PNN可以直接处理分布数据,具有较强的非线性分类能力。PNN是基于核函数的一种模型,可以自动学习数据的高阶特征,并且能够将多个特征组合起来进行分类。PNN在模式识别、图像分类、文本分类等领域有着广泛的应用。总的来说,PNN是一种新颖且具有...

1080:余数相同问题--信息学一本通(c++)_c++1080余数相同问题-程序员宅基地

NOIP信息学奥赛资料下载时间限制: 1000 ms 内存限制: 65536 KB提交数: 11258 通过数: 7479【题目描述】已知三个正整数a,b,c。现有一个大于1的整数x,将其作为除数分别除a,b,c,得到的余数相同。请问满足上述条件的x的最小值是多少?数据保证x有解。【输入】一行,三个不大于1000000的正整数a,b,c,两个整数之间用一个空格..._c++1080余数相同问题

SRAM的存储原理_sram书籍-程序员宅基地

RAM的存储原理ram的主要作用是存储数据和代码供cpu调用。但这些数据并不像是装大米一样很简单的就装进去,我们在装进去的同时还要保证可以在需要的时候拿出来。可以类比图书馆的书籍管理,假如每本书编号都由两部分组成,一个行编号+一个列编号,已知一本书的编号是34,则我们锁定在第3行和第4列,这样就找到了这本书了。在RAM存储器中也利用了相似的原理。现在让我们回到ram存储器上,对于ram来说,其..._sram书籍

5.数据库访问_数据库的访问-程序员宅基地

本文是读《Spring Boot2精髓-从构件小系统到架构分布式大系统》的读书笔记。本章介绍 MVC 中的后端视图技术, 一种是后端模板引擎Beetl,用于渲染模板;另外一种是 JSON 序列化技术 Jackson 。Beetl这里不多写对于 JSON 的序列化和反序列化技术,也有很多工具可以采用,如国内的 Fastjson , 国外的Jackson 、 Gson . Jackson 是 Spring Boot 内 置的,也是 Spring Boot 相关很多开源产品内置的序列化工具,与Beetl _数据库的访问

envi中如何导出多波段数据和三波段数据_用envi提取3个波段_生态与遥感应用的博客-程序员宅基地

在envi中有时我们需要导出波段合成后的三个波段的数据,而有时为了转换数据格式需要将数据的所有波段全部导出。这里我们分别讲一下如何进行两种数据的导出。一、导出三波段的数据(1)在envi中打开要导出的数据,并进行数据的波段组合。(2)鼠标在数据右击,选择Export Layer to tiff,这样导出的数据就是仅有选择好的三个波段的数据了。二、导出多波段数据 (1)打开影像后,选择file>save as>save as...(ENVI..._用envi提取3个波段

DB-Engines 2017年7月数据库排名发布 Oracle再逢反转-程序员宅基地

近日,DB-Engines发布了2017年7月数据库排名。数据库排行又是一轮洗牌后,本轮数据库排名在2017年下半年首月迈入新格局。首先是接连遭到比分威胁的Oracle终于在7月迎来局势反转,得分反弹数值高达23.11,与次席MySQL的分差再度拉大至25分开外,霸主地位之稳固可见一斑。同时,保持较长一段时间稳定的前十名排行本月亦出现波动:搜索引擎式数据库Elasticsearch成功跻身前十之列...

推荐文章

热门文章

相关标签