11월 21일 마스터 클래스 (조은) - boostcampwm-2022/web33-Mildo GitHub Wiki
-
프론트엔드 아키텍쳐 → ‘데이터 - 서비스 - 모델 - 컴포넌트’의 분리
-
디자인 패턴 (디자인, 디자인, 디자인, …)
-
Design(설계) → Design as a Programmer
-
예쁜 무언가를 만드는 행위가 아니라, 무언가를 설계하는 행위를 ‘디자인’이라고 한다.
-
UI, GUI, UX, Visual Graphics, Visual Design, Motion Graphics, Motion Design, …
-
엔지니어에게 디자인이란 우리가 원하는 제품을 만들기 위한 설계이자, 우리가 더 잘 일하기 위한 협약
-
프론트엔드 웹서비스의 협의 → 도메인이랑 UI 어떻게 분리하지? UI 컴포넌트를 어떻게 만들까? API로 만들 영역이랑 그냥 바로 DB를 찌르는 것을 어떻게 구분할까? 도메인을 어떻게 쪼갤까? → DDD? 테스트는 어떻게 짤까?
-
예시:
-
유저스토리: 비즈니스 로직이랑 UI를 어떻게 분리해야 할까? → 버튼을 클릭했을 때 Modal이 노출되어야 한다. → Modal은 특정 데이터를 입력받은 뒤, 확인 버튼을 누르면 닫힌다 → 확인 버튼은 데이터를 서버로 전송하여 DB에 저장한다.
-
Button과 Modal은 둘 다 UI이다
- Button(UI)
- clickable
- disabled
- Modal(UI)
- open / close
- close button
- Button(UI)
-
export const Button = ({onclick, children}) ⇒ { // do something… }
-
CASE 1: const Button = ({onclick}) ⇒ {
return <button onclick={onclick}>Button</button>
}
-
CASE 2: export const ModalButton = () ⇒ {
const onClick = () ⇒ { // do something } return <button onclick={onclick}>Button</button>
}
-
-
어떤 코드를 작성해야 하는 경우
- 확장성 vs 폐쇄성, 로직을 어디서 관리할 지, 추상화 단계 (내가 얼마나 이 로직과 컴포넌트를 공통으로 가져갈 건지, 내가 굳이 을 만들고, 를 만드는 이유를 본인들이 찾아야 한다.
- 컴포넌트가 많아질수록, 로직은 분산된다. 컴포넌트의 개수와 로직의 복잡도는 비례 관계일 수도 있다. 만약에 Modal, Button, Title, Text에 대한 컴포넌트가 각각 있다면 파악하기 힘들지만(추상화를 해서 뭐하는 지 정확히 모름), 그렇지 않다면 파악하기 쉽다.
-
어떻게 추상화를 해야 할까?
- 추상화된 개체는 내부를 몰라도 개발자가 쓸 수 있어야 한다.
- 추상화한다는 자체가 ‘내부를 몰라도 된다’를 베이스로 깔고 감. → 추상화를 할 때도 외부에 공개된 인터페이스와 내부에 은닉할 로직들을 잘 생각해서 추상화해야 한다.
-
Business Logic
- 비즈니스 로직이 뭐지?
- 데이터를 입력받는다 → 입력된 데이터를 검증한다 (유효성 체크) → 입력된 데이터를 서버에 전송한다. → 입력된 데이터가 올바르지 않은 경우에는 에러 또는 경고를 노출한다.
const Form = () => {
const { closeModal } = useModal();
const [name, setName] = useState('');
const [error, setError] = useState(false);
const onNameInputChange = (e) => {
// 입력값 검증을 실시간으로 할 지, 전송할 때만 할 지
const pattern = /[a-zA-Z]/g; //util로 뺄 수 없을까?
if(e.target.value.match(pattern) === null) {
// 비정상
}
// 입력 관련 로직 처리
}
const onSubmit = async (e) => {
e.preventDefault();
try {
// submit (서버에 데이터를 전송)
await axios.post('/api/send', () => {
data: { name }
});
closeModal();
} catch (error) {
// do something with error (에러가 났을 때의 처리)
if (error.statusCode === 404) {
setError(404);
}
}
}
return (
<form onSubmit={onSubmit}>
<label>이름</label>
<input />
</form>
)
}
const [isModalShow, setIsModalShow] = useState(false);
<ModalContext Provider={}>
<Modal> // Modal 관련 UI 로직
<ModalBody>
<Form /> // 입력 관련 비즈니스 로직 처리
</ModalBody>
</Modal>
- 비즈니스
- Domain Logic
- 해당 도메인에 강하게 묶여있어서 분리가 불가능한 로직
- 흔하게 대부분의 경우 도메인 로직
- 보통 React의 경우 한 컴포넌트 내에 같이 존재하는 케이스가 많음
- Common Logic
- 여러 도메인에서 공통으로 쓰이는 로직
- 주로 유효성 검사 (휴대폰 번호 검사), 에러 처리
- 보통 Utils로 빼서 처리함
- Domain Logic
const ENGLISH_ONLY_REGEXP = /[a-zA-Z]/g;
export const validateEnglishOnly = (text) ⇒ {
return text.match(ENGLISH_ONLY_REGEXP) === null,
}
- Q. 어떤 것을 유틸로 빼고, 어떤 것을 도메인으로 유지할까요?
- 2번 이상 쓰면, 컴포넌트의 주 목적과 관련 없으면, 기능을 돕느라 쓰이는, 비즈니스 로직이랑 결합도가 낮다면, 상태에 영향을 받지 않으면, 도메인이 애매하면, …
- 도메인 로직의 일부를 유틸로 분리하는 이유: 재사용성, 함수가 너무 긴 건 생각보다 중요하지 않음, 추상화 → 이거 정말 유틸로 빼야될 정도로 자주 쓰여?
- 유틸로 뺀다 === 관리해야 할 포인트가 많아진다.
const A = () => {
getSomething = () => {
}
onClick = () => {
getSomething();
}
return (
<a>Hello</a>
)
}
- 코드의 복잡도가 높아진다에 대한 오헤
- 코드가 복잡하다??
// 누가 봐도 1, 2를 더하겠구나
const result = sum(10, 20);
const result = sum(10, sum(10, sum(20, 30)));
// 이게 뭔 소리야?
const result = sum(10, true);
// 대충 1, 2를 더할 거 같은데... 옵션이 있나?
const result = sum(10, 20, true);
// click 이벤트를 준다, 콜백 함수, 버블링 캡쳐링 결정
addEventListener('click', () => {}, false)
// 대충 x가 10인 것 같고, y가 30?
move(10, 30);
// -> 이 코드의 문제점: 뭐가 움직이는 건데? move가 애초에 움직이는 건 맞아? move가 너무 광범위해
// 지도의 특정한 위치로 이동하겠구나
Map.move(10, 30)
// 지도의 특정 좌표로 이동할 것이다
Map.moveToAxis(10, 30)
- 코드 복잡도 결정
- 의도한 대로 동작하는가?
- 추상화되어있는 코드의 이름만 봐도 이 코드가 어떤 동작을 할 지 기대할 수 있는가?
- 추상화되어있는 함수 또는 메서드의 파라미터가 추상화 단계와 어느 정도 일치하는가?
- 어떤 코드의 동작이 어떤 객체 또는 클래스와 결합되어있는가?
- 이 동작이 어느 객체에서만 동작하거나, 어느 클래스에서만 동작하는 경우에는 유틸로 분리했을 때 오히려 혼란을 야기한다.
- 이 동작이 지나치게 General한 경우 하나의 용어로 오히려 더 많은 혼란을 가중한다.
- 의도한 대로 동작하는가?
const postData = async () => {
const response = await axios.post();
const data = await response.data;
}
const handleError = (error) => {};
const onClick = async () => {
try {
// do something
const data = await postData();
} catch (error) {
handleError(error);
}
}
- 함수 이름만 봤을 때 어떤 액션/행동을 취했는 지 알 수 없다.
- 함수 내부에서 무슨 일이 일어나고 있는 지 한눈에 보이나요?
- 함수 내부의 흐름도가 명확하게 보일 수록 좋은 코드이다.
- 복잡도를 만들어내는 일은 많다. 그러나 복잡도를 만들어내는 것이 반드시 위 항목들로만 구성되는 것도 아니고, 위 항목들이 포함되어 있다고 해서 반드시 복잡도가 높다고 보기에는 어렵다.
- 한 번 짜놓은 코드를 직간접적으로 건드릴 일이 많이 줄어든다. 어지간하면
- postData가 300줄 정도 되는 코드이다.
- 복잡도가 높은가? 리팩토링 타겟? → postData가 복잡한가 아닌가를 판단하는 기준
- postData 자체를 바꾸려고 할 때, postData 내부 변경에 따라서 외부에도 영향이 가는 경우
- 내부 흐름도를 내가 알아야 하는 경우 (→ 이 코드의 복잡도라는 것을 인지)
- 그렇지 않은 경우 복잡도는 내 알 바 아님
- 복잡도가 높은가? 리팩토링 타겟? → postData가 복잡한가 아닌가를 판단하는 기준
- postData가 300줄 정도 되는 코드이다.
// 예시
addEventListener('click', () => {}, false); // 1000줄 넘어도 하나도 신경 안써도 된다.
- 복잡도에 영향을 주는 것 → 추상화가 얼마나 잘 되어있는가
- 특정 서비스 제작 → 보편적으로 쓰이는 패턴 몇 가지를 고려함 → 디자인 패턴
- 웹 서비스의 전체 구조
- 모놀리스
- MSA
- 모놀리식 구조
- FE ↔ BE
- 하나로 퉁친다 (BE + Template Engine + UI Lib)
- FE ↔ BE
- frontend / backend 저장소 분리
- 저장소는 하나인데 폴더만 분리
FE
- 어떤 컴포넌트
- 어떤 페이지? → 컴포지션 레이어
- 라우팅 어떻게 관리할까?
- 비즈니스 로직 → 서비스로 분리 → 서비스 클래스 만들어서 해당 클래스를 이용해서 로직 관리
- Redux로 분리 (Reducer를 비즈니스 로직 관리용도로 사용)
- Custom Hooks
- 유틸? 스타일 관리?
components
domain
Product
ProductCard
ProductCard.tsx
ProductCard.css
ProductList
ProductList.tsx
ProductList.scss
common
Button
A
pages
Product
ProductPage.tsx
router
router.tsx
index.js
app.tsx
- 프론트엔드의 고민
- 어떤 로직을 BE에서 관리하고, 어떤 로직을 FE에서 관리할 것인가?
- 컴포넌트를 어떻게 분리할 것인가? → API로 내려주는 데이터가 어떤 형태인가 (=== 내가 어떤 컴포넌트에서 어떤 API를 찔러서 데이터를 주고받을 것인가 / 개별 컴포넌트가 가지고 있어야 하는 상태가 무엇인가?)
- 스타일 관리를 어떻게 할 것인가?
- 아마 다들 Styled-Components, Emotion, 등등..
- 어떤 액션에 의해서 UI가 바뀌어야 하는 경우, 그 UI가 다른 UI에 영향을 미치는 경우
- 이런 케이스들을 전부 고려하여 Style을 관리
- 레이아웃, 애니메이션, 트랜지션, 트랜스폼 등등을 별도로 관리하면 좋음
- 레이아웃에 영향이 없는 것들은 개별적으로 관리하는 것이 더 나을 수도 있다. → 레이아웃과 그렇지 않은 부분들을 분리하자
const ProductList = () => {
const {data} = useSWR(",fetcher);
return (
<div>
<ProductThumb src={data.photo.src} alt={data.photo.alt} />
</div>
)
}
const ProductThumb = ({src, alt}) => {
const [size, setSize] = useState();
return (
<img src={src} alt={ddd} lazyload="" />
)
}
- 백엔드 개발자랑 많이 협의하자 → API의 형태, 배포 주기, 누가 먼저 배포를 해야 할까?
- 매니저 → 너가 만약 API가 필요하면 백엔드 개발자에게 Hey Give me API pls 해.. 그러면 그들이 줄거야. 만약 안준다면 많은 협의를 거치렴…
- 백엔드에서 가능한 영역이 있고, 프론트엔드에서 가능한 영역이 있다. 이것 자체는 True
- 그럼에도 협의에 따라서 API의 형태, 응답을 어떻게 보낼지 등의 협의 가 가능하다.
- 이 협의가 잘 안된다 === 죽음뿐이다…
- 일단 프로젝트는 README.md를 꾸미자
- 예시) Geolocation API를 활용하여 위치를 가져온 뒤, Naver Maps API를 통해 주변 음식점의 정보를 제공해줍니다.
- 예시) 모임을 생성하여 링크를 공유하는 식으로 동작하기 때문에, 개별 모임당 신규 라우팅을 생성하고, 라우팅은 모임이 끝날때까지 유지하는 것으로 설계
- React, React Router DOM을 사용하여 구축하였고, 리액트에서 비즈니스 로직은 커스텀 훅으로 빼고자 노력하였으며…