SOLID五种设计模式 REACT === 更干净可读的代码
admin
2024-03-07 11:59:42
0

概述

设计模式,是一个面试过程中经常问到的问题。其实有时候在我们日常开发当中,已经在使用了,只不过没有注意到而已

本文就通过面向对象编程中,最基本的五种设计模式S.O.L.I.D.,如何在React当中应用,来告诉大家,其实他们是很简单的。

本文主要参考了一个视频,B站的版本在这里

S - SRP 单一职责原则

基本含义:一个类只应该负责一件事

在React当中,可以简单理解为一个组件、Hook只应该做一件事

下面的代码中,是一个正常React组件:

你不必看完这个低码,粗略看一下即可

export function Bad() {const [products, setProducts] = useState([]);const [filterRate, setFilterRate] = useState(1);const fetchProducts = async () => {const response = await axios.get('https://fakestoreapi.com/products');if (response && response.data) setProducts(response.data);};useEffect(() => {fetchProducts();}, []);const handleRating = (rate: number) => {setFilterRate(rate);};const filteredProducts = useMemo(() => products.filter((product: any) => product.rating.rate > filterRate),[products, filterRate],);return (
Minimum Rating filterRate}SVGclassName="inline-block"onClick={handleRating}/>
{filteredProducts.map((product: any) => (...))}
); }

看起来也还不错,但实际上,他其实做了很多事情

  • 获取数据
  • 筛选
  • 渲染星级和产品列表

严格上来说,这不符合SRP,来让我们稍微改造一下


// 可以看到,我封装了
// Product商品组件,这是个纯UI组件
// useProducts custom hook
// useRateFilter custom hook
export function Good() {const { products } = useProducts();const { filterRate, handleRating } = useRateFilter();return (
filterRate as number}handleRating={handleRating}/>
{filterProducts(products, filterRate).map((product: any) => (product} />))}
); }

首先说明一个点,当在写React 代码的时候

  • 如果遇到 useStateuseEffect 来获取数据的时候,应该首先去考虑封装成一个custom hook
  • 如果遇到map去遍历一个比较长的组件时,应该首先考虑封装成一个纯UI组件

在上面的代码中,数据获取在一个custom hook中,筛选数据也被单独放到了hook中,渲染产品列表也被单独封装为一个dummy component , 即纯UI组件。

怎么样是不是代码干净可读多了?

O - OCP 开闭原则

基本含义:类允许被扩展,但不允许被修改

听起来有些矛盾,但理解了他的意思之后,就会变的很简单。

考虑以下代码

interface IMyButtonProps = {role: 'back' | 'forward' | 'home';
}
export const MyFuncyButton = (props: IMyButtonProps) => {return <>....
}

在上面的例子当中,按钮分为了三种类型,包括前进、后退、返回主页。一切良好,但如果有一天我想添加一个比如支付的按钮,那该怎么办么?

恐怕只有去改代码内部,这显然是与OCP原则不符的。

所以我们看主流的UI框架,他们的按钮主题都是按照颜色维度去区分的,而不是业务类型,而内部的文案由props传入。这样足够满足各种需求。

主要按钮成功按钮信息按钮警告按钮危险按钮

这里还要说明一点,开闭原则有一个超级经典的使用场景,就是装饰器

@Entity()
export class Blog {@PrimaryGeneratedColumn()id: number;
}

以上是Nest.js的一个实体类,对于这个Blog类,如果我不添加@Entity()注解,那么这就是一个普通的类,但如果我添加了这个注解,他就是一个map到数据库的实体类。即为该类添加了功能,但并没有改变类本身的源代码

我还可添加更多的注解,比如打印日志等等,可以实现同样的目的。

L - LSP 里氏替换原则

基本含义: 基类设定一系列的规范和契约,子类需要遵守

这个原则比较抽象,但个人感觉,这无疑是强类型语言的基石。

我举一个最简单的例子。

试想,在Javascript当中,对于任何一个对象,我们是不是都可以调用toString()方法。这是为什么?

可能大家会说,原型链的顶端定义了toString(),其实这已经回答了里氏替换原则。

因为我们可以认为,原型链顶端,也就是Object.prototype这个原型对象,他就是JS当中所有对象的父类,或说超类。他身上的属性方法就像是一个契约,所有的子类都满足这个契约。

这一点非常重要,比如

class 猿人 {吃饭() {console.log('我会吃饭')}睡觉() {}
}
class 你 extends 猿人 {
}interface xxx {listYourBehaviors(person: 猿人);
} 

上述代码中listYourBehaviors方法接受一个猿人类型,但因为继承自猿人,所以如果我入参传入,那么也不会报错。

显然这是一种契约,需要开发者去遵守。

说的有点多。在React当中,我们经常会封装组件,比如

export const MyFuncyInput = () => {return 
}

假设你封装了一下input组件。可能你的组件很牛~,很多技术小伙伴都在用,但有一天,一个人和你说,我使用组件的时候,传入了一个placeholder,发现并没有生效,这是怎么回事?

额,当然不会生效,因为你没有把placeholder添加到iinput上…

在这里,你可以认为input就是父类,而你封装的MyFuncyInput就是子类,那么父类具备的属性placeholder,子类也应该去实现,当然,如果挨个写会很麻烦,所以可以这样写

interface IMyFuncyInputProps extends React.HTMLInputAttributes {isLargeInput: boolean
}
export const MyFuncyInput = (props: IMyFuncyInputProps) => {const {isLargeInput, ...restProps} return {isLargeInput ? ...} {...restProps} /> : ...restProps} />
}

每当我们封装一个组件的时候,都应该想到,对于父组件本身原有的属性,我们应该怎样处理,去符合里氏替换原则。

I - ISP 接口分离原则

基本含义:客户端不应依赖于他不使用的接口

这个是非常简单的,在React当中,他的意思一个不应向组件传入其不使用的Props

有人可能觉得为什么要这样干?

考虑一下代码

product} />

thumbnail就是产品缩略图的意思,当上面的组件如果被其他任何人使用,是否会觉得会不清楚,为什么我需要传入一个完整的product对象进行。缩略图不应该是需要一个图片地址么?

所以可以调整如下

product.imgUrl} />

这是不是就清楚多了,其实很多时候,代码的干净整洁都是一点点积累出来的。关注每一个小点,可以最终让你的应用更容易维护。比如上边的情况,如果用户获取不到product对象,他只能获取到订单对象,订单上也包含缩略图,那么他就不能使用你的组件了么?

D - DIP 依赖倒转原则

基本含义:接口依赖于抽象,而不是具体实现

我记得这里有个经典的例子,就是计算机各部件的组成,比如,USB接口,无论你是摄像机,鼠标,键盘,无论实现了什么样的功能,这些具体功能对于电脑而言是隐藏的。对外暴露的只是通过USB这个接口去进行数据传递。

试想如果一个鼠标需要对各种类型的电脑适配不同的接口,那绝对是个灾难。

在React当中,我们其实已经会不知觉的大量使用了,因为react的是一门函数式的库,他的UI函数是通过Props去传递的,Props就是所谓的接口。比如

export const SubmitForm = () => {const onSubmit = () {api.sendRequest('https://xxx.com/api/product');}return 
onSubmit}> }

对于上边的表单提交组件,如果有一天,我希望能够将其复用到其他地方,但提交的URL不一样,那么显然,对不不同使用这个表单的地方,都应该只关注该表单的接口。即,他可以改成下面这样

interface ISubmitFrom {onSubmit: () => void
}
export const SubmitForm = (props: ISubmitFrom) => {const onSubmit = () {props.onSubmit();}return onSubmit}>
}

如上,即定义了一个接口,他会指定一个回调函数,就是onSubmit,任何使用这个组件的人,是需要是实现回调的逻辑就可以了。也就是所谓的面向接口编程

其实,我们想象一下,在前后端开发联调的过程中,大家都是按照接口文档是实现。比如前端开发,你会去注意后端逻辑是怎么实现的么,你会关注,比如产品列表,后台是直接读MySQL数据库,还是走Redis缓存?

结束语

恭喜你,看到了最后,也感谢你的时间。

如果你是直接跳到这里懒得看,或者想可视化的去了解一下,可以看这个视频。

代码开发就像刘备说的一句话,“勿以善小而不为”,好的代码习惯都是一点点的积累起来的。当你坚持了一段时间,再回首,就发现自己的代码已经很干净优雅了。

相关内容

热门资讯

关于虚荣心 关于虚荣心正所谓爱美之心人皆有之,人人都希望自己能有一个好的形象,渐渐的产生了虚荣之心.虚荣从一定意...
笑傲江湖没演完怎么就演贤妻了呢... 笑傲江湖没演完怎么就演贤妻了呢!明天在播 大结局 后篇明天会有,等待吧7:30播
想咨询心理专家杨凤池,怎样联系... 想咨询心理专家杨凤池,怎样联系他?可以直接去北京找他。尽管可能会排在三年之后,但是还是有机会,不是么...
天热这6道菜端上桌,比肉受欢迎... 天热这6道菜端上桌,比肉受欢迎,营养开胃,老少皆宜。天气炎热,没有胃口吃饭的吃饭的朋友看过来,下面分...
求几部类似于潘多拉之心,交响诗... 求几部类似于潘多拉之心,交响诗篇,灼眼夏娜,零之使魔龙之界点武器种族传说,男女主角15岁的楼下的龙之...
阿杜的人狼是哪个专辑的? 什么... 阿杜的人狼是哪个专辑的? 什么时候发行的?如题阿杜在2010年2月26日出了最新专辑《没什么好怕》人...
星辰变通灵草哪里最多有没有地图... 星辰变通灵草哪里最多有没有地图坐标?星辰变通灵草哪里最多有没有地图坐标?竹林谷 有18处
我对一个女孩有好感他给我唱不完... 我对一个女孩有好感他给我唱不完美小孩是什么意思呀求解答我对一个女孩有好感他给我唱不完美小孩是什么意思...
天下3什么时候开新区? 天下3什么时候开新区?今天就开了啊这个谁能知道呢。。。只能看官网的通告了。不过一般比较大的节日应该都...
虾仁睡觉都需要放什么 虾仁睡觉都需要放什么哈,是虾仁水饺吧?可放少许韭菜及鸡蛋,除正常调料外,再加点胡椒粉。
在幼儿园不说话 在幼儿园不说话病情描述(主要症状、发病时间):我家是两个孩子,平时在家说话,干什么都挺好,可是在幼儿...
世界上有那么痴情的男人吗? 世界上有那么痴情的男人吗?我们都是有家庭的人,他一直都喜欢着我,现在说如果我答应跟他,他会每月瞒着他...
孕妇怎么穿着时尚 孕妇怎么穿着时尚 孕期是女人生命中一段特殊的日子,注重自己形象的孕妈咪不仅会在这段时期体现一贯的高贵...
中译日。。。高手请进。。。。 中译日。。。高手请进。。。。展覧品(てんらんひ搜芦ん)非売品(ひばいひん友困)商売が繁盛する(しょう...
我想听一些 伤感的 外文歌~~... 我想听一些 伤感的 外文歌~~~ 还有,要是女的唱的哦 ~~呵呵!多介绍点哦~~my heart w...
女朋友发『我不是主角,我只是舞... 女朋友发『我不是主角,我只是舞台角落边的一个小丑』这个说说,要怎么回复。急急急!!!(你是我的主角就...
有没有一个男人为了自己心爱的女... 有没有一个男人为了自己心爱的女人而放弃一切的,甚至生命的有没有?有,比如我本人,曾经为了自己喜欢的人...
赛尔号2水系最厉害的是什么精灵 赛尔号2水系最厉害的是什么精灵目前水系精灵很少,硬要说的话,水墨点点、阿卡纳斯、迪兰特都比较可以,其...
圣安地列斯飞机秘籍 圣安地列斯飞机秘籍jumpjet
白内障早期有哪些症状,流泪看东... 白内障早期有哪些症状,流泪看东西模糊眼白内障早期症状是看东西眼睛像蒙了一层塑料布似的早期白内障是我不...