SOLID 원칙으로 리액트 훅 작성하기(Korean FE Articale)
한줄 소감
(번역) SOLID 원칙으로 리액트 훅 작성하기
원문 : Write SOLID React Hooks SOLID는 가장 일반적으로 사용되는 디자인 패턴 중 하나입니다. 여러 언어와 프레임워크에서 사용되며, 리액트에서 사용하는 방법을 소개하는 문서도 많습니다. SOLID에
ykss.netlify.app
디자인 패턴을 아는 것과 체화해서 사용하는 것은 별개의 일이다. 양질의 컴퍼넌트를 사용하려고 여러 패턴을 공부하고 도입하였지만, 종종 룰을 어긋날 때도 많고 불안정한 모습도 많다. 위의 아티클을 읽으면서 리액트 훅에서 SOLID원칙을 사용할 때 어떤 실수를 많이 하고 대안은 어떠한 지에 대해서 학습할 수 있어서 좋았다.
내용
SOLID 원칙에 대해서 들어본 적은 많고, 내용에 대해서 숙지한 적도 있다. 하지만 막상 내 코드들이 이 원칙을 지키고 있는지에 대해서는 부정적이다.
SOLID는 각각 아래와 같다.
S : 단일 책임 원칙(SRP - Single Responsibility Principle)
O : 개방/폐쇄 원칙(OCP - Open/Closed Principle)
L : 리스코프 치환 원칙 (LSP - Liskov Substitution Principle)
I : 인터페이스 분리 원칙 (ISP - Interface Segregation Principle)
D : 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
1. 단일 책임 원칙(SRP - Single Responsibility Principle)
관심사 분리등 학습을 많이 하였고 데이터, 계산 , 액션에 따라서 관심사를 나누려고 하는 노력은 많이 하지만, 그럼에도 불구하고 내가 만든 훅이나 컴퍼넌트가 단일 책임원칙을 지키고 있을까? 에 대한 의문이 든다.. ( 내가 제대로 했는지?)
이때 해당 아티클에서는 아래와 같은 3가지 가이드라인을 확인해볼 것을 추천해주었다. 이를 지키지 않으면 단일 책임 원칙에 위배된 것이다.
- UI(프레젠테이션)를 표시해야 하는 컴포넌트인가요? 아니면 데이터(로직)를 처리해야 하는 컴포넌트인가요?
- 이 훅은 어떤 단일 유형의 데이터를 처리해야 하나요?
- 이 훅이나 컴포넌트는 어떤 레이어에 속하나요? 데이터 저장소를 처리하는 건가요? 아니면 UI의 일부일까요?
2. 개방/폐쇄 원칙(OCP - Open/Closed Principle)
최근 프로젝트를 만들면서 얼마 만큼의 확장성을 고려해야하는지 의문이다. 아래 코드는 원문에서 제시해준 OCP에 위배된 코드이다.
import { useState } from 'react'
import { getUser, updateUser } from 'somewhere'
const useUser = ({ userType }) => {
const [user, setUser] = useState()
useEffect(() => {
const userInfo = getUser()
setUser(userInfo)
}, [])
const updateEmail = (newEmail) => {
if (user && userType === 'admin') {
updateUser({ ...user, email: newEmail })
} else {
console.error('Cannot update email')
}
}
return { user, updateEmail }
}
admin인 경우에 따라 결과가 달라지고,, 만약 또 다른 유형의 유저가 추가되면 테스트코드까지 수정될 수 있기에 아래와 같이 useUser훅을 확장한 새로운 훅을 생성하였다.
import { useState } from 'react';
import { getUser, updateUser } from 'somewhere';
// useUser 훅은 이제 이메일 업데이트 기능 없이
// 사용자만 반환합니다.
const useUser = () => {
const [user, setUser] = useState();
useEffect(() => {
const userInfo = getUser();
setUser(userInfo);
}, []);
return { user };
};
// 새로운 훅인 useAdmin은 useUser 훅을 확장하며,
// 이메일을 업데이트하는 추가 기능을 제공합니다.
const useAdmin = () => {
const { user } = useUser();
const updateEmail = newEmail => {
if (user) {
updateUser({ ...user, email: newEmail });
} else {
console.error('Cannot update email');
}
};
return { user, updateEmail };
};
그 동안 HOC 패턴, Compound패턴을 사용하는 등, 확장을 용이하게 하려는 노력을 했다 생각했는데 정작 내가 만든 기능들 중에 OCP를 지키지 않은 것들이 꽤 있겠다고 느껴지는 부분이었다..
개인적으로 진행하는 프로젝트들은 확장이 어느 정도 제한되어있기 때문에, 얼마만큼의 확장성을 고려해야 하나 이런게 고민되고 이 정도면 되겠지라고 생각하고 넘어가는 부분들이 종종있었는데, 애초에 '원칙을 지키지 않기 떄문에 이런 고민이 더 크게 생기지 않았나?' 의문이 든다.
3. 리스코프 치환 원칙 (LSP - Liskov Substitution Principle)
아직 코딩을 하는 과정에서 확장을 많이 한 경험은 없다.. 타입스크립트에서 extends로 인터페이스 확장한 정도,,
다만 특정 훅을 확장할 때, 유의해야겠다는 느낌 정도 받았다..
4. 인터페이스 분리 원칙 (ISP - Interface Segregation Principle)
정의 : 사용하지 않는 메서드에 의존하도록 코드를 강제해서는 안 됩니다.
타입스크립트를 사용하다 보면 특히 많이 겪게 되는 고민이 담겨 있었다. 인터페이스를 어떻게 설계를 할까..?
옵셔널을 좀 쉽게 박았던 경향이 있었던 거 같은데 관련 내용을 확인하여 분리했어야 했던거 아닐까? 에 대한 고민을 해봐야할 거 같다.
5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
정의 : 고수준 모듈은 저수준 모듈로부터 아무것도 가져오지 말아야 하며, 둘 다 추상화에 의존해야 합니다.
이전에 SOLID에 대해서 공부할 때도 가장 어렵게 느껴졌던 부분이었다.
프레임워크를 공부하다보면 듣게 되는 제어의 역전.. 조금은 추상적으로 받아들이고 있었던 것 같다.
제어의 역전(Inversion of Control, IoC)
제어의 역전은 의존성 역전 문제를 해결하기 위해 사용되는 원칙입니다. 이 원칙은 모듈의 의존성을 외부 엔티티나 프레임워크에서 제공해야 한다고 명시합니다. 이렇게 하면 모듈 자체는 의존성을 단순히 사용하기만 하면 되고, 의존성을 생성하거나 관리할 필요가 없습니다.
출처 : 아티클
저수준 모듈에서 아무것도 가져오지 않지만 , 모듈의 의존성을 외부 엔티티나 프레임워크에서 제공..
이러한 방법중에 가장 흔한 예시로 context와 HOC패턴을 예로 듭니다.
느낀점
좀 더 이해한 느낌은 받았지만, 아직 사용에는 자신이 없는 상태입니다. 현재 진행중인 프로젝트가 곧 끝나는데, 리팩토링 기간에 이를 참고해서 고쳐봐야겠습니다..
그리고 '테스트코드는 좋은 설계를 돕는다' 라는 말을 '인프런 조커님의 테스트코드 강의'에서 봤는데,여기서도 SOLID의 장점 중 하나가 테스트하기 쉽다라고 종종 설명하네요.. 좋은 설계와 테스트코드는 연관 될 수 밖에 없는데, 아직 테스트코드에 익숙하지 않은게 제 단점인 거 같기도 하네요.. 마찬가지로 추후 리팩토링에 테스트코드도 적용하려고 노력해봐야겠어요.