# 컴포넌트 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

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shhn0509.gitbook.io/react/react-study/mini-project/part-1/component-props.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
