# 컴포넌트 Props 디자인

{% hint style="warning" %}
**주의!**&#x20;

* props의 기본값 설정 계속 실수 한다. 기본값 설정! 해주기!&#x20;
  {% endhint %}

## 1. 컴포넌트 Props 설계 &#x20;

&#x20;**1.1 사용자가 전달 해야 하는 props**

* 사용자가 값을 던지면 속성으로 설정 되기 때문에 꼭 속성을 명시할 필요는 없다. → `...restProps`에 동적으로 포함 되로록 설정할 수 있다.&#x20;
* `...restProps`에 포함 되지 않도록 속성을 객체로 전달해서 원하는 요소에 속성을 적용할 수 있다.&#x20;

&#x20;**1.2 요소가 가지고 있어야할 props**

* &#x20;요소가 기본적으로 가질 속성을 설정할 경우 nullish 연산자/ 3항식을 사용해서 기본값을 설정할 수 있다.&#x20;
* 속성의 true/false 설정으로 두 개의 속성을 한 번에 제어할 수 있다.

컴포넌트를 Props를 설계할 때 크게 구분해야 하는 것은 위의 두 부분이다. 전달할 것인가? 기본적으로 갖고 있을 것인가? &#x20;

**1.3 추가적으로 설정할 수 있는 것**&#x20;

* 필수로 전달할 속성과 속성의 유형을 설정할 수 있다. → propTypes&#x20;

### 1.4 부모 컴포넌트 AppHeader

```javascript
import './AppHeader.scss'
import React from 'react'
import AppHomeLink from '../AppHomeLink/AppHomeLink'
import AppNavigation from '../AppNavigation/AppNavigation'

const AppHeader = () => {
  return (
    <header className="appHeader">
      <AppHomeLink title="홈 페이지로 이동">
        <span className="a11yHidden" lang="en">
          EDIYA COFFEE
        </span>
      </AppHomeLink>
      <AppNavigation />
    </header>
  )
}

export default AppHeader

```

### 1.5 자식 컴포넌트 AppHomeLink

```javascript
import './AppHomeLink.scss'
import React from 'react'
import { string } from 'prop-types'

const AppHomeLink = ({ children, external, ...domProps }) => {
  return (
    <h1 className="appHeader__brand">
      <a
        // 기본값이 아닌 사용자가 입력한 나머지 속성이 포함되도록 한다.
        {...domProps}
        className="appHeader__homeLink"
        // external 속성에 true/false를 사용해서 두 개의 속성을 모두 설정하거나 제거 하도록 할 수 있다.
        target={external ? '_blank' : null}
        rel={external ? 'noopener noreferrer' : null}
      >
        {/* 기본 값이 필요한 속성은 아래와 같이 기본 값을 할 수 있다. */}
        {children ?? <span className="a11yHidden">홈링크</span>}
      </a>
    </h1>
  )
}

// href이 반드시 전달 될 수 있도록 propTypes를 설정한다.
AppHomeLink.propTypes = {
  href: string.isRequired,
}

export default AppHomeLink

```

## 2. classNames 설정&#x20;

클래스 이름을 같이 중첩하여 사용하고 싶을 때 사용한다.&#x20;

```javascript
const linkClassNames = classNames('appHeader__homeLink', className ?? '')
```

{% hint style="warning" %}
**props 우선 적용 순위**&#x20;

&#x20;만약 아래와 같은 경우에 domProps에 className(domProps)을 전달 할 경우 **나중에 나오는 className**("appHeader")이 전달 된 className(domProps)를 덮어쓰게 된다.&#x20;

```javascript
<div  
  {...domProps}
  className="appHeader"
</div>
```

{% endhint %}

classNames을 사용하지 않으면 아래의 로직으로 사용자가 직접 커스텀으로 만들어서 사용할 수 있다.&#x20;

```javascript
const linkClassNames = [className, 'appHeader__homeLink'].join(' ')
```

## 3. 특정 요소에 속성 전달&#x20;

하나의 요소가 아닌 컴포넌트의 다른 요소에 속성을 전달하고 싶은 경우가 있다. 이 경우엔 해당 요소에 전달할 속성을 객체로 전달하고 전개연산자를 사용하면 편하다.&#x20;

#### 3.1 부모 컴포넌트&#x20;

```javascript
<AppHomeLink
  wrapperProps={{
    as: "h2"
    className: 'wrapper',
    title: '래퍼',
  }}
  className="test"
  href="/"
  title="홈 페이지로 이동"
>
```

#### 3.2 자식 컴포넌트&#x20;

```javascript
const AppHomeLink = ({
  // wrapperProps을 구조 분해 할당 하여 사용한다. 
  // 이때 AppHomeLink가 전달받은 className과 
  // wrapperProps가 전달받은 wrapperClassName의 속성이름이 같으면 안되서 다르게 설정했다. 
  // wrapperProps: { wrapperClassName, ...restWrapperProps },
  wrapperProps: {
    as: WrapperComponent
    className: WrapperClassName,
    ...restWrapperprops
  },
  children,
  external,
  className,
  ...domProps
}) => {
  // classNames를 사용해서 클래스 이름을 중첩되게 한다. 
  const combineWrapperClassNames = classNames(
    'appHeader__brand',
    WrapperClassName ?? '' 
  )
  return (
    // 전개 연산자를 사용해서 래퍼 요소에 속성이 적용될 수 있도록 한다. 
    <WrapperComponent {...restWrapperprops} className={combineWrapperClassNames}>
    </WrapperComponent>
  )
 } 
```

## 4. 요소 별칭 등록&#x20;

as 속성을 사용해서 별칭을 등록할 수 있다.&#x20;

#### 4.1 부모 컴포넌트&#x20;

```javascript
<AppHomeLink
  wrapperProps={{
    // as 속성을 사용해서 변경할 태그 네임을 문자열로 전달한다. 
    as: 'h2',
    className: 'wrapper',
    title: '래퍼',
  }}
>
```

#### 4.2 자식 컴포넌트

```javascript
const AppHomeLink = ({
  // 전달 받은 as 속성에 별칭을 등록하고 
  wrapperProps: { as: WrapperComponent, wrapperClassName, ...restWrapperProps }
}) => {
  const wrapperClassNames = classNames(
    'appHeader__brand',
    wrapperClassName ?? ''
  )
  return (
  // h1 요소의 이름으로 설정하면 h1 → h2로 변경된다. 
    <WrapperComponent {...restWrapperProps} className={wrapperClassNames}>
    </WrapperComponent>
  )
}

// as의 기본 값을 h1으로 설정 
AppHomeLink.propTypes = {
  wrapperProps: {
    as: 'h1'
  }
}
```

## 5. props 전개

컴포넌트로 전달된 `props` 객체를 전개(spread) 하여 컴포넌트의 내부의 구조(Markup)에 모두 추가 설정할 수 있다.&#x20;

```
<Component {...props} />
```

다만, `props` 객체의 속성을 전개하였을 때 사용된 `external`과 같은 **비 표준 속성은 오류를 발생**시킨다.&#x20;

{% hint style="warning" %}
Warning: Received \`true\` for a non-boolean attribute \`external\`.\
If you want to write it ti the DOM, pass a string instead: external="true" or external={value.toString()}.
{% endhint %}

오류 메시지에서 해결책을 안내하고 있지만, 안내 방법대로 해도 해결이 안되는 경우가 있다. 이런 경우 컴포넌트로 전달된 `props`에서 비 표준 속성을 걸러내어 문제를 해결 할 수 있다. (\<a> DOM Node에 설정되는 속성이므로 `aNodeProps`라는 이름을 사용했습니다)

```
// 구조 분해 할당 + 나머지 연산
const { external, children, ...aNodeProps } = props;

// 전개 연산
<Component {...aNodeProps}>
  {children}
</Component>
```

## 6. props 검사

[PropTypes](https://xn--xy1bk56a.run/react-master/lecture/r-pay-propTypes.html#react-%EC%86%8D%EC%84%B1-%ED%83%80%EC%9E%85-%EA%B2%80%EC%82%AC)를 사용하여 컴포넌트에 전달된 `props`를 검사하도록 설정한다. &#x20;

```
import PropTypes from 'prop-types';
```

## 7. props 기본 값

필요한 경우 컴포넌트의 전달 속성인 `props` 객체의 속성 중 일부에 기본 값을 설정할 수 있다.

```
Component.defaultProps = {
  prop1: 'efaltValue'
}
```

## 8. 다른 예제 보기&#x20;

#### &#x20;8.1 컴포넌트 추출 전 코드&#x20;

```javascript
<button
  onClick={this.handleCloseNav}
  className="resetButton is-close-menu"
  type="button"
  title="메뉴 닫기"
  aria-label="메뉴 닫기"
>
  <span className="close" aria-hidden="true">
    ×
  </span>
</button>
```

#### &#x20;8.2 컴포넌트 추출 후 부모 컴포넌트&#x20;

```javascript
import './AppNavigation.scss'
import React from 'react'
import EdiyaContext from '~/context/ediyaContext'
import AppButton from '../AppButton/AppButton'

class AppNavigation extends React.Component {

  render() {
    const { items } = this.context.navigation
    return (
      <>
        <AppButton
          handleNav={this.handleOpenNav}
          className="is-open-menu"
          title="메뉴 열기"
          aria-label="메뉴 열기"
        >
          <span className="ir"></span>
        </AppButton>
          <AppButton
            handleNav={this.handleCloseNav}
            className="is-close-menu"
            label="메뉴 닫기"
          >
            <span className="close" aria-hidden="true">
              ×
            </span>
          </AppButton>
        </nav>
      </>
    )
  }
}

export default AppNavigation

```

#### &#x20;8.3  AppButton 컴포넌트&#x20;

```javascript
import React from 'react'
import classNames from 'classnames'

const AppButton = ({
  children,
  handleNav,
  className,
  label,
  ...restProps
}) => {
  const buttonClassNames = classNames('resetButton', className ?? '')

  return (
    <button
      {...restProps}
      onClick={handleNav}
      className={buttonClassNames}
      // type 버튼의 경우는 기본값을 사용할 필요없다. 왜냐하면 해당 컴포넌트는 Button으로만 사용할 것이기 때문에 변하지 않는다.
      type="button"
      // title과 aria-label 값이 동일하니 label로 한번에 설정할 수 있다.
      title={label ?? null}
      aria-label={label ?? null}
    >
      {children ?? <span className="ir"></span>}
    </button>
  )
}

export default AppButton

```
