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
    • 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>
  • 비즈니스
    1. Domain Logic
      1. 해당 도메인에 강하게 묶여있어서 분리가 불가능한 로직
      2. 흔하게 대부분의 경우 도메인 로직
      3. 보통 React의 경우 한 컴포넌트 내에 같이 존재하는 케이스가 많음
    2. Common Logic
      1. 여러 도메인에서 공통으로 쓰이는 로직
      2. 주로 유효성 검사 (휴대폰 번호 검사), 에러 처리
      3. 보통 Utils로 빼서 처리함

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)
  • 코드 복잡도 결정
    1. 의도한 대로 동작하는가?
      1. 추상화되어있는 코드의 이름만 봐도 이 코드가 어떤 동작을 할 지 기대할 수 있는가?
      2. 추상화되어있는 함수 또는 메서드의 파라미터가 추상화 단계와 어느 정도 일치하는가?
    2. 어떤 코드의 동작이 어떤 객체 또는 클래스와 결합되어있는가?
      1. 이 동작이 어느 객체에서만 동작하거나, 어느 클래스에서만 동작하는 경우에는 유틸로 분리했을 때 오히려 혼란을 야기한다.
      2. 이 동작이 지나치게 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);
	}
}
  1. 함수 이름만 봤을 때 어떤 액션/행동을 취했는 지 알 수 없다.
  2. 함수 내부에서 무슨 일이 일어나고 있는 지 한눈에 보이나요?
    1. 함수 내부의 흐름도가 명확하게 보일 수록 좋은 코드이다.
  • 복잡도를 만들어내는 일은 많다. 그러나 복잡도를 만들어내는 것이 반드시 위 항목들로만 구성되는 것도 아니고, 위 항목들이 포함되어 있다고 해서 반드시 복잡도가 높다고 보기에는 어렵다.
  • 한 번 짜놓은 코드를 직간접적으로 건드릴 일이 많이 줄어든다. 어지간하면
    • postData가 300줄 정도 되는 코드이다.
      • 복잡도가 높은가? 리팩토링 타겟? → postData가 복잡한가 아닌가를 판단하는 기준
        1. postData 자체를 바꾸려고 할 때, postData 내부 변경에 따라서 외부에도 영향이 가는 경우
        2. 내부 흐름도를 내가 알아야 하는 경우 (→ 이 코드의 복잡도라는 것을 인지)
        3. 그렇지 않은 경우 복잡도는 내 알 바 아님
// 예시
addEventListener('click', () => {}, false); // 1000줄 넘어도 하나도 신경 안써도 된다.
  • 복잡도에 영향을 주는 것 → 추상화가 얼마나 잘 되어있는가
  • 특정 서비스 제작 → 보편적으로 쓰이는 패턴 몇 가지를 고려함 → 디자인 패턴
  • 웹 서비스의 전체 구조
    1. 모놀리스
    2. MSA
  • 모놀리식 구조
    1. FE ↔ BE
    2. 하나로 퉁친다 (BE + Template Engine + UI Lib)
  • FE ↔ BE
    1. frontend / backend 저장소 분리
    2. 저장소는 하나인데 폴더만 분리

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을 사용하여 구축하였고, 리액트에서 비즈니스 로직은 커스텀 훅으로 빼고자 노력하였으며…
⚠️ **GitHub.com Fallback** ⚠️