• React---SOLID五种代码干净原则,提升代码可读性与可维护性。


    为了提升代码的可读性,可维护,可测试。保证代码的整洁干净(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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

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


    但是从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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    Product
    interface IProductProps {
      product: {
        rate: number;
        name: string;
        [key: string]: any;
      };
    }
    
    function Product(props: IProductProps) {
      return <div>这里有三百行代码来渲染产品相关的内容</div>;
    }
    
    export default Product;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    修改之后Index的render⬇️
    return (
        <div>
          <FilterRate filterRate={filterRate} handleRating={handleRating} />
          {filterProducts.map((product) => {
            return <Product product={product} />;
          })}
        </div>
      );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    现在的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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

    useRateFilter
    function useRateFilter() {
      const [filterRate, setFilterRate] = useState(1); // 过滤产品的🌟🌟级数
      // 设置过滤的星级
      const handleRating = (rate: number) => {
        setFilterRate(rate);
      };
      return { filterRate, handleRating };
    }
    
    export default useRateFilter;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    现在的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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    最终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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们开发一个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" && 
    button按钮后边跟一个前进的箭头
    } {role === "back" &&
    button按钮后边跟一个后退的箭头
    } */
    } {icon} </div> ); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    直接传一个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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    ClearCode

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

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

    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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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>
      );
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    新增的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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    不符合接口分离原则
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    一个正常的登陆页面,但是如果要复用的话就会有问题,复用的登陆地址不是同一个。
    因此他违背了依赖倒转原则:这个组件是为了具体实现而开发的。
    具体实现的是———在这个请求地址登陆的。复用的时候需要另一个地址,难道在组件里写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;Ï
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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


    参考视频
    感谢观看!!!

  • 相关阅读:
    Python编程基础:实验3——字典及集合的使用
    最长上升子序列模型
    wx.getLocation() 频繁调用会增加电量损耗及频率限制的替换解决方案
    x86架构上构建arm64架构的docker镜像
    判断一个矩阵是另一个矩阵的子矩阵C++
    git分布式版本控制系统(六)
    用好技巧,文件夹、文件重命名其实很简单
    Ubuntu22.04 部署Mqtt服务器
    监控易机房运维大屏:打造高效机房管理的新标杆
    设计模式——结构性设计模式
  • 原文地址:https://blog.csdn.net/BWater_monster/article/details/128167274