# 시작하기 앞서

우선, react 함수형 컴포넌트 기준으로 설명합니다.

react, vue 같은 ui 'library'의 역할이 무엇인지 생각해봐야합니다.

왜 이들이 라이브러리라고 불리는지, 무엇을 하는지 정확하게 알고 넘어가야합니다.

ui library는 ui 데이터(상태, 속성)가 변경되면 ui를 갱신해줍니다.

직접 DOM에 접근하여 ui를 갱신하는 것은 상당히 피곤한 일 입니다. 크로스브라우징 이슈를 신경써야하며, 비즈니스 로직과 ui를 갱신하는 로직이 섞여서 유지보수가 힘들어집니다.

위에서도 언급했던 것처럼 브라우저 API마다 다른 동작을 할 수 있는 가능성을 사전에 차단합니다. => 크로스 브라우징

라이브러리의 지원범위내의 브라우저라면, 브라우저에는 개발자가 의도하는대로 렌더링됩니다.

라이브러리에 버그가 있다면 라이브러리측에서 수정해주길 기다리거나, 직접 수정해서 사용하면 됩니다. => 제어역전

ui library 자체는 결국 단순 '렌더링'만 하기때문에 router 라던지 상태관리 등 다른 기능은 써드파티 라이브러리를 사용해야합니다.

# 컴포넌트

이해하기쉽게 'html 엘리먼트의 집합체' 혹은 블럭이라고 표현할 수 있겠습니다.

마치 레고 블럭처럼 컴포넌트들을 조합하여 앱을 개발합니다.

vue에서는 vue 객체, react에서는 일반적으로 React Element tree를 리턴하는 자바스크립트 함수입니다.

아무것도 렌더링하지않으려면 null을 리턴해야합니다. (react에서는 컴포넌트가 undefined를 리턴하면 에러입니다.)

요약하면 리액트 컴포넌트는 react element tree or null을 반환하고 함수 이름이 PascalCase인 자바스크립트 함수입니다.

리액트 공식문서에서는 아래와 같이 설명하고 있습니다.

개념적으로 컴포넌트는 JavaScript 함수와 유사합니다.
“props”라고 하는 임의의 입력을 받은 후,
화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.

# 컴포넌트 설계

js숙련도가 있다면 컴포넌트를 잘 설계하는게 FE개발 실력의 핵심이라고 할 수 있습니다.

같은 기능을 가진 컴포넌트라도 설계는 사람마다 달라질 수 있죠.

그래서 아래에서 설명하는 컴포넌트 설계방법은 꽤 주관적이며 관점에 따라 다를 수 있습니다. 어찌보면 당연한 내용도 포함되어있고요.

좋은 컴포넌트 개발의 핵심은 재활용 가능한 컴포넌트입니다.

컴포넌트는 기본적으로 StateSetter(StateUpdater)가 호출되면 리렌더됩니다.

성능관점에서 state와 props는 최대한 변경되지 않을수록 유리합니다.

props만 받아서 렌더만하는 컴포넌트는(프레젠테이션 컴포넌트) storybook을 활용하기 좋으며 쉽게 테스트하고 재활용 할 수 있습니다.

모델렌더를 실현하기도 쉬워지고요. 모델렌더는 아래에서 다시 설명합니다.

최근 react에서는 함수형 컴포넌트를 작성하면서 hook API을 사용하게되는데, custom hook을 적극 활용하는게 좋습니다.

마치 클래스 컴포넌트를 사용할때, 프레젠테이션과 컨테이너를 분리했던 것처럼요. 

프레젠테이션 컴포넌트 + 모델(hook)을 조합하면 이상적인 컴포넌트 구현입니다.

컴포넌트에서 로직을 깔끔하게 분리해낼 수 있으며 로직의 재활용성과 가독성을 향상시킵니다. (관심사 분리)

훅만 테스트하는건 거의 로직만 테스트하는거라, 테스트코드 작성도 상대적으로 매우 간편해집니다.

상태와 로직, 템플릿이 마구 섞여져있을 수 있는데,

상태를 관리하는 hook - props, state, ref, useMemo를 사용한 computed variable 등..

로직을 관리하는 hook - useCallback을 이용한 메모이제이션된 함수들

hook에 대한 자세한 내용은 여기를 참고하세요

# 모델렌더 model render

뷰가 상태를 갖는것이 아닌, 순수 js 객체가 상태를 가져야합니다.

같은 데이터라면 같은 렌더링이 되어야한다는 것입니다.

뷰를 직접 제어하지 않으므로 버그를 발생시키지않고 단순히 모델을 갱신해서 렌더를 발생시켜야합니다.

= DOM을 직접 조작하지말고 setState를 사용해라.

그래야 테스트코드도 작성하기 쉬워집니다.

# props

극단적으로 표현하면...

state만 있더라도 컴포넌트를 개발할 수 있습니다.

props의 존재 이유는 사실 컴포넌트 재활용 말고는 없다고 봐도 됩니다.

무분별하게 컴포넌트를 나누기보단, 컴포넌트가 재활용될 가능성을 생각해보고 컴포넌트를 나누는게 좋습니다.

기본적으로 함수형 리액트 컴포넌트에서는 상위 컴포넌트를 리렌더할때 하위 컴포넌트들을 다시 호출하기 때문이죠.

물론 재활용을 안하더라도, 가독성이나 storybook등 다른 이유로 컴포넌트를 나눌수는 있겠지만, 본질에 대해 다시 생각해볼 필요는 있습니다.

그렇다고 극단적으로 성능관점에서 설계를 하라는 말은 아닙니다. 역할을 분리하는게 더 중요하죠.

# 템플릿

템플릿 - 함수형 컴포넌트에서 리턴하는 React Element tree

템플릿에는 expression을 최소화합니다. - useMemo나 useCallback을 사용하여 변수나 함수만 바인딩하도록 합니다.

템플릿에 로직을 직접 작성하지마세요.

static markup에 해당하는 부분들은 가독성을 위해 기능별로 컴포넌트로 분리합니다.

자주 렌더를 수행하는 구간에 필요하다면 React.memo를 활용해도 좋고요.

# 안티패턴

# 템플릿내 로직

위에서도 언급했지만, 템플릿엔 로직을 작성하지마세요.

테스트를 어렵게 만들고 가독성을 해칩니다.

1component 1hook 을 달성하려면 템플릿내 로직이 없어야합니다.

# 렌더링 간섭

위에서 컴포넌트는 state, props가 변경되면 리렌더되어야한다고 했습니다.

반대로 state, props외에 다른 요소들이 렌더에 영향을 주게되면 안티패턴이라고 할 수 있습니다.

대표적으로 DOM조작을 직접(ref를 사용하여 element를 조작하는것 또한 마찬가지입니다.)하거나, key를 사용해서 리렌더하는 방식입니다.

ref를 사용하는 자체가 나쁜게 아니라 react 역할인 DOM조작을 개발자가 직접 할필요가 없고, 방해해서는 안됩니다.

# 가독성?

# 조건부 렌더링

템플릿에서 조건부 렌더링을 구현할때 가능하면 if 구문을 쓰지말고 삼항연산자나 && 연산자를 사용하는게 좋다고 생각합니다.

잠깐 &&을 짚고 넘어가면, falsy한 값이 있다면 그것을 먼저 반환한다. 그 뒤의 expression은 무시되며 연산하지도 않는다.

연산중에 truthy값이 있다면 그것을 반환한다.

그러므로 조건에 falsy한 값이 있는지 잘 확인해야한다.

예를 들면, 어떤 회원의 포인트를 렌더링한다고 하자. 포인트는 number형이다.

<div>{point && <span>회원님의 포인트는 {point}점 입니다.</span>}</div>

회원의 포인트가 0일때 단순히 0을 렌더링할것이고, '회원님의 ~' 부분은 렌더링되지 않을 것이다.

의도하는게 아니라면, 조건을 boolean형으로 적절히 변형하자.

이런 코드에서 문제점을 한눈에 발견하기 어려울 수도 있다.

조건이 복잡하다면 별도 변수로 추출하여(useMemo) 보기좋게 해주면 된다.

삼항연산자는 2개 이상 중첩하여 사용하면 상대적으로 가독성이 떨어지는 것 같다.

그리고 조건은 boolean형으로 명시적으로 변환해주는게 좋은데,

이유는.. 렌더링 테스트를 작성한다고 했을때, 아래와 같은 간단한 컴포넌트가 있다고 가정하면..

const App = () => {
  const [input, setInput] = useState<string>('');

  const onChangeInput = e => setInput(e.target.value);

  return (
    <div className="wrapper">
      <div className="title">App</div>
      <div className="control">
        <input value={myInput} onChange={onChangeInput}/>
      </div>
      {myInput && <div className="result">{myInput}</div>}
    </div>
  );
}

초기렌더링은, wrapper 내부에, title, control, input 필드 이렇게 나타난다.

하지만.. 조건을 보면 myInput이 string이다.

그러면 빈문자열이 당연히 렌더되는것이라고 생각하고, 테스트 프레임워크에서는 output을

wrapper : ['<div className="title">App</div>', '<div className="control"><input value={myInput} onChange={onChangeInput}/></div>', '']

이런식으로 나타나게된다. (예시임)

그러면 wrapper는 2개의 element가 있어야하는데, 보이지 않지만 빈 문자열이 렌더링됐으므로 마치 3개가 있는것처럼 보인다.

에러가 아닌데 에러처럼 보인다.

위에서 언급한것처럼 0을 렌더링한다던지.. 이런 문제를 해결하기위해

myInput => !!myInput 이런식으로 변경하도록 하자.


ref: 머릿속에 있던 내용과 실전 리액트 프로그래밍이라는 책

그리고 https://www.bsidesoft.com/8267 의 저자(선생님)의 가르침