728x90
SOLID
S: Single Responsibility
- 한 컴포넌트 안에 useState가 두 개 이상 있다면 리팩토링을 고려하자
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();
}, []);
...
}
# hooks/useProducts.tsx
export const useProducts = () => {
const [products, setProducts] = useState<any[]>([]);
const fetchProducts = async () => {
const response = await axios.get(
"https://fakestoreapi.com/products"
);
if (response && response.data) setProducts(response.data);
};
useEffect(() => {
fetchProducts();
}, []);
return { products };
};
# hooks/useRateFilter.tsx
export function useRateFilter() {
const [filterRate, setFilterRate] = useState(1);
const handleRating = (rate: number) => {
setFilterRate(rate);
};
return { filterRate, handleRating };
}
- 한 컴포넌트 내 두개 이상의 map 함수를 랜더링 하고 있다면 리팩토링을 고려하자.
export function Bad() {
return (
<div className="h-full flex flex-wrap justify-center">
{filteredProducts.map((product: any) => (
...
div/>
...
<div className="flex items-center mt-2.5 mb-5 flex-1">
{Array(parseInt(product.rating.rate))
.fill("")
.map((_, idx) => (
<svg
...
</div>
...
)
}
# product.tsx
export function Product(props: IProductProps) {...}
# filter.tsx
export function Filter(props: IFilterProps) {...}
# good.tsx
export function Good() {
<div className="flex flex-col h-full">
<Filter
filterRate={filterRate as number}
handleRating={handleRating}
/>
<div className="h-full flex flex-wrap justify-center">
{filterProducts(products, filterRate).map((product: any) => (
<Product product={product} />
))}
</div>
</div>
}
O: Open-Closed
- Software Entities(Classes, Modules, Functions)은 확장에 열려있고 수정에 닫혀있게 구성해야 한다. 다시 말해, 더 많은 기능을 추가하기 위해 소스 코드를 수정해선 안된다.
interface IButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
role?: "back" | "forward" | "main" | "not-found";
icon?: React.ReactNode;
}
export function Button(props: IButtonProps) {
const { text, role, icon } = props;
return (
...
<div className="m-2">
/* BAD */
{/* {role === "forward" && <HiOutlineArrowNarrowRight />}
{role === "back" && <HiOutlineArrowNarrowLeft />} */}
/* GOOD */
{icon}
</div>
...
);
}
export function OCP() {
return (
<div className="flex space-x-10">
<Button
text="Go Home"
// role="forward"
icon={<HiOutlineArrowNarrowRight />}
/>
<Button
text="Go Back"
// role="back"
icon={<HiOutlineArrowNarrowLeft />}
/>
</div>
);
}
# BAD
const TextInput = ({
type,
name,
value,
textareaRows,
isShortInput,
isLoading,
className,
containerClassName,
formGroupClassName,
placeholder,
id,
onChange,
onInput,
isTouched,
onClearClick,
addonLeft,
addonRight,
helpText,
labelStyle,
onInvalid,
onBlur,
maxLength,
hasClearButton,
label,
addonLeftClassName,
addonRightClassName,
inputRef
}) => {
return <div><input />...</div>;
};
# GOOD
<TextInputContainer ...props>
<TextInputLabel ...props />
<TextInputLeftAddon ...props />
<TextInputControl ...props />
<TextInputRightAddon ...props />
</TextInputContainer>
L: Liskov Subsititution
- 하위 유형 개체는 상위 유형 개체를 대체할 수 있어야 한다.
# LSP.tsx
export function LSP() {
const [value, setValue] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
return (
<SearchInput value={value} onChange={handleChange} isLarge />
);
}
# SearchInput.tsx
interface ISearchInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
isLarge?: boolean;
}
export function SearchInput(props: ISearchInputProps) {
const { value, onChange, isLarge, ...restProps } = props;
return (
<div className="flex w-10/12">
...
<input
type="search"
required
value={value}
onChange={onChange}
{...restProps}
/>
</div>
</div>
);
}
I: Interface Segregation
- 클라이언트가 사용하지 않는 인터페이스에 의존하지 않게 분리해야 한다.
# BAD
<Thumbnail product={product} />
# GOOD
<Thumbnail imageUrl={product.image} />
D: Dependency Inversion
- 낮은 결합도와 테스트 용이성을 위해 추상화에 의존해야지, 구체화에 의존하면 안된다
예를 들어, Create Form vs Edit Form 두 가지 영역에서 Form을 공유하고 있다고 하자.
하지만 두 개는 다른 로직으로 Submit이 처리되야 한다. 그래서 Form을 분리하고 Submission Logic만 분리하는 방법이다.
import { Form } from "./form";
export function ConnectedForm() {
const handleSubmit = async (email: string, password: string) => {
await axios.post("https://localhost:3000/login", {
email,
password,
});
};
return <Form onSubmit={handleSubmit} />;
}
참조
https://blog.bitsrc.io/open-closed-principle-for-react-js-applications-583ac7f887a7
https://www.youtube.com/watch?v=MSq_DCRxOxw&t=226s&ab_channel=CoderOne
'software engineering > frontend' 카테고리의 다른 글
웹 접근성(a11y)과 storybook addon (0) | 2023.01.06 |
---|---|
Goodbye, useEffect - David Khourshid (0) | 2022.12.05 |
SSR 그리고 사용자 경험 (0) | 2022.11.25 |
Migrate to Next.js 13 on Turborepo (0) | 2022.10.28 |
FE CONF 2021 Track A (0) | 2022.10.09 |