技术标签: TypeScript OTHER react.js 前端 React技术栈相关 javascript
为了提升代码的可读性,可维护,可测试。保证代码的整洁干净(clear code)是很重要的一件事
单一责任:每一个类(组件)只负责做一件事
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.首先,把dom部分拆分成两个更小的组件
interface IFilterProps {
filterRate: number;
handleRating: (rate: number) => void;
}
// 这里是选择等级的组件,props接收两个参数,分别是要过滤的等级和选择等级的回调
function FilterRate(props: IFilterProps) {
return (
<div>
这里是一个选择星级的dom,有个点击方法,调handleRating,选择过滤的等级
</div>
);
}
export default FilterRate;
interface IProductProps {
product: {
rate: number;
name: string;
[key: string]: any;
};
}
function Product(props: IProductProps) {
return <div>这里有三百行代码来渲染产品相关的内容</div>;
}
export default Product;
return (
<div>
<FilterRate filterRate={
filterRate} handleRating={
handleRating} />
{
filterProducts.map((product) => {
return <Product product={
product} />;
})}
</div>
);
2.其次,数据的请求(一个useEffect,一个function,一个useState)需要一个自定义hook来处理
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;
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
function useRateFilter() {
const [filterRate, setFilterRate] = useState(1); // 过滤产品的级数
// 设置过滤的星级
const handleRating = (rate: number) => {
setFilterRate(rate);
};
return {
filterRate, handleRating };
}
export default useRateFilter;
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组件里写。
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>;
};
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>
);
}
开闭原则:软件实体应该是可拓展的而不是可修改的
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.如果需要的不是前进后退的箭头,需要的是上箭头或者下箭头,那就要修改源代码。
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属性
里氏替换原则:子类型对象应该可以替代超类型对象
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。
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>
);
}
接口分离原则:客户端不需要依赖于它不使用的接口(组件不应依赖于它用不到的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
<Thumbnail imageUrl={
product.imageUrl} />
interface ThumbnailProps {
imageUrl: string;
}
function Thumbnail(props: ThumbnailProps) {
return (
<div>
<img src={
props.imageUrl} alt="" />
</div>
);
}
export default Thumbnail;
依赖倒转原则:一个实体应该依赖于抽象而不是具体实现(组件应该更独立,更拓展,而不是为了具体的实现而重写)
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吗,肯定不行的。那可以把地址传进来呀,但是不能保证在请求又有其它的逻辑。
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的回调(是抽象的),而不是具体在组件内部实现(具体的登陆接口写在组件内)。简单点说,就是不把登陆接口写死在组件内。
参考视频
感谢观看!!!
题目大意:给一个数字n,求出大于等于n的第一个满足如下条件的数:2^a*3^b*5^c*7^d;解题思路:一般这种类型的题目思路有两种:1、提前打表,当输入n后在打好的表中进行查询;2、按照题目描述进行模拟,即从n开始,判断每一个数字是否符合条件对于本题,若采用第二种思路会TLE,因此应采用第一种思路,而第一种思路中包含两个过程,打表和查询,需要分别最这两个过程进行效率优化,否则同样会TL
当延迟出现时,为防止通讯双方不断重传,在网络中注入过多数据,所以建立了拥塞控制机制。而拥塞控制包括四个算法:慢启动,拥塞避免,快重传,快恢复。先来介绍一些背景:1.TCP有一个计时器,可以判断超时。2.快速重传指接收方收到失序报文段后立刻发出重传确认。而快速重传算法规定发送方一旦收到3个重复确认就立即重传,不用等到计时器超时。以上是背景。建立连接后首先进入慢启动算法,以1为拥塞窗口,每次传输成功将窗口大小翻倍,直到窗口大小达到阈值。达到阈值后开始拥塞避免算法,控制窗口的递增速度从翻倍的指数级减小
本章主要内容Shell的启动和功能简介 shell识别的命令形式 输入输出重定向和管道 shell变量和引用符 Shell脚本程序的建立与运行 shell的语句类别 *流编辑器sed和报表生成器awk简介第十章 shell的交互功能与shell程序设计UNIX系统中的Shell具有两大功能:命令解释器: 解释用户发出的各种操作系统命令 程序设计语言: 功能强大, 可包容引用所有的操作系统命令和可执行程序。 10.1 shell 的启动和终止用户登录(......_shell程序设计
专用SQL结构(SSQLS) 特性可以让你很轻易的定义用来匹配SQL表的C++结构.最通俗的理解:对于SQL表中的每一个字段,SSQLS结构都有一个变量与之对应.但是,MySQL++内部也会使用SSQLS的其他方法,操作符来提供简洁的功能.要定义SSQLSes,需要使用定义在ssqls.h中的宏,这也是MySQL++唯一没有自动加入mysql++.h的头文件.sql_create假如你有如下SQL..._mysql 宏
https://blog.csdn.net/lianshaohua/article/details/88381039这里有详细方案,很有效注意点:1.在配置中:安装位置进去后如果本机有多个盘要选中各个盘(插入的u盘不用选)才能进入手动分区
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是一种新颖且具有...
NOIP信息学奥赛资料下载时间限制: 1000 ms 内存限制: 65536 KB提交数: 11258 通过数: 7479【题目描述】已知三个正整数a,b,c。现有一个大于1的整数x,将其作为除数分别除a,b,c,得到的余数相同。请问满足上述条件的x的最小值是多少?数据保证x有解。【输入】一行,三个不大于1000000的正整数a,b,c,两个整数之间用一个空格..._c++1080余数相同问题
RAM的存储原理ram的主要作用是存储数据和代码供cpu调用。但这些数据并不像是装大米一样很简单的就装进去,我们在装进去的同时还要保证可以在需要的时候拿出来。可以类比图书馆的书籍管理,假如每本书编号都由两部分组成,一个行编号+一个列编号,已知一本书的编号是34,则我们锁定在第3行和第4列,这样就找到了这本书了。在RAM存储器中也利用了相似的原理。现在让我们回到ram存储器上,对于ram来说,其..._sram书籍
本文是读《Spring Boot2精髓-从构件小系统到架构分布式大系统》的读书笔记。本章介绍 MVC 中的后端视图技术, 一种是后端模板引擎Beetl,用于渲染模板;另外一种是 JSON 序列化技术 Jackson 。Beetl这里不多写对于 JSON 的序列化和反序列化技术,也有很多工具可以采用,如国内的 Fastjson , 国外的Jackson 、 Gson . Jackson 是 Spring Boot 内 置的,也是 Spring Boot 相关很多开源产品内置的序列化工具,与Beetl _数据库的访问
在envi中有时我们需要导出波段合成后的三个波段的数据,而有时为了转换数据格式需要将数据的所有波段全部导出。这里我们分别讲一下如何进行两种数据的导出。一、导出三波段的数据(1)在envi中打开要导出的数据,并进行数据的波段组合。(2)鼠标在数据右击,选择Export Layer to tiff,这样导出的数据就是仅有选择好的三个波段的数据了。二、导出多波段数据 (1)打开影像后,选择file>save as>save as...(ENVI..._用envi提取3个波段
近日,DB-Engines发布了2017年7月数据库排名。数据库排行又是一轮洗牌后,本轮数据库排名在2017年下半年首月迈入新格局。首先是接连遭到比分威胁的Oracle终于在7月迎来局势反转,得分反弹数值高达23.11,与次席MySQL的分差再度拉大至25分开外,霸主地位之稳固可见一斑。同时,保持较长一段时间稳定的前十名排行本月亦出现波动:搜索引擎式数据库Elasticsearch成功跻身前十之列...