<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>heejeen-choi.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 11 May 2023 01:35:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>heejeen-choi.log</title>
            <url>https://velog.velcdn.com/images/heejeen-choi/profile/f945eb16-e477-45fd-b30e-05cb717fd286/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. heejeen-choi.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/heejeen-choi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[subscription.tsx (+dummyData)]]></title>
            <link>https://velog.io/@heejeen-choi/subscription.tsx-dummyData</link>
            <guid>https://velog.io/@heejeen-choi/subscription.tsx-dummyData</guid>
            <pubDate>Thu, 11 May 2023 01:35:58 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">// Subscription.tsx

import React, { useEffect, useState } from &quot;react&quot;;
// import { Link } from &quot;react-router-dom&quot;;
import AddressSet from &quot;../components/AddressSet&quot;;
import Card from &quot;../../common/Card&quot;;
import Row from &quot;../../common/Row&quot;;
import Input from &quot;../../common/Input&quot;;
import Checkbox from &quot;../../common/Checkbox&quot;;
import Radio from &quot;../../common/Radio&quot;;
import { useNavigate } from &quot;react-router-dom&quot;;
// import Select from &quot;../../common/Select&quot;;
import Profile, {CreateUserDataType} from &quot;@dscience/profile-manage/src/features/profile/pages/Profile&quot;;
import DatePicker from &quot;react-datepicker&quot;;
import &quot;react-datepicker/dist/react-datepicker.css&quot;;
import AdminMemo from &quot;../../adminmemo/pages/AdminMemo&quot;;
import Payment from &quot;../../payment/pages/Payment&quot;;
import AddOrder from &quot;../../addorder/pages/AddOrder&quot;;
import Volume from &quot;../../volume/pages/Volume&quot;;
import { CreateSubscriptionType, GiftType } from &quot;../../../types/Types&quot;;
import dayjs from &quot;dayjs&quot;;
import PhoneNumber from &quot;../components/PhoneNumber&quot;;
import { useMutation, useQuery } from &quot;react-query&quot;;
import axios from &quot;axios&quot;;
import PageHeader from &quot;../../common/PageHeader&quot;;

const defaultSubscriptionData = {
  // 구독 상품
  product: {},
  // 회원 정보
  applicant: {},
  // 알림 설정
  notification: {},
  // 구독자 정보
  subscriber: {},
  // 사은품
  gifts: [],
  // 샘플
  samples: [],
  // 구독 기간
  period: {
    begin: undefined,
    end: undefined,
  },
  // 갱신 여부
  extend: false,
  // 메모
  note: &quot;&quot;,
  // 구독 권수
  amount: 0,
};

interface ProductType {
  no: string;
  name: string;
  introduction: string;
  kind: string;
  listPrice: number;
  salesPrice: number;
  active: boolean;
}

const dummyGifts = [
  { name: &quot;선택하세요.&quot;, value: &quot;&quot; },
  { name: &quot;사은품1&quot;, value: &quot;gift1&quot; },
  { name: &quot;사은품2&quot;, value: &quot;gift2&quot; },
  { name: &quot;사은품3&quot;, value: &quot;gift3&quot; },
  { name: &quot;사은품4&quot;, value: &quot;gift4&quot; },
  { name: &quot;사은품5&quot;, value: &quot;gift5&quot; },
];

const dummyAdditionalGifts = [
  { name: &quot;선택하세요.&quot;, value: &quot;&quot; },
  { name: &quot;추가사은품1&quot;, value: &quot;addGift1&quot; },
  { name: &quot;추가사은품2&quot;, value: &quot;addGift2&quot; },
  { name: &quot;추가사은품3&quot;, value: &quot;addGift3&quot; },
  { name: &quot;추가사은품4&quot;, value: &quot;addGift4&quot; },
  { name: &quot;추가사은품5&quot;, value: &quot;addGift5&quot; },
];

let profileId: string = &quot;&quot;;

const Subscription = () =&gt; {
  const [subscriptionData, setSubscriptionData] = useState&lt;CreateSubscriptionType&gt;(defaultSubscriptionData);

  const [subscriptionProducts, setSubscriptionProducts] = useState&lt;ProductType[]&gt;();
  useEffect(() =&gt; {
    refetch().then();
  }, []);
</code></pre>
<ul>
<li>useEffect를 사용할 때에는 첫번째파라미터에는 함수, 두번째파라미터에는 의존값이 들어있는 배열(deps) 넣는다.</li>
<li>만약 deps배열을 비우게 되면 컴포넌트가 처음 나타날때만 useEffect 에 등록한 함수가 호출된다.</li>
<li>useEffect 에서는 함수를 반환 할 수 있는데, 이를 cleanup함수라고함.</li>
<li>cleanup함수는 useEffect에 대한 뒷정리를 함, deps가 비어있는 경우에는 컴포넌트가 사라질때 cleanup함수가 호출된다.</li>
</ul>
<hr>
<h3 id="마운트-시에-하는-작업들--component가-mount-됐을-때처음-나타났을때">마운트 시에 하는 작업들 : Component가 mount 됐을 때(처음 나타났을때)</h3>
<pre><code>- props 로 받은 값을 컴포넌트의 로컬상태로 설정
- 외부 API 요청(REST API 등)
- 라이브러리사용
- setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업예약
- 컴포넌트가 화면에 가장 처음 렌더링 될 때 한번만 실행하고 싶을 때는 deps위치에 빈 배열을 넣는다.
&gt; useEffect(() =&gt; { console.log(&#39;마운트 될 때만 실행된다&#39;) }, `[]`);
&gt; useEffect(() =&gt; { console.log(&#39;렌더링 될 때마다 실행된다&#39;) }) : 배열 생략하면 리렌더링 될 때마다 실행 됨.
- Component가 update 될 때 (특정 props, state가 바뀔 때)
&gt; useEffect(() =&gt; { console.log(name); console.log(&#39;업데이트 될 때 실행된다&#39;) }, `[name]`)
- 특정값이 업데이트 될 때 실행하고 싶을 때는 deps 위치의 배열 안에 검사하고 싶은 값을 넣어준다.(의존값이 들어있는 배열 deps 라고도 함. dependency)
- 업데이트 될 때만 실행하는 것이 아니라 마운트 될 때도 실행된다. 그래서 업데이트 될 때만 특정 함수를 실행하고 싶다면 ?
&gt; const mounted = useRef(false);
&gt; useEffect(() =&gt; { if(!mounted.current){mounted.current = true} else(//ajax) }, `[바뀌는 값]`)
&gt; 컴포넌트가 마운트 될 때는 if문에서 아무것도 실행하지 않고 mounted 값만 바꿔주고, else에서 배열 안의 값이 바뀌면, ajax 서버 통신이라던지 원하는 코드를 실행할 수 있다.</code></pre><hr>
<h3 id="언마운트-시에-하는-작업들--component가-unmount-될-때사라질때--update-되기-직전에">언마운트 시에 하는 작업들 : Component가 unmount 될 때(사라질때) &amp; update 되기 직전에</h3>
<pre><code>- setInterval, setTimeout 을 사용하여 등록한 작업들을 clear하기 (clearInterval, clearTimeout)
- 라이브러리 인스턴스 제거
&gt; useEffect(() =&gt; {console.log(&#39;effect&#39;); console.log(name); return () =&gt; {console.log(&#39;cleanup&#39;); console.log(name);};}, `[]`);
- cleanup 함수 반환 (return 뒤에 나오는 함수이며 useEffect에 대한 뒷정리함수 라고 한다.)
- 언마운트 될 때만 cleanup함수를 실행하고 싶을 때 : 두번째 파라미터로 빈 배열 `[]`을 넣는다.
- 특정 값이 업데이트되기 직전에 cleanup함수를 실행하고 싶을 때 : deps 배열 안에 검사하고 싶은 값을 넣어준다.
- deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 도리 때마다 호출된다.
- 리액트컴포넌트는 기본적으로 부모컴포넌트가 리렌더링되면 자식컴포넌트 또한 리렌더링이 된다. 바뀐 내용이 없더라도//</code></pre><hr>
<h3 id="deps에-특정-값-넣기">deps에 특정 값 넣기</h3>
<pre><code>- deps에 특정 값을 넣게 된다면 : (1)컴포넌트가 처음 마운트 될 때, (2)지정한 값이 바뀔 때, (3)언마운트 될 때, (4)값이 바뀌기 직전에 모두 호출된다.
- useEffect 안에서 사용하는 상태나, props가 있다면 : useEffect 의 deps에 넣어주는 것이 규칙이다.
- 만약 사용하는 값을 넣어주지 않는다면, useEffect 안의 함수가 실행 될 때 최신 상태, props를 가리키지 않는다.
- deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 useEffect 함수가 호출된다.</code></pre><hr>
<h3 id="마운트의-예시-코드-추가설명-useref">마운트의 예시 코드 추가설명 <code>useRef()</code></h3>
<pre><code>- useRef()함수 사용법</code></pre><pre><code class="language-javascript">// 설명할 부분 복붙
- 업데이트 될 때만 실행하는 것이 아니라 마운트 될 때도 실행된다. 그래서 업데이트 될 때만 특정 함수를 실행하고 싶다면 ?  
    const mounted = useRef(false);
    useEffect(() =&gt; { 
      if(!mounted.current){
        mounted.current = true
      } else(
        //ajax
      )
    }, [바뀌는 값]); 
- 컴포넌트가 마운트 될 때는 if문에서 아무것도 실행하지 않고 mounted 값만 바꿔주고, else에서 배열 안의 값이 바뀌면, ajax 서버 통신이라던지 원하는 코드를 실행할 수 있다.</code></pre>
<ul>
<li>자바스크립트에서 특정 DOM을 선택해야 할 때는 <code>DOM Selector</code>를 사용한다.</li>
<li>리액트 프로젝트에서도 특정 요소의 크기를 가져오거나, 포커스를 설정하거나, <code>특정 DOM을 선택해야할 상황</code>이 있음.</li>
<li>이 경우에, 리액트 함수형 컴포넌트에서는 useRef() 사용한다, ( 클래스형에서는 : 콜백함수 | React.creatRef 함수 사용함)</li>
</ul>
<pre><code class="language-javascript">// 초기화 버튼 누르면 input태그에 focus 잡히는 기능 구현하기.
// InputTest.js

import React, {useState, useRef} from &#39;react&#39;;

const Input = () =&gt; {
  const [text, setText] = useState(&#39;&#39;);
  const nameInput = useRef();
  const onChange = (e) =&gt; {
    setText(e.target.value)
  };
  const onReset = () =&gt; {
    setText(&#39;&#39;);
    nameInput.current.focus();
  };

  return (
    &lt;div&gt;
      &lt;input
        name=&quot;name&quot;
        onChange={onChange}
        value={text}
        // &lt;input&gt;태그의 value 속성은 &lt;input&gt;요소의 초기값을 명시한다. value속성은 &lt;input&gt;요소의 type속성값에 따라 다른 용도로 사용된다. 
        // type=&#39;button&#39;, type=&#39;submit&#39;, type=&#39;reset&#39; : 버튼 내의 텍스트를 정의함.
        // type=&#39;hidden&#39;, type=&#39;password&#39;, type=&#39;text&#39; : 입력 필드의 초기값을 정의 함.
        //        &lt;input type=&#39;text&#39; value=&#39;아이디를 입력하세요&#39;&gt; , 단점은 : 사용자가 초기값을 지우고 입력해야한다. placeholder 사용하면 초기값 자동섹제 됨.
        // type=&#39;checkbox&#39;, type=&#39;image&#39;, type=&#39;radio&#39; : 해당 입력 필드를 선택 시 서버로 &quot;제출&quot;되는 값을 정의함.
        // &lt;input&gt;요소의 type속성값이 &#39;file&#39;인 경우에는 value 속성을 사용할 수 없다.</code></pre>
<p>** Button : <input type='button' value='' onClick='()=>{}'> <code>&lt;input type=&#39;button&#39; value=&#39;&#39; onClick=&#39;()=&gt;{}&#39;&gt;</code>
    - value === 버튼 위애 표시되는 문자
    - onClick === 클릭했을 때 실행되는 함수
** Input태그와 value값 : <input type='text' id='' value='' /> <code>&lt;input type=&#39;text&#39; id=&#39;&#39; value=&#39;&#39; /&gt;</code>
    - value === text박스에 표시되는 글자
    - id 값을 준 후, script에서 getElementByID(&#39;&#39;).value 를 사용하여 value값을 가져올 수 있다.
** Radio의 value값 : <input type='radio' name='Group' value=''> <code>&lt;input type=&#39;radio&#39; name=&#39;Group&#39; value=&#39;&#39;&gt;</code>
    - name === radio의 group이름, name이 같은 버튼 중 하나를 선택할 수 있다.
    - value === 각 radio버튼의 value값, getElementsByName(&#39;&#39;).value 를 사용하여 같은 name을 가지는 value값들을 불러올 수 있음.
** CheckBox : <input type='checkbox' name='group' value=''> <code>&lt;input type=&#39;checkbox&#39; name=&#39;group&#39; value=&#39;&#39;&gt;</code>
    - name == radio의 gruop이름. name이 같은 버튼 중 하나를 선택가능
    - value == 각 radio버튼의 value값. getElementsByName(&#39;&#39;).value를 사용하여 같은 name을 가지는 value값들을 불러 올 수 있음.</p>
<hr>
<h3 id="react-input의-value-가져오기">[REACT] input의 value 가져오기</h3>
<pre><code>- useState를 이용하여 input의 value를 가져오기
    - 첫번째 아이디 input값의 value, 두번째 비밀번호 input값의 value를 가져오기</code></pre><pre><code class="language-javascript">//1.
const [idValue, setIdValue] = useState(&#39;&#39;);
const [pwValue, setPwValue] = useState(&#39;&#39;);
        - 아이디와 비번의 처음은 빈값이기 때문에 useState 안에 (&#39;&#39;) 비었다는 표시 해줌,

//2,
const saveUserId = event =&gt; {
  setIdValue(event.target.value);
  //console.log(event.target.value)
}
const saveUserPw = envent =&gt; {
  setPwValue(event.target.value)
}</code></pre>
<hr>
<h3 id="etargetvalue">e.target.value</h3>
<pre><code class="language-javascript">1) onChange 함수 정의
const onChange = () =&gt; { setNumber(e.target.value) }

2) input태그 안에 value와 onChange 정의
&lt;input value={number} onChange={onChange}/&gt;

  - input창에 &#39;ㅔ&#39;라고 글자 입력했을때 onChange함수가 실행되면서 이벤트 객체를 반환한다.
  - 반환된 이벤트가 onChange 함수에서 실행될 때 받은 인자 
  1.e의 객체구조, 
    2.e.target의 구조, 
      3.e.target.value의 구조는 아래와 같다

1.e의 객체구조
console.log(e);

//output is 
SyntheticBaseEvent {
    _reactName: &quot;onChange&quot;,
    _targetInst: null, 
    type: &quot;change&quot;, 
    nativeEvent: InputEvent, 
    target: input, 
    bubbles: true
    cancelable: false
    currentTarget: null
    defaultPrevented: false
    eventPhase: 3
    isDefaultPrevented: ƒ 
    functionThatReturnsFalse()
    isPropagationStopped: ƒ 
    functionThatReturnsFalse()
    isTrusted: true
    nativeEvent: InputEvent {
    isTrusted: true, 
    data: &quot;ㅇ&quot;, 
    isComposing: true, 
    inputType: &quot;insertCompositionText&quot;, 
    dataTransfer: null, …}
    target: inputtimeStamp: 1090.3649999963818
    type: &quot;change&quot;
    _reactName: &quot;onChange&quot;
    _targetInst: null
    proto: Object}

2.e.target의 구조
- 이벤트객체의 target만 가져와서 출력한다. 위 객체에서 target의 value는 input이기 때문에 다음과 같이 출력
console.log(e.target);
//output is
&lt;input placeholder=&#39;할일입력&#39; value=&#39;ㅔ&#39;&gt;

3.e.target.value의 구조
- input의 value를 가져오므로 입력한 값인 &#39;ㅔ&#39;를 출력한다
console.log(e.target.value);
//output is
ㅔ

        - 여러개의 console.log()를 실행하면서 알게 된 것은, console.log(e.target.value)와 console.log(e.nativeEvent.data)가 같은 값 &#39;ㅔ&#39;를 출력한다는 것

      273 565 035 18487
10470
</code></pre>
<hr>
<pre><code class="language-javascript">        ref={nameInput}
      /&gt;
      &lt;button onClick={onReset}&gt; 초기화 &lt;button&gt;
      &lt;div&gt;
        &lt;b&gt;내용: &lt;b&gt;
        {text}
      &lt;div&gt;
    &lt;div&gt;
  );
};

export default InputTest;</code></pre>
<hr>
<p>````javascript
  const { isLoading, error, data, isFetching, refetch } = useQuery({
    queryKey: [&quot;subscriptionProducts&quot;],
    queryFn: () =&gt; querySubscriptionProducts(),
  });
  const querySubscriptionProducts = async () =&gt; {
    const response = await axios.get(
      <code>/api/product/dsstore/dssdlabs/-/product</code>,
      {
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
          service: &quot;product&quot;,
          query: &quot;QuerySubscriptionProducts&quot;,
        },
      }
    );
    return setSubscriptionProducts(response.data);
  };</p>
<p>  const autoCompleteDateRangeAndAmount = (productId: string) =&gt; {
    const begin = new Date().getTime();
    const end = new Date(
      dayjs(new Date()).add(365, &quot;d&quot;).format(&quot;YYYY-MM-DD&quot;)
    ).getTime();
    const period = {
      begin,
      end,
    };
    if (productId == &quot;1&quot; || productId == &quot;3&quot;) {
      setSubscriptionData({ ...subscriptionData, amount: 12, period });
    } else {
      setSubscriptionData({ ...subscriptionData, amount: 24, period });
    }
  };</p>
<p>  const handleSampleCheck = (checked: boolean, id: string, name: string) =&gt; {
    if (checked) {
      subscriptionData.samples?.push({ id, name });
    } else {
      const samples = subscriptionData.samples?.filter((f) =&gt; f.id !== id);
      setSubscriptionData({ ...subscriptionData, samples });
    }
  };</p>
<p>  const handleDatePicker = (date: Date, isBegin: boolean) =&gt; {
    const period = subscriptionData.period;
    const time = date.getTime();
    if (isBegin) {
      setSubscriptionData({
        ...subscriptionData,
        period: { ...period, begin: time },
      });
    } else {
      setSubscriptionData({
        ...subscriptionData,
        period: { ...period, end: time },
      });
    }
  };</p>
<p>  useEffect(() =&gt; {
    if (subscriptionData.product.no) {
      autoCompleteDateRangeAndAmount(subscriptionData.product.no);
    }
  }, [subscriptionData.product.no]);</p>
<p>  const handleSelectAdditionalGift = (gift: GiftType) =&gt; {
    const defaultGifts = subscriptionData.gifts?.filter(
      (f) =&gt; f.kind === &quot;Additional&quot;
    );
    let additionalGifts;
    if (defaultGifts &amp;&amp; defaultGifts.length &gt; 0) {
      additionalGifts = [...defaultGifts, gift];
    } else {
      additionalGifts = [gift];
    }
    setSubscriptionData({ ...subscriptionData, gifts: additionalGifts });
  };
  const handleSelectGift = (gift: GiftType) =&gt; {
    const currentGifts = subscriptionData.gifts?.filter(
      (f) =&gt; f.kind === &quot;Default&quot;
    );
    let gifts;
    if (currentGifts &amp;&amp; currentGifts.length &gt; 0) {
      gifts = [...currentGifts, gift];
    } else {
      gifts = [gift];
    }
    setSubscriptionData({ ...subscriptionData, gifts });
  };</p>
<p>  console.log(subscriptionData);</p>
<p>  useEffect(() =&gt; {
    if (subscriptionData.product.no) {
      autoCompleteDateRangeAndAmount(subscriptionData.product.no);
    }
  }, [subscriptionData.product.no]);</p>
<p>  // 신청자 입력 정보
  const [copyApplicant, setCopyApplicant] = useState(false);
  const [profileData, setProfileData] = useState<CreateUserDataType>();</p>
<p>  // const createSession = useMutation(
  //   () =&gt;
  //     axios.post(
  //       &quot;/api/standalone/session?tenantId=6d1decb&quot;,
  //       {},
  //       {
  //         headers: {
  //           command: &quot;CreateStandaloneSession&quot;,
  //         },
  //       }
  //     ),
  //   {
  //     onSuccess: () =&gt; {
  //       createSubscription.mutate();
  //       createProfile.mutate();
  //     },
  //   }
  // );</p>
<p>  const navigate = useNavigate();</p>
<p>  const createProfile = useMutation(
    () =&gt;
      axios.post(&quot;/api/profile/dsstore/dssdlabs/-/profile&quot;, profileData, {
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
          command: &quot;CreateProfile&quot;,
          withCredentials: true,
          method: &quot;POST&quot;,
        },
      }),
    {
      onSuccess: (data) =&gt; {
        console.log(&quot;profile complete&quot;, data);
        // 8자리 사용자 식별자 - data.data
        profileId = data.data;
        console.log(&quot;profileId&quot;, profileId);
        createOrder.mutate();
      },
      onError: (e) =&gt; {
        console.log(&quot;JSON.stringify(e)&quot;, JSON.stringify(e));
      },
    }
  );</p>
<p>  const createOrder = useMutation(
    () =&gt;
      axios.post(
        &quot;/api/order/dsstore/dssdlabs/-/order&quot;,
        {
          orderer: {
            id: profileId,
            name: subscriptionData.applicant.name,
          },
          items: [
            {
              itemId: &quot;11111111&quot;,
              product: {
                id: subscriptionData.product.no,
                name: subscriptionData.product.name,
              },
              price: subscriptionData.product.salesPrice,
              quantity: 1,
            },
          ],
        },
        {
          headers: {
            &quot;Content-Type&quot;: &quot;application/json&quot;,
            command: &quot;CreateOrder&quot;,
            service: &quot;order&quot;,
            withCredentials: true,
            method: &quot;POST&quot;,
          },
        }
      ),
    {
      onSuccess: (data) =&gt; {
        console.log(&quot;create order&quot;, data);
        createSubscription.mutate();
      },
      onError: (e) =&gt; {
        console.log(&quot;JSON.stringify(e)&quot;, JSON.stringify(e));
      },
    }
  );</p>
<p>  const createSubscription = useMutation(
    () =&gt;
      axios.post(
        &quot;/api/subscription/dsstore/dssdlabs/-/subscription&quot;,
        {
          product: {
            id: subscriptionData.product.no,
            name: subscriptionData.product.name,
          },
          applicant: {
            id: profileId,
            name: subscriptionData.applicant.name,
            address: {
              zipCode: subscriptionData.applicant.address?.zipCode,
              basic: subscriptionData.applicant.address?.basic,
              detail: subscriptionData.applicant.address?.detail,
            },
            contacts: [
              subscriptionData.applicant.telNo &amp;&amp; {
                type: &quot;Landline&quot;,
                number: subscriptionData.applicant.telNo,
              },
              subscriptionData.applicant.mobileNo &amp;&amp; {
                type: &quot;Cellular&quot;,
                number: subscriptionData.applicant.mobileNo,
              },
            ],
            email: {
              name: subscriptionData.applicant.email?.split(&quot;@&quot;)[0],
              domain: subscriptionData.applicant.email?.split(&quot;@&quot;)[1],
            },
            librarianEmail: {
              name: subscriptionData.applicant.librarianEmail?.split(&quot;@&quot;)[0],
              domain: subscriptionData.applicant.librarianEmail?.split(&quot;@&quot;)[1],
            },
          },
          notification: {
            notifyEmail: subscriptionData.applicant.emailReception,
            notifyMessage: subscriptionData.applicant.smsReception,
          },
          subscriber: {
            name: subscriptionData.subscriber?.name,
            address: {
              zipCode: subscriptionData.subscriber?.address?.zipCode,
              basic: subscriptionData.subscriber?.address?.basic,
              detail: subscriptionData.subscriber?.address?.detail,
            },
            contacts: [
              subscriptionData.subscriber?.telNo &amp;&amp; {
                type: &quot;Landline&quot;,
                number: subscriptionData.subscriber?.telNo,
              },
              subscriptionData.subscriber?.mobileNo &amp;&amp; {
                type: &quot;Cellular&quot;,
                number: subscriptionData.subscriber?.mobileNo,
              },
            ],
            jobName: subscriptionData.subscriber?.jobName,
          },
          gifts: subscriptionData.gifts,
          samples: subscriptionData.samples,
          period: {
            begin: subscriptionData.period?.begin,
            end: subscriptionData.period?.end,
          },
          extend: subscriptionData.extend,
          note: subscriptionData.note,
        },
        {
          headers: {
            &quot;Content-Type&quot;: &quot;application/json&quot;,
            command: &quot;CreateSubscription&quot;,
            withCredentials: true,
            method: &quot;POST&quot;,
          },
        }
      ),
    {
      onSuccess: (data) =&gt; {
        console.log(&quot;subscription complete&quot;, data);
        navigate(&quot;/dsstore/dssdlabs/-/subscription&quot;);
      },
      onError: (e) =&gt; {
        console.log(&quot;JSON.stringify(e)&quot;, JSON.stringify(e));
      },
    }
  );</p>
<p>  return (
    &lt;&gt;
      &lt;PageHeader
        pageTitle={&quot;정기구독&quot;}
        rightContent={
          &lt;button
            className=&quot;btn btn-sm btn-outline btn-primary&quot;
            onClick={() =&gt; {
              createProfile.mutate();
            }}
          &gt;
            정기구독 신청
          </button>
        }
      /&gt;
      &lt;Row style={{ paddingBottom: &quot;100px&quot; }}&gt;
        <div className="col-6">
          &lt;Profile
            setProfileData={(e: CreateUserDataType) =&gt; {
              setProfileData({ ...e });
              setSubscriptionData({ ...subscriptionData, applicant: { ...e } });
            }}
            handleCopySubscriber={(e: boolean) =&gt; {
              if (e) {
                setSubscriptionData((prev) =&gt; {
                  return {
                    ...prev,
                    applicant: {
                      mobileNo: subscriptionData.subscriber?.mobileNo,
                      telNo: subscriptionData.subscriber?.telNo,
                      address: {
                        basic: subscriptionData.subscriber?.address?.basic,
                        detail: subscriptionData.subscriber?.address?.detail,
                        zipCode: subscriptionData.subscriber?.address?.zipCode,
                      },
                      name: subscriptionData.subscriber?.name,
                    },
                  };
                });
              } else {
                setSubscriptionData((prev) =&gt; {
                  return {
                    ...prev,
                    applicant: {
                      mobileNo: &quot;&quot;,
                      telNo: &quot;&quot;,
                      address: undefined,
                      name: &quot;&quot;,
                    },
                  };
                });
              }
            }}
            profileData={{
              ...subscriptionData.applicant,
              address: { ...subscriptionData.applicant?.address },
            }}
          /&gt;
          &lt;Card header={&quot;정기구독 신청&quot;} className=&quot;mt-10&quot;&gt;
            <Row className="mb-2">
              <label className="col-lg-3 col-form-label fw-bold fs-6">
                <span className="required"> 구독잡지 </span>
              </label>
              <div className="col-lg-9 fv-row ">
                <div className="col-9">
                  {subscriptionProducts
                    ? subscriptionProducts.map((product) =&gt; {
                        return (
                          &lt;Radio
                            checked={subscriptionData.product.no === product.no}
                            onChange={(e) =&gt; {
                              if (e.target.checked) {
                                console.log(&quot;e &gt;&gt;&quot;, product);</p>
<pre><code>                            setSubscriptionData({
                              ...subscriptionData,
                              product,
                            });
                          }
                        }}
                        label={product.name}
                        className={&quot;form-check-inline&quot;}
                        key={product.no}
                      /&gt;
                    );
                  })
                : null}
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;div className=&quot;col-md-6&quot;&gt;
            &lt;label
              id=&quot;kt_td_picker_linked_1_input&quot;
              className=&quot;form-label fw-bold&quot;
            &gt;
              구독시작일
            &lt;/label&gt;
            &lt;div
              className=&quot;input-group log-event&quot;
              id=&quot;kt_td_picker_linked_1&quot;
              data-td-target-input=&quot;nearest&quot;
              data-td-target-toggle=&quot;nearest&quot;
            &gt;
              &lt;DatePicker
                id=&quot;kt_td_picker_linked_1_input&quot;
                className=&quot;datePicker form-control&quot;
                data-td-target=&quot;#kt_td_picker_linked_1&quot;
                dateFormat={&quot;yyyy-MM-dd&quot;}
                value={
                  subscriptionData.period?.begin
                    ? dayjs(subscriptionData.period?.begin).format(
                        &quot;YYYY-MM-DD&quot;
                      )
                    : undefined
                }
                onSelect={(e) =&gt; {
                  handleDatePicker(e, true);
                }}
                onChange={(e) =&gt; {
                  if (e) {
                    handleDatePicker(e, true);
                  }
                }}
              /&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div className=&quot;col-md-6&quot;&gt;
            &lt;label
              id=&quot;kt_td_picker_linked_2_input&quot;
              className=&quot;form-label fw-bold&quot;
            &gt;
              구독종료일
            &lt;/label&gt;
            &lt;div
              className=&quot;input-group log-event&quot;
              id=&quot;kt_td_picker_linked_2&quot;
              data-td-target-input=&quot;nearest&quot;
              data-td-target-toggle=&quot;nearest&quot;
            &gt;
              &lt;DatePicker
                id=&quot;kt_td_picker_linked_1_input&quot;
                className=&quot;datePicker form-control&quot;
                data-td-target=&quot;#kt_td_picker_linked_1&quot;
                dateFormat={&quot;yyyy-MM-dd&quot;}
                value={
                  subscriptionData.period?.end
                    ? dayjs(subscriptionData.period?.end).format(
                        &quot;YYYY-MM-DD&quot;
                      )
                    : undefined
                }
                onSelect={(e) =&gt; {
                  handleDatePicker(e, false);
                }}
                onChange={(e) =&gt; {
                  if (e) {
                    handleDatePicker(e, false);
                  }
                }}
              /&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot; style={{ alignItems: &quot;center&quot; }}&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 구독권수 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-4 fv-row &quot;&gt;
            &lt;Input
              type=&quot;text&quot;
              className=&quot;form-control form-control-sm&quot;
              placeholder=&quot;숫자를 입력해주세요&quot;
              name=&quot;company&quot;
              value={subscriptionData.amount}
              onChange={(e) =&gt;
                setSubscriptionData({
                  ...subscriptionData,
                  amount: Number(e.target.value.replace(/[^0-9]/g, &quot;&quot;)),
                })
              }
            /&gt;
          &lt;/div&gt;
          권
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 기본사은품 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;select
              className=&quot;form-select form-select-sm&quot;
              name=&quot;currency&quot;
              onChange={(e) =&gt;
                handleSelectGift({
                  productId: e.target.value,
                  productName: e.target.value,
                  kind: &quot;Default&quot;,
                })
              }
            &gt;
              {dummyGifts.map((gift, index) =&gt; {
                return (
                  &lt;React.Fragment key={gift.name + index}&gt;
                    &lt;option value={gift.value}&gt;{gift.name}&lt;/option&gt;
                  &lt;/React.Fragment&gt;
                );
              })}
            &lt;/select&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 추가사은품 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;select
              className=&quot;form-select form-select-sm&quot;
              name=&quot;currency&quot;
              onChange={(e) =&gt;
                handleSelectAdditionalGift({
                  productId: e.target.value,
                  productName: e.target.value,
                  kind: &quot;Additional&quot;,
                })
              }
            &gt;
              {dummyAdditionalGifts.map((addGift, index) =&gt; {
                return (
                  &lt;React.Fragment key={addGift.name + index}&gt;
                    &lt;option value={addGift.value}&gt;{addGift.name}&lt;/option&gt;
                  &lt;/React.Fragment&gt;
                );
              })}
              {/* &lt;option value=&quot;&quot;&gt;선택하세요.&lt;/option&gt;
              &lt;option value=&quot;addgift1&quot;&gt;추가사은품1&lt;/option&gt;
              &lt;option value=&quot;addgift2&quot;&gt;추가사은품2&lt;/option&gt;
              &lt;option value=&quot;addgift3&quot;&gt;추가사은품3&lt;/option&gt;
              &lt;option value=&quot;addgift4&quot;&gt;추가사은품4&lt;/option&gt;
              &lt;option value=&quot;addgift5&quot;&gt;추가사은품5&lt;/option&gt; */}
            &lt;/select&gt;
          &lt;/div&gt;
        &lt;/Row&gt;

        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 샘플항목 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9 fv-row &quot;&gt;
            &lt;div className=&quot;col-9&quot;&gt;
              {subscriptionProducts
                ? subscriptionProducts.map((product) =&gt; {
                    return (
                      &lt;Checkbox
                        onChange={(e) =&gt; {
                          handleSampleCheck(
                            e.target.checked,
                            product.no,
                            product.name
                          );
                        }}
                        label={product.name.replace(&quot;(정기구독)&quot;, &quot;&quot;)}
                        className={&quot;form-check-inline&quot;}
                        key={product.no}
                      /&gt;
                    );
                  })
                : null}
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        {/* &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 샘플항목 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;Row&gt;
              &lt;Checkbox
                label=&quot;과동&quot;
                className=&quot;col-3&quot;
                onChange={(e) =&gt;
                  handleSampleCheck(e.target.checked, &quot;과동&quot;)
                }
              /&gt;
              &lt;Checkbox
                label=&quot;어동&quot;
                className=&quot;col-3&quot;
                onChange={(e) =&gt;
                  handleSampleCheck(e.target.checked, &quot;어동&quot;)
                }
              /&gt;
              &lt;Checkbox
                label=&quot;수동&quot;
                className=&quot;col-3&quot;
                onChange={(e) =&gt;
                  handleSampleCheck(e.target.checked, &quot;수동&quot;)
                }
              /&gt;
              &lt;Checkbox
                label=&quot;어수동&quot;
                className=&quot;col-3&quot;
                onChange={(e) =&gt;
                  handleSampleCheck(e.target.checked, &quot;어수동&quot;)
                }
              /&gt;
            &lt;/Row&gt;
          &lt;/div&gt;
        &lt;/Row&gt; */}

        &lt;Row className=&quot;mb-2&quot; style={{ alignItems: &quot;center&quot; }}&gt;
          {/* 구독자 정보 */}
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 구독자 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-6 fv-row &quot;&gt;
            &lt;Input
              type=&quot;text&quot;
              className=&quot;form-control form-control-sm&quot;
              placeholder=&quot;구독자이름을 입력해주세요&quot;
              name=&quot;company&quot;
              value={subscriptionData.subscriber?.name}
              onChange={(e) =&gt;
                setSubscriptionData((prev) =&gt; {
                  return { ...prev, subscriber: { name: e.target.value } };
                })
              }
            /&gt;
          &lt;/div&gt;
          &lt;Checkbox
            label=&quot;신청자 정보와 동일&quot;
            className=&quot;col-3&quot;
            onChange={(e) =&gt; {
              setCopyApplicant(e.target.checked);
              if (e.target.checked) {
                setSubscriptionData({
                  ...subscriptionData,
                  subscriber: {
                    mobileNo: subscriptionData.applicant.mobileNo,
                    telNo: subscriptionData.applicant.telNo,
                    address: { ...subscriptionData.applicant.address },
                    name: subscriptionData.applicant.name,
                  },
                });
              } else {
                setSubscriptionData({
                  ...subscriptionData,
                  subscriber: {
                    address: undefined,
                    mobileNo: &quot;&quot;,
                    name: &quot;&quot;,
                    telNo: &quot;&quot;,
                  },
                });
              }
            }}
          /&gt;
        &lt;/Row&gt;

        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 주소 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;AddressSet
              handleInput={(k, e) =&gt;
                setSubscriptionData((prev) =&gt; {
                  return {
                    ...prev,
                    subscriber: {
                      ...prev.subscriber,
                      address: { ...prev.subscriber?.address, [k]: e },
                    },
                  };
                })
              }
              basic={subscriptionData.subscriber?.address?.basic}
              detail={subscriptionData.subscriber?.address?.detail}
              zipCode={subscriptionData.subscriber?.address?.zipCode}
            /&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 전화번호 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;PhoneNumber
              number={
                copyApplicant
                  ? subscriptionData.applicant?.telNo
                  : subscriptionData.subscriber?.telNo
              }
              handleNumber={(e) =&gt; {
                setSubscriptionData({
                  ...subscriptionData,
                  subscriber: {
                    ...subscriptionData.subscriber,
                    telNo: e,
                  },
                });
              }}
            /&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 휴대폰번호 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;PhoneNumber
              number={
                copyApplicant
                  ? subscriptionData.applicant.mobileNo
                  : subscriptionData.subscriber?.mobileNo
              }
              handleNumber={(e) =&gt; {
                setSubscriptionData({
                  ...subscriptionData,
                  subscriber: {
                    ...subscriptionData.subscriber,
                    mobileNo: e,
                  },
                });
              }}
            /&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row className=&quot;mb-2&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 구독자직업 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-9&quot;&gt;
            &lt;select
              className=&quot;form-select form-select-sm&quot;
              name=&quot;currency&quot;
              onChange={(e) =&gt; {
                setSubscriptionData({
                  ...subscriptionData,
                  subscriber: {
                    ...subscriptionData.subscriber,
                    jobName: e.target.value,
                  },
                });
              }}
            &gt;
              &lt;option value=&quot;&quot;&gt;선택하세요.&lt;/option&gt;
              &lt;option value=&quot;teacher&quot;&gt;교사&lt;/option&gt;
              &lt;option value=&quot;medicine&quot;&gt;의료&lt;/option&gt;
              &lt;option value=&quot;business&quot;&gt;사업&lt;/option&gt;
              &lt;option value=&quot;IT&quot;&gt;IT&lt;/option&gt;
              &lt;option value=&quot;notSelect&quot;&gt;선택안함&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;Row style={{ alignItems: &quot;center&quot; }} className=&quot;mb-6&quot;&gt;
          &lt;label className=&quot;col-lg-3 col-form-label fw-bold fs-6&quot;&gt;
            &lt;span className=&quot;required&quot;&gt; 연장여부 &lt;/span&gt;
          &lt;/label&gt;
          &lt;div className=&quot;col-lg-2&quot;&gt;
            &lt;a
              href=&quot;#&quot;
              className=&quot;btn btn-warning  btn-sm&quot;
              aria-disabled={true}
            &gt;
              연장
            &lt;/a&gt;
          &lt;/div&gt;
          &lt;div className=&quot;col-lg-7&quot;&gt;
            &lt;Input
              type=&quot;text&quot;
              className=&quot;form-control form-control-sm&quot;
              placeholder=&quot;자동입력부분(더블클릭삭제)&quot;
              name=&quot;company&quot;
              value={subscriptionData.extend ? &quot;연장&quot; : &quot;신규&quot;}
              readOnly
            /&gt;
          &lt;/div&gt;
        &lt;/Row&gt;
        &lt;label id=&quot;&quot; className=&quot;form-label&quot;&gt;
          코멘트
        &lt;/label&gt;
        &lt;textarea
          className=&quot;form-control form-control&quot;
          data-kt-autosize=&quot;true&quot;
          placeholder=&quot;입력해주세요&quot;
          onChange={(e) =&gt; {
            setSubscriptionData({
              ...subscriptionData,
              note: e.target.value,
            });
          }}
        /&gt;
      &lt;/Card&gt;
    &lt;/div&gt;
    &lt;div className=&quot;col-6&quot;&gt;
      &lt;Payment /&gt;
      &lt;AddOrder className=&quot;mt-6&quot; /&gt;
      &lt;Volume className=&quot;mt-6&quot; /&gt;
      &lt;AdminMemo className=&quot;mt-6&quot; /&gt;
    &lt;/div&gt;
  &lt;/Row&gt;
&lt;/&gt;</code></pre><p>  );
};</p>
<p>export default Subscription;</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Campaign Init setting_ apply to RestfulAPI ]]></title>
            <link>https://velog.io/@heejeen-choi/Campaign-Init-setting-apply-to-RestfulAPI</link>
            <guid>https://velog.io/@heejeen-choi/Campaign-Init-setting-apply-to-RestfulAPI</guid>
            <pubDate>Wed, 03 May 2023 02:35:15 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">// aggregate/Campaign.java

public class Campaign {                                        
    //
    private String id;
    private String name;
    //
    private Period period;
    private List&lt;Product&gt; products;

    public Campaign(CreateCampaign command) {
        //
        this.id = command.getId();
        this.name = command.getName();
        this.period = command.getPeriod();
        //
        this.products = new ArrayList&lt;&gt;();
    }

    public void change(UpdateCampaign command) {
        //
        this.name = command.getName();
        this.period = command.getPeriod();
    }

    public void attachProduct(AttachProduct command) {
        //
        this.products.add(command.getProduct());
    }

    public void detachProduct(DetachProduct command) {
        //
        Optional&lt;Product&gt; product = this.findProduct(command.getProductNo());
        if (product.isPresent()) {
            this.products.remove(product);
        }
    }

    private Optional&lt;Product&gt; findProduct(String productNo) {
        //
        return this.products.stream()
                .filter((product) -&gt; { return productNo.equals(product.getNo()); })
                .findFirst();
    }
}


//aggregate/Product.java

public class Product {
    //
    private String no;
    private String name;
    private int listPrice;
    private int salesPrice;
}</code></pre>
<pre><code class="language-javascript">
//Campaign.tsx



import React, { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;;
import { useMutation, useQuery } from &quot;react-query&quot;;

// const Campaign = () =&gt; {
//   const [campaignProducts, setCampaignProducts] = useState();

//   const { isLoading, error, data, isFetching, refetch } = useQuery({
//     queryKey: [&quot;campaignProducts&quot;],
//     queryFn: () =&gt; queryCampaignProducts(),
//   });
//   const queryCampaignProducts = async () =&gt; {
//     const response = await axios.get(
//       `/api/campaign/dsstore/dssdlabs/-/campaign`,
//       {
//         headers: {
//           &quot;Content-Type&quot;: &quot;application/json&quot;,
//           service: &quot;campaign&quot;,
//           query: &quot;QueryCampaign&quot;,
//         },
//       }
//     );
//     return setCampaignProducts(response.data);
//   };
// };

// export default Campaign;

interface CampaignType {
  campaignId: string;
  product: string;
  productNo: string;
  id: string;
  name: string;
  period: string;
}

//AttachProduct.java
public class AttachProduct extends Command {
    //
    private String campaignId;
    private Product product;
}

//Campaign.tsx
const Campaign = () =&gt; {
  const AttachProduct = () =&gt; {
    fetch(&quot;/api/campaign/dsstore/dssdlabs/-/campaign&quot;, {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        service: &quot;campaign&quot;,
        command: &quot;AttachProduct&quot;,
      },
      body: JSON.stringify({
        campaignId: &quot;campaignId&quot;,
        product: {
          no: &quot;&quot;,
          name: &quot;&quot;,
          listPrice: 1000,
          salesPrice: 1000,
        },
      }),
    }).then((response) =&gt; {
      if (response.status === 200) {
        console.log(&quot;성공&quot;);
      } else {
        console.log(&quot;실패&quot;);
      }
    });
  };</code></pre>
<blockquote>
<p>CampaignEndpoint.java</p>
<pre><code class="language-javascript">    @PostMapping(value = &quot;/{universeName}/**&quot;, headers = {&quot;command=AttachProduct&quot;})
    public void attachProduct(@RequestBody AttachProduct command, @PathVariable String universeName, HttpServletRequest httpServletRequest) {
        //
        final String path = httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
        final String bestMatchingPattern = httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
        String spaceName = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path).replace(PREFIX, &quot;&quot;);

       this.campaignService.attachProduct(command);
    }</code></pre>
</blockquote>
<pre><code class="language-javascript">
//CreateCampaign.java
public class CreateCampaign extends Command {
    //
    private transient String id;
    private String name;
    private Period period;
}

//Campaign.tsx
  const CreateCampaign = async () =&gt; {
    const response = await fetch(&quot;api/campaign/dsstore/dssdlabs/-/campaign&quot;, {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        service: &quot;campaign&quot;,
        command: &quot;CreateCampaign&quot;,
      },
      body: JSON.stringify({
        name: &quot;name&quot;,
        period: {
          from: 111,
          to: 111,
        },
      }),
    });
    if (response.status === 200) {
      console.log(&quot;response.body: &quot;, response.body);
    } else {
      console.log(response);
    }
  };</code></pre>
<blockquote>
<p>CampaignEndpoint.java</p>
<pre><code class="language-javascript">    @PostMapping(value = &quot;/{universeName}/**&quot;, headers = {&quot;command=CreateCampaign&quot;})
    public String createCampaign(@RequestBody CreateCampaign command, @PathVariable String universeName, HttpServletRequest httpServletRequest) {
        //
        final String path = httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
        final String bestMatchingPattern = httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
        String spaceName = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path).replace(PREFIX, &quot;&quot;);
</code></pre>
</blockquote>
<pre><code>    return this.campaignService.createCampaign(command);
}</code></pre><pre><code class="language-javascript">
//DetachProduct.java
public class DetachProduct extends Command {
    //
    private String campaignId;
    private String productNo;
}

//Campaign.tsx
  const DetachProduct = () =&gt; {
    fetch(&quot;api/campaign/dsstore/dssdlabs/-/campaign&quot;, {
      method: &quot;DELETE&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        service: &quot;campaign&quot;,
        command: &quot;DetachProduct&quot;,
      },
      body: JSON.stringify({
        campaignId: &quot;campaignId&quot;,
        productNo: &quot;productNo&quot;,
      }),
    });
  };</code></pre>
<blockquote>
<p>CampaignEndpoint.java</p>
<pre><code class="language-javascript">    @DeleteMapping(value = &quot;/{universeName}/**&quot;, headers = {&quot;command=DetachProduct&quot;})
    public void detachProduct(@RequestBody DetachProduct command, @PathVariable String universeName, HttpServletRequest httpServletRequest) {
        //
        final String path = httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
        final String bestMatchingPattern = httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
        String spaceName = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path).replace(PREFIX, &quot;&quot;);
</code></pre>
</blockquote>
<pre><code>    this.campaignService.detachProduct(command);
}</code></pre><pre><code class="language-javascript">
//UpdateCampaign.java
public class UpdateCampaign extends Command {
    //
    private String id;
    private String name;
    private Period period;
}

//Campaign.tsx
  const UpdateCampaign = () =&gt; {
    fetch(&quot;api/campaign/dsstore/dssdlabs/-/campaign&quot;, {
      method: &quot;PUT&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        service: &quot;campaign&quot;,
        command: &quot;UpdateCampaign&quot;,
      },
      body: JSON.stringify({
        campaignId: &quot;campaignId&quot;,
        productNo: &quot;productNo&quot;,
      }),
    });
  };</code></pre>
<blockquote>
<p>CampaignEndpoint.java</p>
<p>   <code>??????????????????</code></p>
</blockquote>
<pre><code class="language-javascript">
//QueryCampaign.java
public class QueryCampaign extends Query {
    //
    private transient String campaignId;
}


//Campaign.tsx
  const QueryCampaign = () =&gt; {
    fetch(&quot;api/campaign/dsstore/dssdlabs/-/campaign?campaignId=${&#39;1&#39;}&quot;, {
      method: &quot;GET&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        service: &quot;campaign&quot;,
        query: &quot;QueryCampaign&quot;,
      },
      // body: JSON.stringify({
      //   campaignId: campaignId,
      //   productNo: productNo,
      // }),
    });
  };
};

export default Campaign;
</code></pre>
<blockquote>
<p>CampaignEndpoint.java</p>
<pre><code class="language-javascript">    @GetMapping(value = &quot;/{universeName}/**&quot;, headers = {&quot;query=QueryCampaign&quot;})
    public Campaign queryCampaign(@RequestParam String campaignId, @PathVariable String universeName, HttpServletRequest httpServletRequest) {
        //
        final String path = httpServletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
        final String bestMatchingPattern = httpServletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
        String spaceName = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path).replace(PREFIX, &quot;&quot;);
</code></pre>
</blockquote>
<pre><code>    QueryCampaign query = new QueryCampaign(campaignId);
    return this.campaignService.query(query);
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[(ing)webpack]]></title>
            <link>https://velog.io/@heejeen-choi/ingwebpack</link>
            <guid>https://velog.io/@heejeen-choi/ingwebpack</guid>
            <pubDate>Mon, 24 Apr 2023 10:03:52 GMT</pubDate>
            <description><![CDATA[<h3 id="1-웹팩">1. 웹팩?</h3>
<ul>
<li>최신 프론트엔드 프레임워크에서 가장 많이 사용되는 모듈번들러.</li>
<li>웹팩에서 모듈이란, 웹 애플리케이션을 구성하는 모든 자원을 말한다.</li>
<li>HTML, CSS JavaScript, Images, Font 등 파일 하나하나가 전부 모듈.</li>
<li>즉, 웹팩은 모듈을 번들링 해주는 모듈번들러.</li>
</ul>
<h3 id="2-모듈번들링">2. 모듈번들링?</h3>
<ul>
<li>웹 애플리케이션을 구성하는 몇십,몇백개의 자원들을 하나의 파일로 병합 및 압축 해주는 동작</li>
</ul>
<h3 id="3-웹팩-쓰게-된-이유">3. 웹팩 쓰게 된 이유</h3>
<pre><code class="language-javascript">1. 파일 단위의 자바스크립트 모듈 관리의 필요성
2. 웹개발 작업 자동화 도구
3. 웹 애플리케이션의 빠른 로딩 속도와 높은 성능</code></pre>
<ul>
<li>파일단위의 자바스크립트 모듈 관리의 필요성</li>
<li>자바스크립트의 변수 유효 범위는 기본적으로 전역 범위를 가진다. 이때문에 서로 다른 모듈에서 같은 이름의 변수를 사용</li>
</ul>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에 적용한 SOLID원칙]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-React%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%9C-SOLID%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-React%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%9C-SOLID%EC%9B%90%EC%B9%99</guid>
            <pubDate>Mon, 17 Apr 2023 01:25:09 GMT</pubDate>
            <description><![CDATA[<h2 id="react에-적용한-solid-원칙">React에 적용한 SOLID 원칙</h2>
<h3 id="1-단일책임원칙-single-responsibility-principle">1. 단일책임원칙 Single Responsibility Principle</h3>
<ul>
<li>함수나 클래스는 한가지 일만 수행해야 한다. 이는 리액트의 컴포넌트도 마찬가지. 컴포넌트 하나가 수행하는 일은 하나인 것이 이상적.</li>
</ul>
<pre><code class="language-javascript">import React, {useEffect, useReducer, useState} from &quot;react&quot;;


const initialState = {
  isLoading: true
};

//COMPLEX STATE MANAGEMENT
function reducer(state, action) {
  switch (action.type) {
    case &#39;LOADING&#39; :
      return {isLoading: true}
    case &#39;FINISHED&#39; :
      return {isLoading: false}
    default:
      return state;
  }
}

export const SingleResponsibilityPrinciple = () =&gt; {

  const [users, setUsers] = useState([])
  const [filteredUsers, setFilteredUsers] = useState([])
  const [state, dispatch] = useReducer(reducer, initialState);

  const showDetails = (userId) =&gt; {
    const user = filteredUsers.find(user =&gt; user.id === userId)
    // 배열의 특정 값 찾기 : find(), filter()
    // 1) find() : `arr.find(callback(element[, index[,array]])[, thisArg])`
    //               배열에서 특정 값을 찾는 조건을 콜백함수를 통해 전달하여, 조건에 맞는 값 중 첫번째 값을 리턴한다. 만약, 배열에 조건을 만족하는 값이 없으면 undefined를 리턴.
    //               - 파라미터 callback(element, index, array)함수 : 조건을 비교할 callback 함수이고, 3개의 파라미터가 전달 된다. 콜백함수에서 사용자가 테스트할 조건을 정의하고, 만약 배열의 값이 조건에 부합하여 true를 리턴하면, 해당 배열의 값이 find() 함수의 리턴값이 됨. 조건에 부합하는 값을 찾으면 그 이휴의 배열값은 테스트되지 않는다. (element: 현재 처리 중인 배열의 element, index: 현재 처리 중인 배열의 index(optional), array: find()가 호출된 배열(optional))
    //               - thisArg(optional) : callback을 실행할 때 this로 사용할 객체
    //               - 리턴값 : callback함수에 정의한 조건에 부합하는 배열의 첫번째 값을 리턴한다. 조건에 부합하는 배열값이 없을 경우 undefined 리턴. (**참조: callback함수의 조건에 부합하는 배열의 첫번째 index값을 알아내기 위해서는 findIndex()함수 사용한다.)
    // const arr = [{naem: &#39;apple&#39;, price: 1000}, {name: &#39;lemon&#39;, price: 2000}, {name: &#39;melon&#39;, price: 3000}];
    // const isApple = (element) =&gt; { if(element.name === &#39;apple&#39;) { return (true;)}}    ▶︎2. isApple()함수는 파라미터로 입력받은 객체(element)의 name이 &#39;apple&#39;이면 true를 리턴한다.
    // const apple = arr.find(isApple);      ▶︎1. find()함수에 isApple()이라는 callback함수를 전달했다. ▶︎3. find()함수는 파라미터로 전달된 callback함수가 true를 리턴하면 해당 배열의 값을 리턴하고, 더이상 나머지 배열의 값은 callback함수로 전달하지 않는다.
    // console.log(apple.name); //apple   ▶︎4. 결국 find()함수는 callback함수에 정의 된 조건에 부합하는 배열의 첫번째 값을 리턴하는 것이고, 해당 예제에서 find()함수는 arr배열의 여러 객체 중, name === &#39;apple&#39;인 arr[0]의 값을 리턴했음.
    // console.log(apple.price); //1000


    // 2) filter() : find()는 특정조건에 부합하는 배열의 첫번째값만 리턴인 반면, filter()는 모든 값을 배열형태로 리턴한다.
    // const arr = [{naem: &#39;apple&#39;, price: 1000}, {name: &#39;lemon&#39;, price: 2000}, {name: &#39;apple&#39;, price: 3000}];
    // const apples = arr.filter(isApple);   ▶︎1. filter()함수는 arr배열에서 object의 name === &#39;apple&#39;인 모든 객체를 찾아서 새로운 배열로 생성하여 리턴한다.
    // console.log(apples.length);  //2    ▶︎2. filter()함수가 리턴하는 apples는 length가 2인 배열이고, 배열의 2개의 값은 arr[0], arr[2]의 값과 같다. 
    // console.log(apples[0].name + &#39;,&#39; + apples[0].price)  //apple, 1000
    // console.log(apples[1].name + &#39;,&#39; + apples[1].price)  //apple, 3000
    alert(user.contact)
  }

  //REMOTE DATA FETCHING
  useEffect(() =&gt; {
    dispatch({type: &#39;LOADING&#39;})
    fetch(&#39;https://jsonplaceholder.typicode.com/users&#39;)
        .then(response =&gt; response.json())
        .then(json =&gt; {
            dispatch({type: &#39;FINISHED&#39;})
              setUsers(json)
        })
  }, [])

  //PROCESSING DATA
  useEffect(() =&gt; {
    const filteredUsers = users.map(user =&gt; {
        return {
          id: user.id,
          name: user.name,
          contact: `${user.phone}. ${user.email}`
        };
    });
    setFilteredUsers(filteredUsers)
  }, [users])

  // COMPLEX UI RENDERING
  return (
    &lt;&gt;
      &lt;div&gt; Users List &lt;div&gt;
      &lt;div&gt; Loading state: {state.isLoading ? &#39;Loading&#39; : &#39;Success&#39;} &lt;div&gt;
      {users.map(user =&gt; {
            return (
               &lt;div key={user.id} onClick={() =&gt; showDetails(user.id)}&gt;
                   &lt;div&gt; {user.name} &lt;div&gt;
                   &lt;div&gt; {user.email} &lt;div&gt;
               &lt;div&gt;
          )
      })}
    &lt;&gt;
  )
}


            - 이 컴포넌트는 하는 일이 매우 많다.
                1. 원격 데이터 가져오기
                2. 데이터 필터링하기
                3. 복잡한 상태를 관리하기
                4. 복잡한 UI 기능
            - 하나의 컴포넌트에서 너무 많은 일을 하는 것은 좋은 코드가 아니다.
            - 각각을 분리해서 각 파일의 양을 줄이고 재사용할 수 있도록 분리해 주는 것이 단일 책임 원칙을 따르는 것이다.1</code></pre>
<h3 id="2-개방-폐쇄-원칙-open-closed-principle">2. 개방 폐쇄 원칙 Open Closed Principle</h3>
<ul>
<li>소프트웨어 엔티티는 확장을위해 열려 있어야하지만, 수정을 위해 닫혀야 한다. 즉, 소스코드를 수정하지 않고 확장 가능</li>
</ul>
<pre><code class="language-javascript">* 리액트에서는 어떻게 사용?

import React, { FC } from &quot;react&quot;;

interface InputProps {
  value: string;
  onChange: React.ChangeEventHandler&lt;HTMLInputElement&gt;;
  className?: string;
  label: string;
}
const Input: FC&lt;InputPrps&gt; = ({label, ...props}) =&gt; {
  return (
    &lt;div&gt;
      &lt;label&gt; {label} &lt;label&gt;
      &lt;input {...props} /&gt;
    &lt;div&gt;
  )
}
export default Input

        - 위과 같은 컴포넌트는 개발을 하다보면 props가 늘어날 수 있다.


import React, { FC } from &quot;react&quot;;

interface InputProps {
  inputRef: React.RefObject&lt;HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement&gt;;
  className?: string;
  containerClassName?: string;
  formGroupClassName?: string;
  type?: &quot;text&quot; | &quot;search&quot; | &quot;password&quot; | &quot;email&quot; | &quot;textarea&quot; | &quot;select&quot;;
  textareaRows?: number;
  name?: string;
  value: string;
  isTouched: boolean;
  onChange: React.ChangeEventHandler&lt;HTMLInputElement | HTMLTextAreaElement | HTMLAreaElement&gt;;
  onInput: React.FormEventHandler&lt;HTMLInputElement | HTMLTextAreaElement&gt;;
  onBlur: React.FocusEventHandler&lt;HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement&gt;;
  onInvalid: React.FormEventHandler&lt;HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement&gt;;
  placeholder?: 
  id: string;
  maxLength?: number;
  hasClearButton?: boolean;
  addonLeft?: React.ReactNode;
  addonRight? React.ReactNode;
  addonLeftClassName?: string;
  addonRightClassName?: string;
  helpText?: React.ReactNode;
  isShortInput?: boolean;
  isLoading?: boolean;
}

const Input: FC&lt;InputProps&gt; = ({
  className,
  containerClassName,
  formGroupClassName,
  type = &quot;text&quot;,
  textareaRows,
  name,
  value = &quot;&quot;,
  isTouched,
  onChange,
  onInput,
  onClearClick,
  onBlur,
  onInvalid,
  placeholder,
  id,
  maxLength,
  hasClearButton,
  label,
  labelStyle,
  addonLeft,
  addonRight,
  addonLeftClassName,
  addonRightClassName,
  helpText,
  isShortInput,
  isLoading,
  ...validationProps
}) =&gt; {
  return (
    &lt;div&gt;
      &lt;input /&gt;
      (...)
    &lt;div&gt;
  )
}
        - (물론, props를 이렇게 많이 넘겨줘야 한다면 잘못 짠 코드이다, 예시용)
        - 확장 가능하지만 수정하지 않는 컴포넌트를 만드는 방법은 다음과 같다,


&lt;InputContainer ...props&gt;
  &lt;InputLabel ...props /&gt;
  &lt;InputLeftAddon ...props /&gt;
  &lt;InputControl ...props /&gt;
  &lt;InputRightAddon ...props /&gt;
&lt;InputContainer&gt;</code></pre>
<h3 id="3-liskov-대체원리-liskov-principle">3. Liskov 대체원리 Liskov Principle</h3>
<ul>
<li>shape 라는 슈퍼클래스가 있고 이로부터 파생된 서브클래스 T 와 S 가 있다는 가정,</li>
<li>Liskov 대체원리란, T와 S가 같은 슈퍼클래스를 공유하는한, T 와 S는 서로 교체할 수 있어야 한다는 것</li>
</ul>
<pre><code class="language-javascript">class Shape {
  render() {
    throw new Error(&quot;Cannot render &#39;Shape&#39;&quot;);
  }
}
class Square extends Shape {
  constructor (height, width) {
    this.height = height;
    this.width = width;
  }
  render() {
    //psuedocode
    Canvas2d
        .drawRect(0, 0, height, width)
        .fill(&quot;white&quot;)
        .border(&quot;1px&quot;, &quot;black&quot;);
    console.log(`Rendering Square (0, 0, ${height}, ${width})`)
  }
}
class Circle extends Shape {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  render() {
    //psuedocode
    Canvas2d
        .drawCircle(0, 0, height, width)
        .fill(&quot;white&quot;)
        .border(&quot;1px&quot;, &quot;black&quot;);
    console.log(`Rendering Circle (0, 0, ${heigh}, ${width})`)
  }
}
class ShapeRenderer {
  constructor(shape) {
    this.shape = shape;
  }
  setShape(shape) {
    this.shape = shape;
  }
  render() {
    this.shape.render();
  }
}
//Create our instances of subtype &#39;Shape&#39;
const mySquare = new ShapeRender(mySquare);
myRenderer.render();
myRenderer.setShape(circle);
myRenderer.render();
        - 슈퍼클래스 Shape로부터 Square 와 Circle 을 만들고, Renderer 에서 인스턴스를 교체함.
        - 둘 다 같은 슈퍼클래스로부터 파생됐기 때문.</code></pre>
<h3 id="4-인터페이스-분리원칙">4. 인터페이스 분리원칙</h3>
<ul>
<li>필요하지않은 것에 의존하면 안된다 즉, 자신이 사용하지 않는 기능(인터페이스)에는 영향을 받지않아야함.</li>
</ul>
<pre><code class="language-javascript">* shape 기능을 유지하면서 하위타입으로 확장할 수 잇기 때문에 Liskov 원칙의 예

//General purpose interface
interface Shape {
  render(): void;
  area(): number;
  radius(): number;
}
        - 공통적으로 사용할 수 있는 객체 Shape 가 있고 이 객체 안의 프로퍼티들을 예제처럼 작성했다고 가정, 만약에 사각형이나 삼각형 객체가 필요하다면 반지름 값인 radius가 없으므로 인터페이스를 분리할 필요가 있다.

interface Shape {
  render(): void;
  area(): number;
}
interface Circle extends Shape {
  radius(): number;
}
        - 이렇게 인터페이스를 분리하면 사각형객체에는 필요없는 radius값을 필수적으로 구현하지않게됨.</code></pre>
<h3 id="5-의존성-역전-원칙-dependency-inversion-principle">5. 의존성 역전 원칙 Dependency Inversion Principle</h3>
<ul>
<li>고수준모듈이 저수준모듈의 구현에 의존해서는 안된다.</li>
<li>즉, 어플리케이션이 특정한 함수나 인스턴스가 아닌 인터페이스, 또는 추상화에 의존해야한다.</li>
<li>하지만 리액트에서는 n개의 component를 가지고 하나의 결과를 내기때문에 쉽지않음</li>
<li>의존성반전을 리액트에서 적용한다면, <pre><code class="language-javascript">const Foo = ({ someVal }) =&gt; {
return (
  &lt;div&gt;
    {someFilterFn(someval)}
  &lt;div&gt;
)
}
      - someFilterFn()함수는 외부의 함수, 클래스 혹은 모듈에서 받았다고 가정
      - 그렇다면, 자식 컴포넌트는 부모로부터 받은 someVal 과 외부로 받아온 someFilterFn 두가지이다.
      - 이 경우, filtering 함수를 부모로부터 받아와 자식컴포넌트의 종속성을 줄일 수 있다. 이를 수정하려면,
</code></pre>
</li>
</ul>
<p>const Foo = ({ callback, someVal }) =&gt; {
  return (
    <div>
      {callback(someval)}
    <div>
  )
}
        - 필터를 수행하는 로직이 상위 컴포넌트 내에 캡슐화 되어 있기 때문에 구성요소에 대한 테스트가 단순화된다.</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[(작성중) running TSC: Part2_ class]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-Part2-class</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-Part2-class</guid>
            <pubDate>Thu, 13 Apr 2023 08:46:37 GMT</pubDate>
            <description><![CDATA[<p>** 클래스메서드 **</p>
<ul>
<li>타입스크립트는 독립함수를 이해하는 것과 동일한 방식으로 메서드를 이해한다.</li>
<li>매개변수 타입에 타입이나 기본값을 지정하지 않으면 any타입을 기본으로 가진다.</li>
<li>메서드를 호출하려면 허용 가능한 수의 인수가 필요하고, 재귀함수가 아니라면 대부분 반환타입 유추가 가능하다.</li>
</ul>
<pre><code class="language-javascript">//1.
class Greeter {
  greet(name: string) {
    console.log(&#39;${name}, do your stuff!&#39;);
  }
}
new Greeter().greet(&#39;Miss Frizzle&#39;);
new Greeter().greet();
// Error: Expected 1 arguments, but got 0.
    - string 타입의 단일필수 매개변수를 갖는 greet 클래스 메서드를 가진 Greeter 클래스를 정의함.

//2.
class Greeted {
  constructor(message: string) {
    console.log(&#39;As I always say: ${message}!&#39;)
  }
}
new Greeted(&#39;take chances, make mistakes, get messy&#39;);
new Greeted();
//Error: Expected 1 arguments, but got 0.
    - 클래스 생성자constructor는 매개변수와 관련하여 전형적인 클래스 메서드처럼 취급된다
    - 타입스크립트는 메서드 호출 시 올바른 타입의 인수가 올바른 수로 제공되는지 확인하기 위해 타입검사를 수행함.
    - Greeted 생성자는 message: string으로 매개변수가 제공되어야 한다.
</code></pre>
<p>** 클래스속성 **</p>
<ul>
<li>타입스크립트에서 클래스의 속성을 읽거나 쓰려면 클래스에 명시적으로 선언해야 한다.</li>
<li>클래스속성은, 인터페이스와 동일한 구문을 사용해 선언한다.</li>
<li>클래스 속성 이름 뒤에는 선택적으로 타입 애너테이션이 붙음.</li>
<li>타입스크립트는 생성자 내의 할당에 대해서 그 멤버가 클래스에 존재하는 멤버인지 추론하려고 시도하지 않는다.<pre><code class="language-javascript">//1.
class FieldTrip {
destination: string;
constructor(destination: string) {
  this.destination = destination;
  console.log(&#39;We&#39;re going to ${this.destination}!&#39;);
  this.nonexistent = destination;
  //Error: Property &#39;nonexistent&#39; does not exist on type &#39;FieldTrip&#39;.
}
}
  - destination은 string으로 명시적선언이 되어있어서 FieldTrip 클래스 인스턴스에 할당되고 접근할 수 있다.
  - 클래스가 nonexistent 속성을 선언하지 않았기 떄문에 생성자에서 this.nonexistent 할당은 허용되지 않음.
</code></pre>
</li>
</ul>
<p>//2.
const trip = new FieldTrip(&#39;planetarium&#39;);
trip.destination;
trip.nonexistant;
//Error: Property &#39;nonexistant&#39; does not exist on type &#39;FieldTrip&#39;
    - 클래스 속성을 명시적으로 선언하면 타입스크립트는 클래스 인스턴스에서 무엇이 허용되고 안되는지 빠르게 이해할 수 있따.
    - 나중에 클래스 인스턴스가 사용될 때, 코드가 trip.nonexistant 처럼 클래스인스턴스에 존재하지 않는 멤버에 접근하려고 시도하면 타입스크립트는 타입오류를 발생.</p>
<pre><code>
** 함수속성 **
- 몇가지 자바스크립트 메서드 스코프와 기본구문을 알아야한다.
- 자바스크립트에는 클래스의 멤버를 호출가능한 함수로 선언하는 두가지 구문이 있다.
- `인스턴스` : 클래스의 복제본
    - 여러상태의 클래스가 동시에 필요할 때는 클래스 앞에 new를 붙여서 클래스의 복제본을 만들어서 서로 다른 상태를 유지할 수 있다.
```javascript
//1.
class WithMethod {
  myMethod() {}
}
new WithMethod().myMethod === new WithMethod().myMethod;
    - myFunction(){} 과 같이 멤버 이름 뒤에 괄호를 붙이는 메서드 접근방식을 보면,
    - 메서드 접근방식은 함수를 클래스 프로토타입에 할당하므로 모든 클래스 인스턴스는 동일한 함수 정의를 사용한다.
    - WithMethod 클래스는 모든 인스턴스가 참조할 수 있는 myMethod 메서드를 선언한다.

//2.
class WithProperty {
  myProperty: () =&gt; {}
}
new WithMethod().myProperty === new WithMethod().myProperty;    //false
    - 값이 함수인 속성을 선언하는 방식도 있다.
    - 이렇게 하면, 클래스의 인스턴스 당 새로운 함수가 생성되며, 항상 클래스 인스턴스를 가리켜야 하는 화살표함수에서 this 스코프를 사용하면 클래스 인스턴스 당 새로운 함수를 생성하는 시간과 메모리 비용측면에서 유용
    - WithProperty 클래스는 이름이 myProperty인 단일 속성을 포함하며 각 클래스 인스턴스에 대해 다시 생성되는 () =&gt; void 타입이다.

//3.
class WithPropertyParameters {
  takesParameters = (input: boolean) =&gt; input ? &#39;yes&#39; : &#39;no&#39;;
}
const instance = new WithPropertyParameters();
instance.takesParameters(true);
instance.takesParameters(123);
//Error: Argument of type &#39;number&#39; is not assignable to parameter of type &#39;boolean&#39;
    - 함수 속성에는 클래스 메서드와 독립함수의 동일한 구문을 사용해 매개변수와 반환 타입을 지정할 수 있다.
    - 결국 함수속성은 클래스 멤버로 할당된 값이고, 그 값은 함수이다.
    - WithPropertyParameters 클래스는 타입이 (input: boolean) =&gt; string 인 takesParameters 속성을 가진다.</code></pre><p>** 초기화검사 **</p>
<ul>
<li>엄격한 컴파일러 설정이 활성화 된 상태에서 타입스크립트는 undefined 타입으로 선언된 각 속성이 생성자에서 할당되었는지 확인한다.</li>
<li>이와 같은 엄격한 초기화 검사는 클래스 속성에 값을 할당하지 않는 실수를 예방가능.<pre><code class="language-javascript">//1.
class WithValue {
immediate = 0;
later: number;    //constructor에서 할당
mayBeUndefined: number | undefined;    //undefined가 되는 것이 허용됨
unused: number;
//Error: Property &#39;unused&#39; has no initializer and is not definitely assigned in the constructor.
constructor() {
  this.later = 1;
}
}
  - WithValue 클래스는 unused 속성에 값을 할당하지 않았고, 타입스크립트는 이 속성을 타입오류로 인식.
</code></pre>
</li>
</ul>
<p>//2.
class MissingInitializwr {
  property: string;
}
new MissingInitializer().property.length;
// TypeError: Cannot read property &#39;length&#39; of undefined
    - 엄격한 초기화 검사가 없다면, 비록 타입 시스템이 undefined 값에 접근할 수 없다고해도 클래스 인스턴스는 undefined 값에 접근할 수 있다.
    - 엄격한 초기화검사가 수행되지 않으면 올바르게 컴파일 되지만 결과 자바스크립트는 런타임 시 문제가 발생한다.</p>
<pre><code>
** 확실하게 할당 된 속성 **
- 엄격한 초기화 검사가 유용한 경우가 대부분이지만 클래스 생성자 다음에 클래스 속성을 의도적으로 할당하지 않는 경우가 있기도 하다.
- 엄격한 초기화검사를 적용하면 안되는 속성인 경우에는 이름 뒤에 ! 를 추가해 검사를 비활성화 하도록 설정한다.
- 이렇게하면 타입스크립트에 속성이 처음 사용되기 전에 undefined 값이 할당된다.
```javascript
class ActivitiesQueue {
  pending!: string[];
  initialize(pending: string[]) {
    this.pending = pending;
  }
  next() {
    return (
      this.pending.pop();
    )
  }
}
const activities = new ActivitiesQueue();
activities.initializer([&#39;eat&#39;, &#39;sleep&#39;, &#39;learn&#39;])
activities.next();
    - ActivitiesQueue 클래스는 생성자와는 별도로 여러번 다시 초기화될 수 있으므로 pending 속성은 !와 함께 할당되어야 한다.
    - 클래스 속성에 대해 엄격한 초기화 검사를 비활성화하는 것은 종종 타입 검사에는 적합하지 않은 방식으로 코드가 설정된다는 신호이다.
    - ! 어서션을 추가하고 속성에 대한 타입 안정성을 줄이는대신 클래스를 리팩터링해서 어서션이 더이상 필요하지 않도록하자.
</code></pre><p>** 선택적속성 **</p>
<ul>
<li>인터페이스와 마찬가지로 클래스는 선언된 속성이름 뒤에 ?를 추가해 속성을 옵션으로 선언한다.</li>
<li>선택적속성은 | undefined 를 포함하는 유니언타입과 거의 동일하게 작동한다.</li>
<li>엄격한 초기화검사는 생성자에서 선택적 속성을 명시적으로 설정하지 않아도 문제되지 않는다.<pre><code class="language-javascript">class MissingInitializer {
property?: string;
}
new MissingInitializer().property?.length;
new MissingInitializer().property.length;
//Error: Objectt is possibly &#39;undefined&#39;.
  - MissingInitializer 클래스는 property를 옵션으로 정의했으므로 엄격한 속성초기화검사와 관계없이 클래스 생성자에서 할당하지않아도 된다.
</code></pre>
</li>
</ul>
<pre><code>
** 읽기전용속성 **
- 인터페이스와 마찬가지로 클래스도 선언된 속성 이름 앞에 readonly 키워드를 추가해 속성을 읽기전용으로 선언한다.
- readonly 키워드는 타입시스템에만 존재하며 자바스크립트로 컴파일할 때 삭제된다.

```javascript
//1.
class Quote {
  readonly text: string;
  constructor(text: string) {
    this.text;
  }
  emphasize() {
    this.text += &#39;!&#39;;
    //Error: Cannot assign to &#39;text&#39; because it is a read-only property
  }
}
const quote = new Quote(
  &#39;There is a brilliant child locked inside every student&#39;
)
Quote.text = &#39;Ha!&#39;
//Error: Cannot assign to &#39;text&#39; because it is a read-only property.
    - readonly로 선언된 속성은 선언된 위치 또는 생성자에서 초깃값만 할당할 수 있다.
    - 클래스 내의 메서드를 포함한 다른 모든 위치에서 속성은 읽을 수만 있고, 쓸 수는 없다.
    - Quote 클래스의 text 속성은 생성자에서는 값이 지정되지만 다른 곳에서 값을 지정하려하면 오류뜬다.
    - npm 패키지로 게시한 코드를 사용하는 외부인이 readonly제한자를 존중하지 않을 수 있다. 특히, 자바스크립트를 작성 중이고 타입검사를 하지 않는 사용자라면 더욱 그러함. 
    - 진정한 읽기전용 보호가 필요하다면 # private 필드나 get() 함수속성 사용을 고려한다.

//2.
class RandomQuote {
  readonly explicit: string = &#39;Home is the nicest word there is.&#39;;
  readonly implicit = &#39;Home is the nicest word there is.&#39;
  constructor() {
    if (Math.random () &gt; 0.5) {
      this.explicit = &#39;We start learning the minute we&#39;re born.&#39;;
      this.implicit = &#39;We start learning the minute we&#39;re born.&#39;;
      //Error: Type &#39;&#39;We start learning the minute we&#39;re born.&#39;&#39; is not assignable to type &#39;&#39;Home is the nicest word there is.&#39;&#39;
    }
  }
}
const quote = new RandomQuote();
quote.explicit;        //타입: string
quote.implicit;        //타입: &#39;Home is the nicest word there is.&#39;

    - 원시타입의 초기값을 갖는 readonly로 선언된 속성은 다른 속성과 조금 다르다.
    - 이런 속성은 더 넓은 원시값이 아니라 값의 타입이 가능한한 좁혀진 리터럴타입으로 유추됨.
    - 타입스크립트는 값이 나중에 변경되지 않는다는 것을 알기 때문에 더 공격적인 초기타입 내로잉을 편하게 느낀다.
    - const 변수가 let 변수보다 더 좁은 타입을 갖는 것과 유사함.
    - 위 예제코드에서, 클래스 속성은 처음에는 모두 문자열리터럴로 선언되므로 둘 중 하나를 string 으로 확장하기 위해서는 타입애너테이션이 필요하다.
    - 속성의 타입을 명시적으로 확장하는 작업이 자주 필요하진 않다.
    - 그럼에도 불구하고 RandomQuote 에서 등장하는 생성자의 조건부 로직처럼 경우에 따라 유용할 수 있음
</code></pre><p>** 타입으로서의 클래스 **</p>
<ul>
<li>타입시스템에서의 클래스는 클래스선언이 런타임값(클래스자체)과 타입 애너테이션에서 사용할 수 있는 타입을 모두 생성한다는 점에서 상대적으로 독특하다.<pre><code class="language-javascript">//1.
class Teacher {
sayHello() {
  console.log(&#39;Take chances, make mistakes, get messy!&#39;);
}
}
const teacher: Teacher;
teacher = new Teacher();
teacher = &#39;Wahoo!&#39;
//Error: Type &#39;string&#39; is not assignable to type &#39;Teacher&#39;.
  - Teacher클래스의 이름은 teacher 변수에 주석을 다는데 사용된다.
  - teacher 변수에는 Teacher 클래스의 자체인스턴스처럼 Teacher클래스에 할당할 수 있는 값만 할당해야함.
</code></pre>
</li>
</ul>
<p>//2.
class SchoolBus {
  getAbilities() {
    return (
      [&#39;magic&#39;, &#39;shapeshifting&#39;]
    )
  }
}
const withSchoolBus = (bus: SchoolBus) =&gt; {
  console.log(bus.getAbilities());
}
withSchoolBus(new SchoolBus());
withSchoolBus({
  getAbilities: () =&gt; [&#39;transmogrification&#39;]
});
withSchoolBus({
  getAbilities: () =&gt; 123
  //Error: Type &#39;number&#39; is not assignable to type &#39;string[]&#39;
})
    - 타입스크립트는 클래스의 동일한 멤버를 모두 포함하는 모든 걕체타입을 클래스에 할당할 수 있는 것으로 간주한다.
    - 타입스크립트의 구조적 타이핑이 선언되는 방식이 아니라 객체의 형태만 고려하기 때문
    - withSchoolBus 는 SchoolBus 타입의 매개변수를 받는다.
    - 매개변수로 SchoolBus 클래스인스턴스처럼 타입이 () =&gt; string[]인 getAbilities 속성을 가진 모든 객체를 할당할 수 있다.
    - 대부분의 실제 코드에서 개발자는, 클래스 타입을 요청하는 위치에 객체의 값을 전달하지 않는다. 이러한 구조적인 확인 동작은 예상하지 못한것처럼 보일 수 있지만 자주 나타나지는 않음.</p>
<pre><code>
** 클래스와 인터페이스 **
- 타입스크립트는 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 추가함으로써 클래스의 해당 인스턴스가 인터페이스를 준수한다고 선언할 수 있다.
- 이렇게 하면 클래스를 각 이터페이스에 할당할 수 있어야함을 타입스크립트에 나타낸다. 타입검사기에의해 모든 불일치에 대해 타입오류가 발생.
```javascript
//1.
interface Learner {
  name: string;
  study(hours: number): void;
}
class Student implements Learner {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  study(hours: number) {
    for (let i=0; i&lt;hours; i+=1) {
      console.log(&#39;...studying&#39;)
    }
  }
}
class Slacker implements Learner {
//Error: Class &#39;Slacker&#39; incorrectly implements interface &#39;Learner&#39;.
//    Property &#39;study&#39; is missing in type &#39;Slacker&#39; but required in type &#39;Learner&#39;.
//    name = &#39;Rocky&#39;;
}
    - Student 클래스는 name 속성과 study 메서드를 포함해 Learner 인터페이스를 올바르게 구현했지만,
    - Slacker 에는 study가 누락 됐으므로 타입오류가 발생.
    - 클래스에 의해 구현되는 인터페이스는 Leaner인터페이스에서 사용된 것처럼 인터페이스 멤버를 함수로 선언하기 위해 메서드 구문을 사용한다.


//2.
class Student implements Learner {
  name;
  //Error: Member &#39;name&#39; implicitly has an &#39;any&#39; type.
  study(hours) {
  //Error: Parameter &#39;hours&#39; implicitly has an &#39;any&#39; type.
  }
}
    - 인터페이스를 구현하는 것으로 클래스를 만들어도 클래스가 사용되는 방식은 변경되지 않는다.
    - 클래스가 이미 인터페이스와 일치하는 경우 타입스크립트의 타입검사기는 인터페이스의 인스턴스가 필요한 곳에서 해당 인스턴스를 사용할 수 있도록 허용한다.
    - 타입스크립트는 인터페이스에서 클래스의 메서드 또는 속성타입을 유추하지 않는다.
    - Slacker 에제에서 study(hours){} 메서드를 추가했다면 타입스크립트는 타입애너테이션을 지정하지 않는한 hours 매개변수를 암시적으로 any로 간주함.
    - 위의 예제는 다른형태로 구현한 Student 클래스는 멤버에 타입애너테이션을 제공하지 않기 때문에 암시적인 any 타입오류발생.
    - 인터페이스를 구현하는 것은 순전히 안정성검사를 위해서이다.
    - 모든 인터페이스 멤버를 클래스정의로 복사하지 않는다.
    - 대신, 인터페이스를 구현하면 클래스 인스턴스가 사용되는 곳에서 나중에 타입검사기로 신호를 보내고 클래스 정의에서 표면적인 타입오류가 발생한다. 변수에 초기값이 있더라도 타입애너테이션을 추가하는 것과 용도가 비슷함.
</code></pre><p>** 다중인터페이스구현 **</p>
<ul>
<li>타입스크립트의 클래스는 다중 인터페이스를 구현해 선언할 수 있다.</li>
<li>클래스에 구현된 인터페이스 목록은 인터페이스 이름 사이에 쉼표를 넣고, 개수 제한 없이 인터페이스를 사용할 수 있다.</li>
</ul>
<pre><code class="language-javascript">//1.
interface Graded {
  grades: number[];
}
interface Reporter {
  report: () =&gt; string;
}
class ReportCard implements Graded, Reporter {
  grades: number[];
  constructor(grades: number[]) {
    this.grades = grades;
  }
  report() {
    return (
      this.grades.join(&#39;,&#39;);
    )
  }
}
class Empty implements Graded, Reporter {}
// Error: Class &#39;Empty&#39; incorrectly implements interface &#39;Graded&#39;.
//    Property &#39;grades&#39; is missing in type &#39;Empty&#39; but required in type &#39;Grade&#39;.
// Error: Class &#39;Empty&#39; incorrectly implements interface &#39;Reporter&#39;.
//    Property &#39;report&#39; is missing in type &#39;Empty&#39; but required in type &#39;Reporter&#39;.
    - 두 클래스에서 모두 Graded를 구현하려면 grades 속성을 가져야 하고, Reporter를 구현하려면 report 속성을 가져야 한다.
    - Empty클래스에는 Graded와 Reporter 인터페이스를 제대로 구현하지 못했으므로 두가지 타입오류가 발생한다.


//2.
interface AgeIsANumber {
  age: number;
}
interface AgeIsNotANumber {
  age: () =&gt; string;
}
class AsNumber implements AgeIsANumber, AgeIsNotANumber {
  age = 0;
  // Error: Property &#39;age&#39; in type &#39;AsNumber&#39; is not assignable to the same property in base type &#39;AgeIsNotANumber&#39;. Type &#39;number&#39; is not assignable to type &#39;() =&gt; string&#39;.
}
class NotAsNumber implements AgeIsANumber, AgeIsNotANumber {
  age() {
    retrn (
      &quot;&quot;;
    )
  }
  //Error: Property &#39;age&#39; in type &#39;NotAsNumber&#39; is not assignable to the same property in base type &#39;AgeIsANumber&#39;. Type &#39;() =&gt; string&#39; is not assignable to type &#39;number&#39;.
}
    - 실제로 클래스가 한번에 두 인터페이스를 구현할 수 없도록 정의하는 인터페이스가 있을 수 있다.
    - 두개의 충돌하는 인터페이스를 구현하는 클래스를 선언하려고하면 클래스에 하나이상의 타입오류가 발생한다.
    - AgeIsNumber와 AgeIsNotNumber 인터페이스는 age 속성을 서로 다른 타입으로 선언한다.
    - AsNumber 클래스와 NotAsNumber 클래스 모두 두 인터페이스를 제대로 구현하지 못했음.
    - `두 인터페이스가 매우 다른 객체형태를 표현하는 경우에는 동일한 클래스로 구현하지 않아야한다.`
</code></pre>
<p>** 클래스확장 **</p>
<blockquote>
<ol>
<li>타입스크립트는 다른 클래스를 확장하거나 하위 클래스를 만드는 자바스크립트 개념에 타입검사를 추가한다.</li>
<li>먼저 기본클래스에 선언된 모든 메서드나 속성은 파생클래스라고도하는 하위클래스에서 사용가능.</li>
</ol>
</blockquote>
<pre><code class="language-javascript">class Teacher {
  teach() {
    console.log(&#39;The surest test of discipline is its absence.&#39;)
  }
}
class StudentTeacher extends Teacher {
  learn() {
    console.log(&#39;I cannot afford the luxury of a closed mind.&#39;)
  }
}
const teacher = new StudentTeacher();
teacher.teach();
teacher.learn();
teacher.other();
// Error: Property &#39;other&#39; does not exist on type &#39;StudentTeacher&#39;.
    - Teacher는 StudentTeacher 하위클래스의 인스턴스에서 사용할 수 있는 teach메서드를 선언한다.</code></pre>
<p>** 할당가능성 확장 **</p>
<ul>
<li>파생인터페이스가 기본인터페이스를 확장하는 것과 마찬가지로 하위클래스도 기본클래스의 멤버를 상속한다.</li>
<li>하위클래스의 인스턴스는 기본클래스의 모든 멤버를 가지므로 기본 클래스의 인스턴스가 필요한 모든 곳에서 사용가능.</li>
<li>기본클래스에 하위클래스가 가지고 있는 모든 멤버가 없으면 더 구체적인 하위클래스가 필요할 때 사용할 수 없다.<pre><code class="language-javascript">//1.
class Lesson {
subject: string;
constructor(subject: string) {
  this.subject = subject
}
}
class OnlineLesson extends Lesson {
url: string;
constructor(subject: string, url: string){
  super(subject);
  this.url = url;
}
}
let lesson: Lesson
lesson = new Lesson(&quot;coding&quot;);
lesson = new OnlineLesson(&quot;coding&quot;, &quot;oreilly.com&quot;);
</code></pre>
</li>
</ul>
<p>let online: OnlineLesson
online = new OnlineLesson(&quot;coding&quot;, &quot;oreilly.com&quot;)
online = new Lesson(&quot;coding&quot;)
// Error: Property &#39;url&#39; is missing in type &#39;Lesson&#39; but required in type &#39;OnlineLesson&#39;;</p>
<pre><code>

** 재정의 된 생성자 **
- 바닐라 자바스크립트와 마찬가지로 타입스크립트에서 하위클래스는 자체 생성자를 정의할 필요가 없다.
- 자체 생성자가 없는 하위 클래스는 암묵적으로 기본 클래스의 생성자를 사용한다.
- 자바스크립트에서 하위클래스가 자체 생성자를 선언하면 super 키워드를 통해 기본클래스생성자를 호출해야 한다. 
- 하위클래스 생성자는 기본클래스에서의 필요여부와 상관없이 모든 매개변수를 선언할 수 있다. 
- 타입스크립트의 타입검사기는 기본 클래스 생성자를 호출할 때 올바른 매개변수를 사용하는지 확인한다.

```javascript
class GradeAnnouncer {
  message: string;
  constructor(grade: number) {
    this.message = grade &gt;= 65 ? &quot;Maybe nest time ...&quot;: &quot;You pass!&quot;
  }
}
class PassingAnnouncer extends GradeAnnouncer {
  constructor() {
    super(100)
  }
}
class FailingAnnouncer extends GradeAnnouncer {
  constructor() {}
  //Error: Constructors for subclasses must contain a &#39;super&#39; call.
}
        - PassingAnnouncer의 생성자는 number 인수를 사용해 기본 클래스인 GradeAnnouncer의 생성자를 올바르게 호출하는 반면, FailingAnnouncer는 기본 생성자를 올바르게 호출하지 않아서 타입오류가 발생한다.
        - 자바스크립트 규칙에 따르면 하위 클래스의 생성자는 this 또는 super에 접근하기 전에 반드시 기본 클래스의 생성자를 호출해야한다.
        - 타입스크립트는 super()를 호출하기 전에 this 또는 super에 접근하려고 하는 경우 타입오류를 보고한다.</code></pre><pre><code class="language-javascript">- ContinuedGradesTally 클래스는 super()를 호출하기 전에 생성자에서 this.grades 를 잘못 참조한다.
class GradesTally {
  grades: number[] = [];
  addGrades(...grades: number[]) {
    this.grades.push(...grades)
  }
}</code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[(작성중 ) Sculptures : ■■■■■■■■□□□□]]></title>
            <link>https://velog.io/@heejeen-choi/Sculptures</link>
            <guid>https://velog.io/@heejeen-choi/Sculptures</guid>
            <pubDate>Thu, 13 Apr 2023 07:40:06 GMT</pubDate>
            <description><![CDATA[<h3 id="1-mathrandom---05">1. Math.random () &gt; 0.5</h3>
<ul>
<li><p>자바스크립트에서 배열을 무작위로 섞는 방법은 : sort() 메서드와, Math.random() 메서드를 이용하여 구현할 수 있다.</p>
<pre><code class="language-javascript">const shuffle = (array) =&gt; {
array.sort(() =&gt; Math.random()-0.5)
}
const arr = [1,2,3];
shuffle(arr);
// (3) [1,3,2]
//     0: 1
//   1: 3
//   2: 2
//   length: 3
//  [[Prototype]]: Array(0)</code></pre>
</li>
<li><p>이렇게 간단하게 무작위 함수 구현가능.</p>
</li>
<li><p>Math.random()-0.5 의 계산 결과는 양수나 음수 둘 중 하나기 때문에 정렬함수는 요소를 무작위로 재정렬할 수 있게 한다.</p>
</li>
</ul>
<pre><code>

### 2. assertion
- Asser
```javascript
</code></pre><h3 id="3-narrowing">3. narrowing</h3>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ds AS-IS]]></title>
            <link>https://velog.io/@heejeen-choi/ds-AS-IS</link>
            <guid>https://velog.io/@heejeen-choi/ds-AS-IS</guid>
            <pubDate>Thu, 13 Apr 2023 03:58:40 GMT</pubDate>
            <description><![CDATA[<ol>
<li>정기구독관리자
PDS배송정보; 과거 동아관련 지사... 지금안씀
주문.발송 옵션정보 : 포장에 대한것. 배송/포장 방법 배송상태나타내기
고객번호 (사실 매체구독번호) : 등록할때마다 변하는 숫자.
구독구분<ul>
<li>특판.... 등</li>
<li>수기면 전화로 꽂히고</li>
<li>웹이면 채널..?</li>
<li>에 따라서 부서명이 바뀜??
교차판매 는 사용하지않는 
진행/종료사유 : 티엠쪽에서 아웃바운드할때
정기구독인증정보 : 정기구독 신청한 고객이 인증했는지를 확인. 웹/모바일에서 결제한 고객은 기본적으로 등록이 되어있다. 이거 가끔 안보일때는 등록버튼 눌러주면 보이기도하고 에러인지뭔진 모르겠음. 수기로 눌러주면 나오긴함.</li>
</ul>
</li>
</ol>
<p>(구독정보가 없음.. 바로볼수는 없지만 정기구독관리자 회원별신청내역 들어가면 아이디로 연동해서 과거에 뭘 봣는지는 보일수있는데 뭐 회차별까지는 없다 볼수없다. )</p>
<p>-연장번호: 과거의 변호를 핸드폰조회해서 연장처리를 해둔거. 연장했기때문에 고객번호.... 03으로 시작되는연장번호를 보면 특판으로 들어왔구나 식으로 알수있는것임.</p>
<ul>
<li>연장횟수를 보면 또 정보를 알수잇ㅇ믐</li>
<li>신청내역, 발송내역(책받았으면 이후의 정보를 알수있는거) 어느주소로, 상태, 상담원들이 추가로 메모할 내역기재도가능. 등록하기 하면 독자는 발송내역이라는 발송관리카테고리에 신청이 되는거고 재바송정보가 하단에 뜬다. 발송정보옆에. 발송을 넘기면 진행으로 바뀌게됨.</li>
</ul>
<ol start="2">
<li>정기구독관리</li>
</ol>
<p>-연장여부 연장횟수에따라 신규독자인지 연장독자인지 결제별체크 결제상태. 등등...</p>
<ul>
<li>회사코드 : 본사채널 지사채널 특판채널이냐 등등등을 조회하는것. 코드를 확인할수있음. 각 지사에따라 채널에따라서 관리가능. </li>
<li>부서코드는 잘 안쓰기는함.</li>
<li>패키지신청관리 : 두세개 여러개신청하는 독자들에 관한것.</li>
<li>해외정기구독관리 : 사용안함.</li>
<li>사은품별 신청관리 : 별로 쓰고있진않음..... 안씀.</li>
<li>추가상품관리 : 정기구독신청하면서 다른 추가잡지도 같이 결제를 할수가 이있는데 다른매거진을 신청했을 때 우리 정기구독과 꼬이면 안되기떄문에 메뉴를 따로 뺸것..... 학교같은경우는 행정실에서 한번에 사고싶어함. 그것을 다루주는 경쟁업체가 있는데 그걸 한방에...! </li>
<li>낱권상품관리 : 지에스스토어에서 낱권으로 사는사람. 심플한 정보들만 있음.간단하게만 있음.
  디에스스토어 주문내역</li>
<li>취소독자낱권확인 : 안씀</li>
</ul>
<ol start="3">
<li>발송관리</li>
</ol>
<ul>
<li>택배패키지발송 : 패키지코드번호가 있는 사람만 보임. 두매체이상 구독을해서 합본.... 주소까지 정확히 일치하는 독자만 택배단품발송에 보이게 됨.</li>
<li>이노지스 연계관리 로 넘어가게 된다.</li>
<li>이노지스 묶음관리에서 택배작업을 하는 것. 엑셀다운받아서 발송작업함.</li>
<li>우편패키지발송 : 정기분 나오기전에 우편작업해야하는 것들을 여기서 작업을 하고 있음.</li>
<li>묶음발송 : 주소가같은 경우가 세건이 넘어가면 한꺼번에 묶으려고.</li>
<li>추가상품, 낱권상품,무료체험발송 &lt;= 이 세개의 메뉴..... 뭐가 더 추가해주면 좋겠다.</li>
<li>택배,재발송, ? 은 매일 사용하는 메뉴</li>
<li>발송내역: 매체별 호별로 발송된 내역관리가능. 매일맹리 쌓이는것... 이리스트가 결국 정산리스트라고 볼수있다. 이걸로 관리하는중.</li>
<li>사은품발송내역</li>
<li>본사지로생성 : 학교..같은 경우. <ul>
<li>처음에 정기구독관리 작성할 때 결제방법에 체크하면 나옴.</li>
</ul>
</li>
<li>고객재발송관리 : 마이페이지에 고객이 직접 접수할수있게 만들어둔거 : 신청횟수가 많아졌을때 재발송발송으로 넘어가서 확인하기가 어렵다??</li>
</ul>
<ol start="4">
<li>선수금관리</li>
</ol>
<ul>
<li>회계프로그램이랑 안맞음 . 차이많이남. 못찾음;;; 몇프로 차이안난다하면 그냥 넘어감.... 특판에서 많이 나타남..특판데이터 외부에서 끌어오는거라서.</li>
<li>잔여선수금</li>
<li>잔여선수금리스트</li>
</ul>
<ol start="5">
<li>기증관리</li>
</ol>
<ul>
<li>엑셀다운로드 비번없이 그냥 됨.</li>
</ul>
<ol start="6">
<li>상품권관리</li>
</ol>
<ul>
<li>상품권관리</li>
</ul>
<p>TM</p>
<ol>
<li>추가상품관리 </li>
<li>낱권상품관리</li>
<li>무료상품관리 : 3333</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[running TSC: Part2_interface]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-Part2interface</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-Part2interface</guid>
            <pubDate>Wed, 12 Apr 2023 11:36:23 GMT</pubDate>
            <description><![CDATA[<h3 id="ch7-인터페이스">ch.7 인터페이스</h3>
<blockquote>
<p>우리가 직접 만들 수 있는데 왜? 지루한 내장타입 형태만 사용??</p>
</blockquote>
<ul>
<li>&#39;인터페이스interface&#39;는 연관된 이름으로 객체 형태를 설명하는 또다른 방법이다.</li>
<li>인터페이스는 별칭으로 된 객체 타입과 여러면에서 유사하지만 일반적으로 더 읽기 쉬운 오류메시지, 더 빠른 컴파일러 성능, 클래스와의 더 나은 상호운용성을 위해 선호된다.</li>
</ul>
<p>** 타입별칭 vs 인터페이스 **</p>
<pre><code class="language-javascript">* born: number 와 name: string 을 가진 객체를 타입 별칭으로 구현하는 구문
type Poet = {
  born: number;
  name: string;
};

* 인터페이스로 구현한 동일한 구문
interface Poet {
  born: number;
  name: string;
};

    - 세미콜론(;)을 선호한다면, 대부분 인터페이스 뒤가 아닌 타입별칭 뒤에 세미콜론을 넣는다.
    - 이 기본설정은 세미콜론을 사용해 변수를 선언하는 것과 세미콜론 없이 클래스 또는 함수를 선언하는 것의 차이를 반영한다.


//2.
const valueLater: Poet;
valueLater = {
  born: 1935,
  naem: &#39;Sara Teasdale&#39;,
};
valueLater = &#39;Emily Dickinson&#39;;
//Error: Type &#39;string&#39; is not assignable to &#39;Poet&#39;.
valueLater = {
  born: true,
  //Error: Type &#39;boolean&#39; is not assignable to type &#39;number&#39;.
  name: &#39;Sappho&#39;
};
    - 인터페이스에 대한 타입스크립트의 할당가능성 검사와 오류메시지는 객체타입에서 실행되는 것과 동일하다.
    - Poet이 인터페이스 또는 타입별칭인 경우 valueLater 변수에 할당하는 것에 대한 할당 가능성 오류는 거의 동일하다.

* 인터페이스와 타입별칭의 주요 차이점
  - 인터페이스는 속성증가를 위해 병합merge할 수 있다, 이 기능은 전역 인터페이스 또는 npm패키지와 같은 외부 코드를 사용할 때 유용하다.
  - 인터페이스는 클래스가 선언된 구조의 타입을 확인하는데 사용할 수 있지만 타입별칭은 사용할 수 없다.
  - 일반적으로 인터페이스에서 타입스크립트 타입 검사기가 더 빨리 작동함.
  - 인터페이스는 타입 별칭이 하는 것처럼 새로운 객체 리터럴의 동적인 복사 붙여놓기보다 내부적으로 더 쉽게 캐시할 수 있는 명령 된 타입을 선언한다.
  - 인터페이스는 이름 없는 객체 리터럴의 별칭이 아닌 이름 있는(명명된) 객체로 간주되므로 어려운 특이케이스에서 나타나는 오류메시지를 좀 더 쉽게 읽을 수 있다.

      - 타입별칭의 유니언타입과 같은 기능이 필요할 때까지는 인터페이스를 사용하는것이 좋다.
</code></pre>
<p>** 속성타입 **</p>
<ul>
<li>getter와 setter를 포함해 가끔 존재하는 속성, 또는 임의의 속성 이름을 사용하는 등 자바스크립트 객체를 실제로 사용할 때 낯설고 이상한 부분을 모델링할 수 있도록 유용한 타입시스템 도구를 제공함.</li>
</ul>
<p>** 선택적 속성 **</p>
<ul>
<li>객체타입과 마찬가지로 모든 객체가 필수로 인터페이스 속성을 가질 필요는 없다.</li>
<li>타입애너테이션 : 앞에 ? 를 사용해 인터페이스의 속성이 선택적 속성임을 나타낼 수 있다.<pre><code class="language-javascript">//1.
interface Book {
author?: string;
pages: number;
};
const ok: Book = {
author: &#39;Rita Dove&#39;,
pages: 80
};
const missing: Book = {
pages: 80
}
  - Book 인터페이스는 필수 속성 pages와 선택적속성 author를  가진다. 
  - Book 인터페이스를 사용하는 객체에 필수 속성만 제공된다면 선택적 속성은 제공되거나 생략가능.
  - undefined 를 포함한 유니언 타입의 선택적 속성과 일반 속성 사이의 차이점과 관련된 주의사항은 객체타입뿐만 아니라 인터페이스에도 적용됨.</code></pre>
</li>
</ul>
<p>** 읽기전용속성 **</p>
<ul>
<li>경우에 따라 인터페이스에 정의된 객체의 속성을 재할당하지 못하도록 인터페이스 사용자를 차단하고 싶을 때</li>
<li>타입스크립트는 속성 이름 앞에 readonly 키워드를 추가해 다른 값으로 설정 될 수 없음을 나타낸다.</li>
<li>이러한 readonly 속성은 평소대로 읽을 수 있지만 새로운 값으로 재할당하지 못한다..<pre><code class="language-javascript">//1.
interface Page {
readonly text: string;
}
const read = (page: Page) =&gt; {
//text 속성을 수정하지 않고 읽는 것
console.log(page.text);
page.text += &#39;!&#39;;
//Error: Cannot assign to &#39;text&#39; because it is read-only property
}
  - Page 인터페이스의 text 속성에 접근하면 string을 반환하지만, text에 새로운 값을 할당하면 타입오류 발생한다.
  - readonly 제한자는 타입시스템에만 존재하며 인터페이스에서만 사용가능
  - readonly 제한자는 객체의 인터페이스를 선언하는 위치에서만 사용되고 실제 객체에는 적용되지 않는다.

</code></pre>
</li>
</ul>
<p>//2.
const pageIsh = {
  text: &#39;Hello, world&#39;
}
// pageIsh는 Page객체가 아니라 text가 있는, 유추된 객체타입이다.
pageIsh.text += &#39;!&#39;;
//pageIsh의 더 구체적인 버전인 Page를 읽는다.
read(pageIsh);
    - Pate예제에서 text속성의 부모객체는 함수 내부에서 text로 명시적으로 사용되지 않았기 때문에 함수 밖에서 속성을 수정할 수 있다.
    - 쓰기가능한 속성을 readonly속성에 할당할 수 있으므로 pageIsh는 Page로 사용할 수 있다.
    - 가변속성은 readonly 속성이 필요한 모든 위치에서 읽을 수 있다.
    - 명시적타입 애너테이션인 : Page로 변수 pageIsh를 선언하면 타입스크립트에 text 속성이 readonly라고 가리키게 된다. 
    - 하지만 유추된 타입은 readonly가 아님
    - readonly 인터페이스 멤버는 코드영역에서 객체를 의도치않게 수정하는 것을 막는 편리한 방법이다. 
    - 그러나 readonly는 타입시스템 구성요소일 뿐 컴파일 된 자바스크립트 출력 코드에는 존재하지 않는다.
    - readonly는 단지 타입스크립트 타입검사기를 사용해 개발 중에 그 속성이 수정되지 못하도록 보호하는 역할을 한다.</p>
<pre><code>
** 함수와 메서드 **
- 자바스크립트에서 객체 멤버가 객체 함수가 되는 것은 매우 일반적, 타입스크립트에서도 인터페이스 멤버를 함수타입으로 선언할 수 있다.
- 타입스크립트는 인터페이스 멤버를 함수로 선언하는 두가지 방법 제공
  (1) ** 메서드구문 ** : 인터페이스 멤버를 member(): void 와 같이 객체의 멤버로 호출되는 함수로 선언
  (2) ** 속성구문 ** : 인터페이스의 멤버를 member: () =&gt; void와 같이 독립 함수와 동일하게 선언
  두가지 선언방법은 자바스크립트에서 객체를 함수로 선언하는 방법과 동일하다.

```javascript
//1.
interface HasBothFunctionTypes {
  property: () =&gt; string;
  method(): string;
}
const hasBoth: HasBothFunctionTypes = {
  property: () =&gt; &#39;&#39;,
  method() {
    return &#39;&#39;;
  }
};
hasBoth.property();
hasBoth.method();
    - method 와 property 멤버는 둘다 매개변수 없이 호출되어 string을 반환한다.

//2.
interface OptionalReadonlyFunctions {
  optionalProperty?: () =&gt; string;
  optionalMethod?(): string;
}
    - 선택적 속성 키워드인 ?를 사용해 필수로 제공하지 않아도 되는 멤버로 나타낼 수 있다.

* 메서드와 속성선언은 대부분 서로 교환하여 사용할 수 있다. 메서드와 속성의 주요 차이점은?????
  (1) 메서드는 readonly로 선언할 수 없지만, 속성은 가능
  (2) 인터페이스 병합은 메서드와 속성을 다르게 처리한다.
  (3) 타입에서 수행되는 일부 작업은 메서드와 속성을 다르게 처리한다. 

* 기본함수가 this 를 참조할 수 있다는 것을 알고 있다면 메서드 함수를 사용. 가장 일반적으로 클래스의 인스턴스에서 사용된다.
* 반대의 경우는 속성함수를 사용
</code></pre><p>** 호출 시그니처 **</p>
<ul>
<li>인터페이스와 객체타입은 호출 시그니처로 선언할 수 있다.</li>
<li>호출 시그니처 : 값을 함수처럼 호출하는 방식에 대한 타입 시스템의 설명이다.</li>
<li>호출 시그니처가 선언한 방식으로 호출되는 값만 인터페이스에 할당할 수 있다.</li>
<li>즉, 할당가능한 매개변수와 반환타입을 가진 함수이다. 함수타입과 비슷한데, 콜론(;) 대신 화살표(=&gt;)로 표시</li>
</ul>
<pre><code class="language-javascript">//1.
type FunctionAlias = (input: string) =&gt; number;
interface CallSignature {
  (input: string): number;
}
//타입: (input: string) =&gt; number
const typedFunctionAlias: FumctionAlias = (input) =&gt; input.length;
//타입: (input: string) =&gt; number
const typedCallSignature: CallSignature = (input) =&gt; input.length;
    - FunctionAlias 와 CallSignature 타입은 모두 동일한 함수 매개변수와 반환타입을 설명한다.
    - 호출시그니처는 사용자 정의 속성을 추가로 갖는 함수를 설명하는데 사용할 수 있다.
    - 타입스크립트는 함수 선언에 추가된 속성을 해당 함수 선언의 타입에 추가하는 것으로 인식한다.


//2.
interface FunctionWithCount {
  count: number;
  (): void;
}
const hasCallCount: FunctionWithCount;
const keepsTrackOfCalls = () =&gt; {
  keepsTrackOfCalls.count += 1;
  console.log(&#39;I&#39;ve been called ${keepsTrackOfCalls.count} times!&#39;);
}
keepsTrackOfCalls.count = 0;
hasCallCount = keepsTrackOfCalls;
const doesNotHaveCount = () =&gt; {
  console.log(&#39;No idea!&#39;);
}
hasCallCount = doesNotHaveCount;
//Error: Property &#39;count&#39; is missing in type &#39;() =&gt; void&#39; but required in type &#39;FunctionWithCall&#39;
    - keepsTrackOfCalls 함수 선언에는 number 타입인 counter 속성이 주어져 FunctionWithCount 인터페이스에 할당할 수 있다.
    - 따라서 FunctionWithCount 타입의 hasCallCount 인수에 할당할 수 있다.
    - 마지막 함수에는 count 속성이 주어지지 않았음.
</code></pre>
<p>** 인덱스시그니처 **</p>
<ul>
<li>일부 자바스크립트 프로젝트는 임의의 string 키에 값을 저장하기 위한 객체를 생성한다.</li>
<li>이러한 &#39;컨테이너container&#39;객체의 경우 모든 가능한 키에 대해 필드가 있는 인터페이스를 선언하는 것은 비현실적/불가능.</li>
<li>타입스크립트는 인덱스 시그니처 구문을 제공해 인터페이스의 객체가 임의의 키를 받고, 해당 키 아래의 특정 타입을 반환할 수 있음을 나타낸다.</li>
<li>자바스크립트 객체 속성 조회는 암묵적으로 키를 문자열로 변환하기 때문에 인터페이스의 객체는 문자열 키와 함꼐 가장 일반적으로 사용된다.</li>
<li>인덱스 시그니처는 일반 속성 정의와 유사하지만 키 다음에 타입이 있고 {[i: stirn]: ...}과 같이 배열의 대괄호를 가진다.<pre><code class="language-javascript">//1.
interface WordCounts {</code></pre>
</li>
</ul>
<p>}
const counts: WordCounts = {};
counts.apple = 0;
counts.banana = 1;
counts.cherry = false;
//Error: Type &#39;boolean&#39; is not assignable to type &#39;number&#39;.
    - WordCounts 인터페이스는 number 값을 갖는 모든 string 키를 허용하는 것으로 선언되어 있다.
    - 이런 타입의 객체는 값이 number 이면 string 키가 아닌 그 어떤 키도 바인딩할 수 없다.
    - 인덱스시그니처는 객체에 값을 할당할 때 편리하지만 타입 안정성을 완벽하게 보장하지는 않는다.
    - 인덱스시그니처는 객체가 어떤 속성에 접근하든 간에 값을 반환해야함을 나타낸다.</p>
<p>//2.
interface DatesByName {
  <a href="number;">i: string</a>: Date;
}
const publishDates: DateByName = {
  Frankenstein: new Date(&#39;1 January 1818&#39;)
}
publishDates.Frankenstein;
console.log(publishDates.Frankenstein.toString())
publishDates.Beloved;
console.log(publishDates.Beloved.toString())
// 타입시스템에서는 오류가 나지 않지만 실제 런타임에서는 오류발생함.
// Runtime Error: Cannot read property &#39;toString&#39; of undefined (reading publishDates.Beloved)
    - publishDates 값은 Date 타입으로 Frankenstein 을 안전하게 반환하지만 타입스크립트는 Beloved가 정의되지 않았음에도 불구하고 정의되었다고 생각하도록 속인다.
    - 키/값 쌍을 저장하려고 하는데 키를 미리 알 수 없다면 Map을 사용하는게 더 안전함.
    - .get메서드는 항상 키가 존재하지 않음을 나타내기 위해서 |undefined 타입을 반환한다.</p>
<pre><code>
** 속성과 인덱스 시그니처 혼합 **
- 인터페이스는 명시적으로 명명된 속성과 포괄적인 용도의 string 인덱스 시그니처를 한번에 포함할 수 있다.
- 각각의 명명된 속성의 타입은 포괄적인 용도의 인덱스 시그니처로 할당할 수 있어야한다.
- 명명된 속성이 더 구체적인 타입을 제공하고, 다른 모든 속성은 인덱스 시그니처의 타입으로 대체하는 것으로 혼합해서 사용할 수 있다.
```javascript
//1.
interface HistoricalNovels {
  Oroonoko: number;
  [i: string]: number;
}
const novels: HistoricalNovels = {
  Outlander: 1991,
  Oroonoko: 1688
}
const missingOroonoko: HistoricalNovels = {
  //Error: Property &#39;Oroonoko&#39; is missing in type
  //    &#39;{Outlander: number}&#39; but required in type &#39;HistoricalNovels&#39;.
  Outlander: 1991
}
    - HistoricalNovels 는 모든 속성을 number 타입으로 선언했고 추가적으로 Oroonoko 속성이 존재해야한다.
    - 속성과 인덱스시그니처를 혼합해서 사용하는 일반적인 타입시스템기법 중 하나는 인텍스 시그니처의 원시속성보다 명명된 속성에 대해 더 구체적인 속성 타입 리터럴을 사용하는 것이다.
    - 명명된 속성으 ㅣ타입이 인덱스 시그니처에 할당될 수 있는 경우(각각의 리터렁 및 원시 속성에 해당) 타입스크립트는 더 구체적인 속성 탕비 리터럴을 사용하는 것을 허용한다.


//2.
interface chapterStarts {
  preface: 0;
  [i: string]: number;
}
const correctPreface: ChapterStarts = {
  preface: 0,
  night: 1,
  shopping: 5
}
const wrongPreface: ChapterStarts = {
  preface: 1
  //Error: Type &#39;1&#39; is not assignavle to type &#39;0&#39;.
}
    - ChapterStarts는 preface 속성은 0으로, 다른 모든 속성은 더 일반적인 number를 갖도록 선언한다.
    - 즉, ChapterStarts를 사용하는 모든 객체의 preface 속성은 반드시 0 이어야 함.
</code></pre><p>** 숫자 인덱스 시그니처 **</p>
<ul>
<li>자바스크립트가 암묵적으로 객체 속성 조회 키를 문자열로 변환하지만 때로는 객체의 키로 숫자만 허용하는 것이 바람직할 수 있다.</li>
<li>타입스크립트 인덱스 시그니처는 키로 string 대신 number 타입을 사용할 수 있지만, 명명된 속성은 그 타입을 포괄적인 용도의 string 인덱스 시그니처의 타입으로 할당할 수 있어야한다.</li>
</ul>
<pre><code class="language-javascript">//1.
interface MoreNarrowNumbers {
  [i: number]: string;
  [i: string]: string | undefined;
}
const mixesNumbersAndStrings: MoreNarrowNumbers = {
  0: &#39;&#39;,
  key1: &#39;&#39;,
  key2: undefined
}
interface MoreNarrowStrings {
  [i: number]: string | undefined;
  // Error: &#39;number&#39; index type &#39;string | undefined&#39; is not assignabl to &#39;string&#39; index type &#39;string&#39;.
  [i: string]: string;
}
    - MoreNarrowNumbers 인터페이스는 string을 string | undefined 에 할당할 수 있지만,
    - MoreNarrowStrings 인터페이스는 string | undefined 를 string에 할당할 수 없다.
</code></pre>
<p>** 중첩 인터페이스 **</p>
<ul>
<li>객체타입이 다른 객체타입의 속성으로 중첩될 수 있는 것처럼 인터페이스 타입도 자체 인터페이스 타입 혹은 객체타입을 속성으로 가질 수 있다.<pre><code class="language-javascript">interface Novel {
author: {
  name: string;
};
setting: Setting;
}
interface Setting {
place: string;
year: number;
}
const myNovel: Novel;
myNovel = {
author: {
  name: &#39;Jane&#39;
},
setting: {
  place: &#39;England&#39;,
  year: 1812
}
}
myNovel = {
author: {
  name: &#39;Emily Bronte&#39;
},
setting: {
//Error: Property &#39;year&#39; is missing in type &#39;{place: string;}&#39; but required in type &#39;Setting&#39;.
  place: &#39;West Yorkshire&#39;
}
}
  - Novel 인터페이스는 인라인 객체 타입인 author 속성과 Setting 인터페이스인 setting 속성을 포함한다.</code></pre>
</li>
</ul>
<p>** 인터페이스 확장 **</p>
<ul>
<li>때로는 서로 형태가 비슷한 여러개의 인터페이스를 갖게 됨.</li>
<li>예) 다른 인터페이스의 모든 멤버를 포함하고, 몇개의 멤버가 추가된 인터페이스인 경우.</li>
<li>타입스크립트는 인터페이스가 다른 인터페이스의 모든 멤버를 복사해서 선언할 수 있는 확장된 인터페이스를 허용한다.</li>
<li>확장할 인터페이스의 이름 뒤에 <code>extends</code> 키워드를 추가해서 다른 인터페이스를 확장한 인터페이스라는걸 표시한다.</li>
<li>이렇게 하면 파생 인터페이스를 준수하는 모든 객체가 기본 인터페이스의 모든 멤버도 가져야 한다는 것을 타입스크립트에 알려줌.<pre><code class="language-javascript">//1.
interface Writing {
title: string;
}
interface Novella extends Writing {
pages: number;
}
const myNovella: Novella = {
pages: 195,
title: &#39;Ethan&#39;
}
const missingPages: Novella = {
title: &#39;Awakening&#39;
// Error: Property &#39;pages&#39; is missing in type &#39;{title: string}&#39; but required in type &#39;Novella&#39;
}
const extraProperty: Novella = {
//Error: Type &#39;{pages: number; strategy: string; style: string}&#39; is not assignable to type &#39;Novella&#39;.
//    Object literal may only specify known properties.
//    and &#39;strategy&#39; does not exist in type &#39;Novella&#39;.
pages: 300,
strategy: &#39;baseline&#39;,
style: &#39;Natural&#39;
}
  - 인터페이스 확장은 프로젝트의 한 엔티티entity 타입이 다른 엔티티의 모든 멤버를 포함하는 상위 집합을 나타내는 실용적 방법/
</code></pre>
</li>
</ul>
<pre><code>
** 재정의 된 속성 **
- 파생 인터페이스는 다른 타입으로 속성을 다시 선언해 기본 인터페이스의 속성을 재정의override 하거나 대체할 수 있다.
- 타입스크립트의 타입 검사기는 재정의 된 속성이 기본 속성에 할당되어 있도록 강요함. 이렇게 하면 파생 인터페이스 타입의 인스턴스를 기본 인터페이스 타입에 할당가능.
- 속성을 재선언하는 대부분의 파생 인터페이스는 해당 속성을 유니언타입의 더 구체적인 하위 집합으로 만들거나 속성을 기본 인터페이스의 타입에서 확장 된 타입으로 만들기 위해 사용한다.
```javascript
interface WithNullableName {
  name: string | null;
}
interface WithNonNullableName extends WithNullableName {
  name: string;
}
interface WithNumericName extends WithNullableName {
  name: number | string
  // Error: Interface &#39;WithNumericName&#39; incorrectly extends interface &#39;WithNullableName&#39;.
  //    Types of property &#39;name&#39; are incompatible.
  //    Type &#39;string | number&#39; is not assignable to type &#39;string | null&#39;.
  //    Type &#39;number&#39; is not assignable to type &#39;string&#39;
}
    - WithNullableName 타입은 WithNonNullableName 에서 null 을 허용하지 않도록 다시 설정되었다.
    - 그러나, WithNumericName 의 name 에는 number | string 이 허용되지 않음.
    - number | string 은, string | null 에 할당할 수 없기 때문.
</code></pre><p>** 다중인터페이스 확장 **</p>
<ul>
<li>타입스크립트의 인터페이스는 여러개의 다른 인터페이스를 확장해서 선언가능</li>
<li>파생 인터페이스 이름에 있는 extends 키워드 뒤에 쉼표로 인터페이스 이름을 구분해 사용하면 된다.</li>
<li>파생 인터페이스는 모든 기본 인터페이스의 모든 멤버를 받음<pre><code class="language-javascript">//1.
interface GivesNumber {
giveNumber(): number;
}
interface GivesString {
giveString(): string;
}
interface GivesBothAndEither extends GivesNumber, GivesString {
giveEither(): number | string;
}
const useGivesBoth = (instance: GivesBothAndEither) =&gt; {
instance.givesEither(); //타입: number | string
instance.giveNumber(); //타입: number
instance.giveString(); //타입: string
}
  - GivesBothAndEither 는 세개의 메서드를 가진다.
  - 1. 자체 메서드  2. GivesNumber 에서,  3. GivesString 에서 왔다.
  - 여러 인터페이스를 확장하는 방식으로 인터페이스를 사용하면 코드 중복을 줄이고 다른 코드영역에서 객체의 형태를 더 쉽게 재사용 가능.
</code></pre>
</li>
</ul>
<pre><code>
** 인터페이스 병합 **
- 인터페이스의 중요한 특징 중 하나 : 서로 병합하는
- 두개의 인터페이스가 동일한 이름으로 동일한 스코프에 선언된 경우, 선언된 모든 필드를 포함하는 더 큰 인터페이스가 코드에 추가된다.
```javascript
//1.
interface Merged {
  fromFirst: string;
}
interface Merged {
  fromSecond: number;
}
    - fromFirst 와 fromSecond 라는 두개의 속성을 갖는 Merged 인터페이스를 선언한다.
    - 다음과 같다.
      interface Merged {
        fromFirst: string;
        fromSecond: number;
      }
    - 일반적으로 타입스크립트 개발에서는 인터페이스 병합을 자주 사용하지는 않는다.
    - 인터페이스가 여러곳에 선언되면 코드를 이해하기 어려워지므로 가능하면 병합 사용하지 않는 것이 좋다.

//2.
interface Window {
  myEnviromentVariable: string;
}
window.myEnviromentVariable; //타입: string
    - 인터페이스 병합은 외부 패키지 또는 Window 같은 내장된 전역 인터페이스를 보강하는데 특히 유용하다.
    - 기본 타입스크립트 컴파일러 옵션을 사용할 때, 파일에서 myEnviromentVariable 속성이 있는 Window 인터페이스를 선언하면 window.myEnviromentVariable 을 사용할 수 있다.
</code></pre><p>** 이름이 충돌되는 멤버 **</p>
<ul>
<li>병합된 인터페이스는 타입이 다른 동일한 이름의 속성을 여러번 선언할 수 없다.</li>
<li>속성이 이미 인터페이스에 선언되어 있ㅏ면 나중에 병합된 인터페이스에서도 동일한 타입을 사용해야한다.<pre><code class="language-javascript">//1.
interface MergeProperties {
same: (input: boolean) =&gt; string;
different: (input: string) =&gt; string;
}
interface MergedProperties {
same: (input: boolean) =&gt; string;
different: (input: number) =&gt; string;
// Error: Subsequent property declaration must have the same type.
//    Property &#39;different&#39; must be of type &#39;(input: string) =&gt; string&#39;
//    but here has type &#39;(input: number) =&gt; string&#39;
}

</code></pre>
</li>
</ul>
<p>//3.
interface MergedMethods {
  different(input: string): string;
}
interface MergedMethods {
  different(input: number): string
}
    - 병합된 인터페이스는 동일한 이름고 ㅏ다른 시그니처를 가진 메서드를 정의할 수 있고, 이렇게 하면 메서드에 대한 <code>함수오버로드</code>가 발생
    - 위의 MergedMethods 인터페이스는 두가지 오버로드가 있는 different 메서드를 생성한다.</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[running TSC: part2]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-part2</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-running-TSC-part2</guid>
            <pubDate>Wed, 12 Apr 2023 07:35:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ch5. 함수
ch6. 배열
ch7. 인터페이스
ch8. 클래스
ch9. 타입제한자
ch10. 제네릭</p>
</blockquote>
<h3 id="ch5-함수">ch5. 함수</h3>
<ul>
<li>한쪽 끝에는 함수 인수가 있고, 다른쪽 끝에는 반환 타입이 있다.</li>
<li>함수 매개변수와 반환타입에서 동일한 작업을 수행하는 방법과 이 방법이 유용한 이유???</li>
</ul>
<p>** 함수 매개변수 **</p>
<pre><code class="language-javascript">//1.
const sing = (song) =&gt; {
  console.log(&#39;Singing: ${song}!&#39;);
};
    - sing함수는 song매개변수를 받아 콘솔에 출력한다.
    - sing함수를 작성한 개발자가 song 매개변수를 제공하기 위해 의도한 값의 타입은 무엇?
    - string인가? 재정의된 toString() 메서드가 있는 객체인가? 이 코드는 버그인가???
    - 명시적 타입 정보가 선언되지 않으면 절대 타입을 알 수 없다.
    - 타입스크립트가 이를 any타입으로 간주하며 매개변수의 타입은 무엇이든 될 수 있다.


//2.
const sing = (song: string) =&gt; {
  console.log(&#39;Singing: ${song}!&#39;);
};
    - 변수와 마찬가지로 타입스크립트를 사용하면 타입 애너테이션으로 함수 매개변수의 타입을 선언할 수 있다.
    - 코드를 유효한 타입스크립트 구문으로 만들기 위해 함수 매개변수에 적절한 타입애너테이션을 추가할 필요는 없다.
    - 타입스크립트는 타입 오류로 오류를 계속 알리지만, 이미 시작 된 자바스크립트는 계속 실행 됨.
    - song 매개변수에 타입 선언이 누락된 코드 스니펫은 여전히 타입스크립트에서 자바스크립트로 변환 된다. </code></pre>
<p>** 필수 매개변수 **</p>
<ul>
<li>자바스크립트에서는 인수의 수와 상관없이 함수를 호출할 수 있다.</li>
<li>하지만 타입스크립트는 함수에 선언 된 모든 매개변수가 필수라고 가정한다.</li>
<li>함수가 잘못된 수의 인수로 호출되면, 타입스크립트는 타입오류의 형태로 이의를 제기함.</li>
<li>함수가 너무 적꺼나 많은 인수로 호출되면 타입스크립트는 인수의 개수를 계산한다.</li>
</ul>
<pre><code class="language-javascript">//1.
const singTwo = (first: string, second: string) =&gt; {
  console.log(&#39;${first} / ${second}&#39;);
};

singTwo(&quot;Ball and Chain&quot;);
// log: &quot;Ball and Chain / undefined&quot;
// Error: Expected 2 arguments, but got 1.
singTwo(&quot;I will Servive&quot;, &quot;Higher Love&quot;);
// log: &quot;I will Servive&quot;, &quot;Higher Love&quot;
singTwo(&quot;Go your own way&quot;, &quot;The Chain&quot;, &quot;Dreams&quot;);
// log: &quot;Go your own way / The Chain&quot;
// Error: Expected 2 arguments, but got 3.
    - 함수에 필수 매개변수 required Parameter를 제공하도록 강제하면, 예상되는 모든 이수값을 함수 내에 존재하도록 만들어 타입안정성을 강화하는데 도움이 된다.
    - 모든 인수값이 존재하는지 확인하지 못하면 이전 singTwo함수가 undefined 를 로그로 남기거나 인수를 무시하는 것과 같이 코드에서 예기치 않은 동작이 발생한다.
    - 매개변수는 인수로 받을 것으로 예상되는 함수의 선언을 나타낸다. 
    - 인수는 함수를 호출할 때 매개변수에 제공되는 값을 나타낸다.

const singTwo = (first: string, second: string) =&gt; {
                  // first와 second : 매개변수
  console.log(&#39;${first} / ${second}&#39;);
};
singTwo(&quot;Go your own way&quot;, &quot;The Chain&quot;, &quot;Dreams&quot;);
                                     // &quot;Dreams&quot;와 같은 문자열은 : 인수
</code></pre>
<p>** 선택적매개변수 **</p>
<ul>
<li>자바스크립트에서 함수 매개변수가 제공되지 않으면 함수 내부의 인수값은 undefinde으로 기본값이 설정된다.</li>
<li>함수 매개변수는 제공할 필요가 없을 때도 잇고, undefined값을 위해 의도적으로 사용할 수도 있다.</li>
<li>타입스크립트가 이러한 선택적 매개변수에 인수를 제공하지 못하는 경우, 타입 오류를 보고하지 않도록/</li>
<li>타입스크립트에서는 : 선택적 객체타입 속성과 유사하게 타입애너테이션의 : 앞에 ? 를 추가해 매개변수가 선택적이라고 표시한다.</li>
<li>함수 호출에 선택적 매개변수를 제공할 필요는 없다. </li>
<li>선택적 매개변수에는 항상 | undefined 가 유니언타입으로 추가되어 있음/</li>
</ul>
<pre><code class="language-javascript">//1.
const announceSong = (song: string, singer?: string) =&gt; {
  console.log(&#39;Song: ${song}&#39;);
  if (singer) {
    console.log(&#39;Song: ${song}&#39;)
  }
};
announceSong(&#39;Greensleeves&#39;);
announceSong(&#39;Greensleeves&#39;, undefined);
announceSong(&#39;Chandelier&#39;, &#39;Sia&#39;);
    - announceSong함수에서 singer매개변수는 선택사항으로 표시된다.
    - 타입은, string | undefined 이며 함수 호출자가 singer 매개변수를 위한 인수를 제공할 필요가 없다.
    - 만일, singer가 제공되면 string 값이거나 undefined일 수 있다.
    - 이러한 선택적 매개변수는 항상 암묵적으로 undefined 가 될 수 있음.
    - singer는 string | undefined 타입으로 시작한 후 if 문에 따라 string타입으로 좁혀짐.
    - 선택적 매개변수는, | undefined 를 포함하는 유니언 타입 매개변수와는 다름.
    - ? 으로 표시된 선택적 매개변수가 아닌 매개변수가 아닌 매개변수는 값이 명시적으로 undefined 라도 항상 제공되어야 함.


//2.
const announceSongBy = (song: string, singer: string|undefined) =&gt; {
  /* ... */
};
announceSongBy(&#39;Greensleeves&#39;);
// Error: Expected 2 arguments, but got 1.
announceSongBy(&#39;Greensleeves&#39;, undefined);
announceSong(&#39;Chandelier&#39;, &#39;Sia&#39;);
    - announceSongBy 함수의 singer 매개변수는 명시적으로 제공되어야 한다.
    - singer는 string 값이거나 undefined가 될 수 있다.
    - 함수에서 사용되는 모든 선택적 매개변수는 마지막 매개변수여야 한다.
    - `필수 매개변수 전에 선택적 매개변수를` 위치시키면 타입스ㅡ구문오류 발생
        const announceSinger = (singer?: string, song: string) =&gt; {};
        // Error: A required parameter cannot follow an optional parameter
</code></pre>
<p>** 기본매개변수 **</p>
<ul>
<li>자바스크립트에서 선택적 매개변수를 선언할 때 =와 값이포함된 기본값을 제공할 수 있다.</li>
<li>즉, 선택적 매개변수에는 기본적으로 값이 제공되기 때문에 해당 타입스ㅡ타입에는 암묵적으로 함수내부에 |undefined 유니언타입이 추가된다.</li>
<li>타입스ㅡ는 함수의 매개변수에 대해 인수를 누락하거나 undefined 인수를 사용해서 호출하는 것을 여전히 허용함.</li>
<li>타입스ㅡ의 타입추론은 초기 변수값과 마찬가지로 기본 함수 매개변수에 대해서도 유사하게 작동한다.</li>
<li>매개변수에 기본값이 있고 타입 애너테이션이 없는 경우, 타입스ㅡ는 해당 기본값을 기반으로 매개변수 타입을 유추함.<pre><code class="language-javascript">//1.
const rateSong = (song: string, rating = 0) =&gt; {
console.log(&#39;${song} gets ${rating}/5 stars!&#39;);
}
rateSong(&#39;Photograph&#39;);
rateSong(&#39;Set Fire to the Rain&#39;, 5);
rateSong(&#39;Set Fire to the Rain&#39;, undefined);
rateSong(&#39;At Last!&#39;, &quot;100&quot;);
// Error: Argument of type &#39;&quot;100&quot;&#39; is mot assignable to paraeter of type &#39;number | undefined&#39;
  - rateSong 함수에서 rating은 number 타입으로 유추되지만, 함수를 호출하는 코드에서는 선택적 number | undefined 가 된다.</code></pre>
</li>
</ul>
<p>** 나머지매개변수 **</p>
<ul>
<li>자바스킙트의 일부 함수는 임의의 수의 인수로 호출할 수 있도록 만들어진다.</li>
<li>...스프레드연산자는 함수 선언의 마지막 매개변수에 위치하고, 해당 매개변수에서 시작해 함수에 전달 된 &#39;나머지&#39;인수가 모두 단일 배열에 저장되어야 함을 나타낸다.</li>
<li>타입스ㅡ는 이러한 나머지 매개변수의 타입을 일반 매개변수와 유사하게 선언가능.</li>
<li>단, 인수배열 나타내기위해 끝에 [] 구문이 추가된다는 것.</li>
</ul>
<pre><code class="language-javascript">//1.
const singAllTheSongs = (singer: string, ...songs: string[]) =&gt; {
  for (const song of songs) {
    console.log(&#39;${song}, by ${singer}&#39;)
  }
};
singAllTheSongs(&#39;Alicia Keys&#39;);
singAllTheSongs(&#39;lady gaga&#39;, &#39;just Dance&#39;, &#39;poker face&#39;);
singAllTheSongs(&#39;Ella Fitzgerald&#39;, 2000);
// Error: Argument of type &#39;number&#39; is not assignable to parameter of type &#39;string&#39;;
    - singAllTheSongs 은 songs 나머지 매개변수에 대해 0개 이상의 string 타입 인수를 사용할 수 있다.</code></pre>
<p>** 반환타입 **</p>
<ul>
<li>타입스크립트는 지각적perceptive이다.</li>
<li>함수가 반환할 수 있는 가능한 모든 값을 이해하면 함수가 반환하는 타입을 알 수 있다.</li>
<li>singSongs는 타입스크립트에서 number를 반환함<pre><code class="language-javascript">//1.
//타입: (song: string[]) =&gt; number
const singSongs = (songs: string[]) =&gt; {
for (const song of songs) {
  console.log(&#39;${song}&#39;);
}
return (
  songs.length;
)
}
  - 함수에 다른 값을 가진 여러개의 반환문을 포함하고 있다면, 타입스크립트는 반환타입을 가능한 모든 반환타입의 조합으로 유추한다.


</code></pre>
</li>
</ul>
<p>//2.
//타입: (songs: string[], index: number) =&gt; string | undefined
const getSongAt = (songsL string[], index: number) =&gt; {
  return (
    index &lt; songs.length ? songs[index] : undefined;
  )
}
    - getSongAt 함수는 string | undefined를 반환하는 것으로 유추 된다.
    - 두가지 가능한 반환값이 각각 string 과 undefined 이기 때문.</p>
<pre><code>

** 명시적반환타입 **

* 변수와 마찬가지로 타입애너테이션을 사용해 함수의 반환 타입을 명시적으로 선언하지 않는 것이 좋다. 그러나 특히 함수에서 반환 타입을 명시적으로 선언하는 방식이 매우 유용할 때가 종종 있음.
    - 가능한 반환값이 많은 함수가 항상 동일한 타입의 값을 반환하도록 강제한다.
    - 타입스크립트는 재귀함수의 반환타입을 통해 타입을 유추하는 것을 거부한다.
    - 수백개 이상의 타입스크립트 파일이 있는 매우 큰 프로젝트에서 타입스크립트 타입검사ㅏ 속도를 높일 수 있다.

```javascript
//1.
const singSongRecursive = (songs: string[], count = 0): number =&gt; {
  return (
    songs.length ? singSongsRecursive(songs.slice(1), count+1) : count;
  )
};
    - 함수의 반환문이 함수의 반환 타입으로 할당할 수없는 값을 반환하는 경ㅇ 타입스크립트는 할당 가능성 오류를 표시함.


//2.
const getSongRecordingDate = (song: string): Date | undefined =&gt; {
  switch (song) {
    case &quot;Strange Fruit&quot;:
      return new Date(&#39;April 20, 1939&#39;);
    case &quot;GreensLeeves&quot;;
      return &quot;unknown&quot;;
      // Error: Type &#39;string&#39; is not assignable to type &#39;Date&#39;
    default:
      return undefined;
  }
};
    - getSongRecordingDate함수는 Date | undefined를 반환하도록 명시적으로 선언했는데,
    - 반환문 중 하나가 string을 반환하도록 잘못 제공하고 있다.</code></pre><p>** 함수타입 **</p>
<ul>
<li>자바스크립트에서는 함수를 값으로 전달할 수 있다.</li>
<li>즉, 함수를 가지기 위한 매개변수 또는 변수의 타입을 선언하는 방법이 필요하다.</li>
<li>함수타입 구문은 화살표함수와 유사하지만 함수 본문 대신 타입이 있다.</li>
</ul>
<pre><code class="language-javascript">//1. 
const nothingInGivesString: () =&gt; string;
    - nothingInGivesString변수타입은 매개변수가 없고 string타입을 반환하는 함수이다.

//2.
const inputAndOutput: (songs: string[], count?: number) =&gt; number;
    - inputAndOutput변수타입은, string[]매개변수와 count선택적매개변수 및 number값을 반환하는 함수.

* 함수타입은 콜백 매개변수(함수로 호출되는 매개변수)를 설명하는데에 자주 사용된다.
//3.
const songs = [&#39;juice&#39;, &#39;shake it off&#39;, &#39;whatup&#39;];
const runOnSongs = (getSongAt: (index: number) =&gt; string) =&gt; {
  for (let i=0; i&lt;songs.length; i+=1) {
    console.log(getSongAt(i))
  }
}
const getSongAt = (index: number) =&gt; {
  return (
    &#39;${songs[index]}&#39;
  );
};
runOnSongs(getSongAt);
const logSong = (song: string) =&gt; {
  return (
    &#39;${song}&#39;
  );
};
runOnSongs(logSong);
// 1. Error: Argument of type &#39;(song: string) =&gt; string&#39; is not assignable to parameter of type &#39;(index: number) =&gt; string&#39;. 
// 2.    Types of parameters &#39;song&#39; and &#39;index&#39; are incompatible.
// 3.    Type &#39;number&#39; is not assignable to type &#39;string&#39;.
    - runOnSongs(logSong)에 대한 오류메시지는 할당 가능성 오류의 예로 몇가지 상세한 단계까지 제공.
    - 두 함수를 서로 할당할 수 없다는 오류를 출력할 때 타입스크립트는 일반적으로 3가지 단계제공
        1. 두 함수 타입을 출력한다.
        2. 일치하지 않는 부분을 지정한다.
        3. 일치하지 않는 부분에 대한 정확한 할당가능성오류를 출력
    - 오류
        1. longSongs: (song: string) =&gt; string 은 getSongAt: (index: number) =&gt; string 에 할당되도록 제공된 타입이다.
        2. longSong의 song 매개변수는, getSongAt의 index 매개변수로 할당된다.
        3. song의 string 타입은 index의 number타입에 할당할 수 없다.
    - 타입스크립트에서 여러줄로 나타나는 오류가 어려워보일 수 있지만 한줄 한줄씩 읽으며 각 부분이 전달하는 내용을 이해하자.
</code></pre>
<p>** 함수타입 괄호 **</p>
<ul>
<li>함수타입은 다른 타입이 사용되는 모든 곳에 배치할 수 있ㄷ.</li>
<li>여기에는 유니언 타입도 포함된다.</li>
<li>유니언타입의 애너테이션에서 함수 반환 위치를 나타내거나 유니언타입을 감싸는 부분을 표시할 때 괄호를 사용.</li>
</ul>
<pre><code class="language-javascript">//타입: string | undefined 유니언을 반환하는 함수
const returnsStringOrUndefined = () =&gt; string | undefined;

//타입: undefined 또는 string을 반환하는 함수
const mayneReturnsString: (() =&gt; string) | undefined;
</code></pre>
<p>** 매개변수 타입 추론 **</p>
<ul>
<li>매개변수로 사용되는 인라인함수를 포함하여 작성한 모든 함수에 대해 매개변수를 선언해야한다면 번거로움</li>
<li>다행히도, 타입스크립트는 선언된 타입의 위치에 제공된 함수의 매개변수 타입을 유추가능하다.</li>
</ul>
<pre><code class="language-javascript">//1.
const singer: (song: string) =&gt; string;
singer = function (song) {
  // song: string타입
  return &#39;Singing: ${song.toUpperCase()}!&#39;;
};
    - singer변수는 string타입의 매개변수를 갖는 함수로 알려져 있으므로
    - 나중에 singer가 할당되는 함수 내의 song 매개변수는 string일것이다 는 것을 알 수 있다.

//2.
const songs = [&#39;Call Mee&#39;, &#39;Jolene&#39;, &#39;The Chain&#39;];
songs.forEach((song, index) =&gt; {
  console.log(&#39;${song} is at ${index}&#39;)
  // song: string, index: number
})
    - song과 index매개변수는 타입스크립트에 따라 각각 string과 number로 유추된다.</code></pre>
<p>** 함수타입별칭 **</p>
<ul>
<li>&#39;유니언과 리터럴&#39;에서 다룬 타입별칭</li>
<li>함수타입에서도 동일하게 타입별칭 사용가능<pre><code class="language-javascript">//1.
type StringToNumber = (input: string) =&gt; number;
const stringToNumber: StringToNumber;
stringToNumber = (input) =&gt; input.length;
stringToNumber = (input) =&gt; input.toUpperCase();
// Error: Type &#39;string&#39; is not assignable to type &#39;number&#39;.
  - StringToNumber타입은 string타입을 받고 number타입을 반환하는 함수의 별칭을 지정한다. 별칭은 이후 변수타입을 설명하는데 사용함.
  - 비슷하게 함수매개변수도 함수타입을 참조하는 별칭입력가능.
</code></pre>
</li>
</ul>
<p>//2.
type NumberToString = (input: number) =&gt; string;
const useNumberToString = (numberToString: NumberToString) =&gt; {
  console.log(&#39;The string id: ${numberToString(1234)});
};
usesNumberToString((input) =&gt; &#39;${}! Hooray!&#39;);
usesNumberToString((input) =&gt; input+2);
//Error: Type &#39;number&#39; is not assignable to type &#39;string&#39;;
    - useNumberToString함수는 함수타입 별칭인 NumberToString의 단일 매개변수를 가진다.
    - 타입별칭은 특히 함수타입에 유용함.
    - 타입별칭을 이용하면 반복적으로 작성하는 매개변수와 반환타입을 갖는 코드공간을 많이 절약할 수 있다.</p>
<pre><code>
** void 타입 **
- 일부함수는 어떤 값도 반환하지 않는다.
- 예를들면, return문이 없는 함수이거나 값을 반환하지 않는 return문을 가진 함수의 경웅미.
- 타입스크립트는 void키워드를 사용해 반환값이 없는 함수의 반환타입을 확인할 수 있다.

```javascript
//1.
const lonSong = (song: string | undefined): void =&gt; {
  if (!song) {
    return;
  }
  console.log(&#39;${song}&#39;);
  return true;
  // Error: Type &#39;boolean&#39; is not assignable to type &#39;void&#39;;
};
    - 반환타입이 void 인 함수는 값을 반환하지 않을 수 있다.
    - logSong 함수는 void를 반환하도록 선언되었으므로 값 반환을 허용하지 않음.
    - 함수타입 선언시 void반환타입은 매우 유용하다.


//2.
const songLogger:(song:string) =&gt; void;
songLogger = (song) =&gt; {
  console.log(&#39;${songs}&#39;);
};
songLogger(&#39;Heart of Glass&#39;);
    - 함수타입 선언할 때 void를 사용하면 함수에서 반환되는 모든 값은 무시된다.
    - songLogger 변수는 song: string을 받고, 값을 반환하지 않는 함수이다.

//3.
const returnsVoid = () =&gt; {
  return;
};
const lazyValue: string | undefined;
lazyValue = returnsVoid();
//Error: Type &#39;void&#39; is not assingnable to type &#39;string|undefined&#39;
    - 자바스크립트함수는 실제값이 반환되지 않으면 기본으로 모두 undefined 를 반환하지만, void는 undefined와 다르다.
    - void는 함수의 반환 타입이 무시된다는 것을 의미하고, undefined 는 반환되는 리터럴값 이다. 
    - undefined 를 포함하는 대신 void타입의 값을 할당하려고 하면 타입오류 나타남.

//4.
const records: string[] = [];
const saveRecords = (newRecords: string[]) =&gt; {
  newRecords.forEach(record =&gt; records.push(record));
}
saveRecords([&#39;21&#39;, &#39;Come On Over&#39;, &#39;The Bodyguard&#39;])
    - undefined 와 void 를 구분해서 사용하면 매우 유용하다.
    - 특히, void를 반환하도록 선언 된 타입 위치에 전달된 함수가 반환 된 모든 값을 무시하도록 설정할 떄 유용하다.
    - 예를들어, 배열의 내장 forEach 메서드는 void를 반환하는 콜백을 받는다.
    - forEach에 제공되는 함수는 원하는 모든 값을 반환할 수 있다.
    - saveRecords 함수의 records.push(record) 는 number(배열의 .push()에서 반환된 값)를 반환하지만, 여전히 newRecords.forEach 에 전달 된 화살표함수에 대한 반환값이 허용된다.
    - void 타입은 자바스크립트가 아닌 함수의 반환 타입을 선언하는데 사용하는 타입스크립트 키워드.
    - void 타입은 함수의 반환값이 자체적으로 반환 될 수 있는 값도 아니고, 사용하기 위한 것도 아님.</code></pre><p>** never반환타입 **</p>
<ul>
<li>일부함수는 값을 반환하지 않을 뿐만 아니라 반환할 생각도 전혀 없음.</li>
<li>never반환 함수는 (의도적으로) 항상 오류를 발생시키거나 무한 루프를 실행하는 함수이다.</li>
<li>함수가 절대 반환하지 않도록 의도하려면 명시적:never타입 애너테이션을 추가해 해당 함수를 호출한 후 모든 코드가 실행되지 않음.</li>
</ul>
<pre><code class="language-javascript">const fail = (message: string): never =&gt; {
  throw new Error(&#39;Invariant failure: ${message}.&#39;);
};
const workWithUnsafeParam = (param: unknown) =&gt; {
  if (typeof param !== &#39;string&#39;) {
    fail(&#39;param should be a string, not ${typeof param}&#39;)
  }
  param.toUpperCase();
};
    - 함수가 절대 반환하지 않도록 의도하려면 명시적 : never 타입 애너테이션을 추가해 해당 함수를 호출한 후 모든 코드가 실행 되지 않음을 나타낸다.
    - fail함수는 오류만 발생시키므로 param의 타입을 string으로 좁혀서 타입스크립트의 제어흐름분석을 도와준다.
    - never는 void와는 다름.
    - void는 아무것도 반환하지 않는 함수를 위한 것이고,
    - never는 절대 반환하지 않는 함수를 위한 것.</code></pre>
<p>** 함수오버로드 **</p>
<ul>
<li>일부 자바스크립트 함수는 선택적매개변수와 나머지 매개변수만으로 표현할 수 없는 매우 다른 매개변수들로 호출 될 수 있다.</li>
<li>이러한 함수를 <code>오버로드 시그니처 overload signature</code>라고 불리는 타입스크립트 구문으로 설명할 수 있다.</li>
<li>즉, 하나의 최종 구현 시그니처와 그 함수의 본문 앞에 서로 다른 버전의 함수이름, 매개변수, 반환타입을 여러번 선언한다.</li>
<li>오버로드 된 함수호출에 대해 구문오류를 생성할지 여부를 결정할 때 타입스크립트는 함수의 오버로드 시그니처만 확인한다.</li>
<li>구현시그니처는 함수의 내부 로직에서만 사용된다.</li>
</ul>
<pre><code class="language-javascript">//1.
function createDate(timestamp: number): Date;
function createDate(month: number, day: number, year: number): Date;
const createDate = (monthOrTimestamp: number, day?: number, year?: number) =&gt; {
  return (
    day === undefined || year === undefined 
    ? new Date(monthOrTimestamp) 
    : new Date(year, monthOrTimestamp, day)
  );
};
createDate(554356800);
createDate(7, 27, 1987);
createDate(4, 1);
// Error: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments
    - createDate 함수는 1개의 timestamp 매개변수 또는 3개의 매개변수(month, day, year)를 사용해 호출.
    - 허용된 수의 인수를 사용해 호출할 수 있지만 2개의 인수를 사용해 호출하면 2개의 인수를 허용하는 오버로드 시그니처가 없기 때문에 타입오류 발생한다.
    - 함수오버로드는 복잡하고 설명하기 어려운 함수타입에 사용하는 최후의 수단. 함수를 단순하게 유지하고 가능하면 함수 오버로드를 사용하지 않는 것이 좋음.</code></pre>
<h3 id="ch6배열">ch6.배열</h3>
<blockquote>
<p>유연한 배열과 고정된 튜플</p>
</blockquote>
<pre><code class="language-javascript">//1.
const elements = [true, null, undefined, 42];
elements.push(&#39;even&#39;, [&#39;more&#39;]);
//elements배열의 값: [true, null, undefined, 42, &#39;even&#39;, [&#39;more&#39;]]
    - 자바스크립트 배열은 매우 유연하고 내부에 모든 타입의 값을 혼합해서 저장할 수 있다.
    - 대부분의 개별 자바스크립트 배열은 하나의 특정 타입의 값만 가진다.
    - 다른 타입의 값을 추가하게 되면 배열을 읽을 때 혼란을 줄 수 있다.
    - 타입스크립트는 초기배열에 어떤 데이터 타입이 있는지 기억하고, 배열이 해당 데이터타입에서만 작동하도록 제한한다.

//2.
const warriors = [&#39;Artemisia&#39;, &#39;Boudica&#39;];
warriors.push(&#39;Zenobia&#39;);
warriors.push(true);
//Error: Argument of type &#39;boolean&#39; is not assignable to parameter of type &#39;string&#39;.
    - warriors배열이 초기에 string타입의 값을 포함한다는 것을 알고 있으므로 이후 string 타입의 값 추가는 허용하지만 다른 데이터타입 추가는 허용하지 않는다.
    - 타입스크립트가 초기배열에 담긴 요소를 통해 배열의 타입을 유추하는 방법은 변수의 초기값에서 변수타입을 유추하는 방법과 유사함.
    - 타입스크립트는 값이 할당되는 방식에서 코드의 의도 된 타입을 이해ㅏㅎ려고 시도하며 배열도 그렇다.
</code></pre>
<p>** 배열타입 **</p>
<ul>
<li>다른 변수선언과 마찬가지로 배열을 저장하기 위한 변수는 초기값이 필요하지 않음.</li>
<li>변수는 undefined로 시작해서 나중에 배열값을 받을 수 있다.</li>
<li>타입스크립트는 변수에 타입 애너테이션을 제공해 배열이 포함해야 하는 값의 타입을 알려주려고 함.</li>
<li>배열에 대한 타입애너테이션은 배열의 요소타입 다음에 [] 가 와야함.</li>
</ul>
<pre><code class="language-javascript">//1.
const arrayOfNumbers: number[];
arrayOfNumbers = [4.8,45,46,23,12];
    - 배열 타입은 Array&lt;number&gt; 같은 구문으로도 작성할 수 있다. 하지만 대부분은 더 간단한 number[]를 선호.</code></pre>
<p>** 배열과 함수타입 **</p>
<ul>
<li>배열타입은 함수타입에 무엇이 있는지를 구별하는 괄호가 필요한 구문 컨테이너의 예이다.</li>
<li>괄호는 애너테이션의 어느부분이 함수반환부분이고 어느부분이 배열타입 묶음인지를 나타내기 위해 사용.</li>
</ul>
<pre><code class="language-javascript">const createStrings: () =&gt; string[];
//타입은 string배열을 반환하는 함수

const stringCreators: (() =&gt; string)[];
//타입은 각각의 string을 반환하는 함수배열
    - 함수타입인 createStrings, 배열타입인 stringCreator 와 동일하지 않다.</code></pre>
<p>** 유니언타입배열 **</p>
<ul>
<li>배열의 각 요소가 여러 선택 타입 중 하나일 수 있음을 나타내려면 유니언타입을 사용한다.</li>
<li>유니언 타입으로 배열타입을 사용할 떄 애너테이션의 어느 부분이 배열의 콘텐츠이고 어느 부분이 유니언 타입 묶음인지를 나타내기 위해 괄호를 사용해야 할 수도 있다.</li>
<li>유니언타입배열에서 괄호사용은 매우 중요함.
```javascript
//1.</li>
<li>두 타입은 동일하지 않다.
const stringOrArrayOfNumbers: string | number[];
//타입은 string 또는 number의 배열</li>
</ul>
<p>const arrayOfStringOrNumbers: (stirng | number)[];
//타입은 각각 number 또는 string인 요소의 배열
    - 타입스크립트는 배열의 선언에서 두가지 이상의 요소타입이 포함 되는 경우, 유니언타입 배열임을 알게 된다.
    - 즉, 배열의 요소타입은 배열에 담긴 요소에 대한 모든 가능한 타입의 집합.</p>
<p>//2.
    //타입: (stirng | undefined)[]
const namesMaybe = [
  &#39;Aqualtune&#39;,
  &#39;Blenda&#39;,
  undefined
];
    - namesMayne는 string값과 undefined 값을 모두 가지므로 (string | undefined)[] 타입이다.</p>
<pre><code>
** any배열의 진화 **
- 초기에 빈 배열로 설정된 변수에서 타입애너테이션을 포함하지 않으면 타입스크립트는 배열을 `any[]`로 취급하고 모든 콘텐츠를 받을 수 있다.
- 하지만 any변수가 변경되는 것처럼, `any[]`배열이 변경되는 것도 좋아하지 않음.
- 타입애너테이션이 없는 빈 배열은 잠재적으로 잘못된 값 추가를 허용해 타입스크립트의 타입검사기가 갖는 이점을 부분적으로 무력화함.

```javascript
//1.
//타입: any[]
const values = [];

//타입: string[]
values.push(&#39;&#39;);

//타입: (number | string)[]
values[0] = 0;
    - values배열은 any요소를 갖고 시작해 string요소를 포함하도록 바뀐 다음, 다시 number|string 요소로 바뀐다.
    - 변수와 마찬가지로 배열이 any타입이 되도록 허용하거나 일반적으로 any타입을 사용하도록 허용하면 타입스크립트의 타입검사목적을 부분적으로 무효화함. 타입스크립트는 값의 타입을 알 떄 가장 잘 작동한다.
</code></pre><p>** 다차원배열 **</p>
<ul>
<li>2차원배열 또는 배열의 배열은 두개의 <code>[]대괄호</code>를 가진다.<pre><code class="language-javascript">//1.
const arrayOfArrayOfNumbers: number[][];
arrayOfArrayOfNumbers = [
[1,2,3],
[2,4,6],
[3,5,7]
];
  - 3차원배열 또는 배열의 배열의 배열에는 세개의 []가 있고
  - 4차원배열에는 네개의 [] 5차원에는 ...
  - 다차원배열타입은 새로운개념이 아님
  - 2차원 배열은 원래의 타입을 가지며 끝에 []가 있고, 그 뒤에 []을 추가하는 것.
</code></pre>
</li>
</ul>
<p>//2.
const arrayOfArrayOfNumbers: (number[])[];
    - arrayOfArraysOfNumbers배열은 number[][]타입이고 (number[])[]으로 나타낼 수 있다.</p>
<pre><code>
** 배열멤버 **
- 타입스크립트는 배열의 멤버를 찾아서 해당 배열의 타입요소를 되돌려주는 전형적인 인덱스 기반 접근방식을 이해하는 것이다.
```javascript
//1.
const defenders = [&#39;Clarenza&#39;, &#39;Dina&#39;];

//타입: string
const defender = defenders[0];
    - defenders배열은 string[]타입이므로 defender는 string타입이다.
    - 유니언타입으로 된 배열의 멤버는 그 자체로 동일한 유니언타입이다.

//2.
const solidersOrDates = [&#39;Deborah Sampson&#39;, new Date(1782, 6, 3)];

//타입: string | Date
const solidersOrDate = solidersOrDates[0];
- solidersOrDates는 (string | Date)[]타입이므로 solidersOrDate 변수는 string | Date 타입이다.</code></pre><p>** 주의사항: 불안정한 멤버 **</p>
<ul>
<li>타입스크립트 타입시스템은 기술적으로 불안정하다고 알려져 있음. 특히, 배열은 타입시스템에서 불안정한 소스이다.</li>
<li>기본적으로 타입스크립트는 모든 배열의 멤버에 대한 접근이 해당 배열의 멤버를 반환한다고 가정하지만, 자바스크립트에서조차도 배열으 ㅣ길이보다 큰 인덱스로 배열요소에 접근하면 undefined를 제공<pre><code class="language-javascript">//1.
const withElements = (elements: string[]) =&gt; {
console.log(elements[9001].length);
//타입오류없음
};
withElements([&quot;It&#39;s&quot;, &quot;over&quot;]);
  - 런타입 시 Cannot read property &#39;length&#39; of undefined 가 발생하며 충돌할거라고 유추할 수 있지만, 타입스크립트는 검색된 배열의 멤버가 존재하는지 의도적으로 확인하지 않는다.
  - elements[9001]은 undefined 가 아니라 string 타입으로 간주된다.
  - 타입스크립트에는 배열조회를 더 제한하고 타입을 안전하게 만드는 noUncheckedIndexedAccess 플래그가 있지만, 이 플래그는 매우 엄격해서 사용 잘 하지않음.</code></pre>
</li>
</ul>
<h3 id="스프레드와-나머지-매개변수">스프레드와 나머지 매개변수</h3>
<ul>
<li>...연산자를 사용하는 나머지 매개변수와 배열 스프레드는 자바스크립트에서 배열과 상호작용하는 핵심방법.</li>
</ul>
<p>** 스프레드 **</p>
<ul>
<li>...스프레드spread연산자를 사용해 배열을 결합한다.</li>
<li>타입스크립트는 입력된 배열 중 하나의 값이 결과배열에 포함될 것임.</li>
<li>만약에, 입력된 배열이 동일한 타입이라면 출력 배열도 동일한 타입이다.</li>
<li>서로 다른 타입의 두 배열을 함꼐 스프레드해 새 배열을 생성하면 새 배열은 두개의 원래 타입 중 어느 하나의 요소인 유니언타입 배열로 이해됨.<pre><code class="language-javascript">//1.
//타입: string[]
const soldiers = [&#39;harriet&#39;, &#39;joan&#39;, &#39;khutulun&#39;];
//타입: number[]
const soldierAges = [90, 19, 45];
//타입: (string | number)[]
const conjoined = [...soldiers, ...soldierAges];
  - conjoined 배열은 string타입과 number타입 값을 모두 포함하므로 
</code></pre>
</li>
</ul>
<pre><code>
** 나머지 매개변수 스프레드 **
- 타입스크립트는 나머지 매개변수로 배열을 스프레드하는 자바스크립트 실행을 인식하고 이에 대해 타입검사를 수행한다.
- 나머지 매개변수를 위한 인수로 사용되는 배열은 나머지매개변수와 동일한 배열 타입을 가져야 한다.
```javascript
const logWarriors = (greeting: string, ...names: string[]) =&gt; {
  for (const name of names) {
    console.log(&#39;${greeting}, ${name}!&#39;)
  }
}
const warriors = [&#39;Cathay Williams&#39;, &#39;Lozen&#39;, &#39;Nzinga&#39;]
logWorriors(&#39;Hello&#39;, ...worriors);
const birthYears = [1844, 1840, 1583];
logWarriors(&#39;Born in&#39;, ...birthYears);
//Error: Argument of type &#39;number&#39; is not assignable to parameter of type &#39;string&#39;.
    - logWarriors함수는 ...names 나머지 매개변수로 string 값만 받는다.
    - string[]타입배열을 스프레드하는 것은 허용되지만 number[]는 허용되지 않음.
</code></pre><p>** 튜플 **</p>
<ul>
<li>자바스크립트 배열은 이론 상 어떤 크기라도 될 수 있지만</li>
<li>때로는 고정된 크기의 배열인 <code>튜플tuple</code>을 사용하는 것이 유용하다.</li>
<li>튜플 배열은 각 인덱스에 알려진 특정 타입을 가지며 배열의 모든 가능한 멤버를 갖는 유니언 타입보다 더 구체적임.</li>
<li>튜플타입을 선언하는 구문은 배열 리터럴처럼 보이지만 요소의 값 대신 타입을 적음.</li>
</ul>
<pre><code class="language-javascript">//1.
const yearAndWarrior: [number, string];
yearAndWarrior = [530, &#39;Tomyris&#39;];
yearAndWarrior = [false, &#39;Tomyris&#39;];
//Error: Type &#39;boolean&#39; is not assignable to type &#39;number&#39;.
yearAndWarrior = [530];
//Error: Type &#39;[number]&#39; is not assignable to type &#39;[number, string]&#39;.
//    Source has 1 element(s) but target requires 2.
    - yearAndWarrior배열은 인덱스 0에 number타입 값을 갖고, 인덱스 1에 string값을 갖는 튜플타입으로 선언됨.
    - 자바스크립트에서는 단일 조건을 기반으로 두개의 변수에 초기값을 설정하는 것처럼 한번에 여러 값을 할당하기 위해 튜플과 배열구조분해할당array destructuring을 함께 자주 사용한다.

//2.
// year 타입 : number
// warrior 타입 : string
const [year, warrior] = Math.random() &gt; 0.5 ? [340, &#39;Archidamia&#39;] : [1828, &#39;Rani or Jhansi&#39;]
    - 타입스크립트는 위 코드에서 year은 항상 number이고, warrior는 항상 string임을 인식한다.</code></pre>
<p>** 튜플할당가능성 **</p>
<ul>
<li>타입스크립트에서 튜플타입은 가변길이variableLength의 배열 타입보다 더 구체적으로 처리됨.</li>
<li>즉, 가변길이의 배열 타입은 튜플타입에 할당 못해<pre><code class="language-javascript">//1.
// 타입 : (boolwan | number)[]
const pairLoose = [false, 123];
const pairTupleLoose: [boolean, number] = pairLoose;
// Error: Type &#39;(number | boolean)[]&#39; is not assignable to type &#39;[boolean, number]&#39;
//        Target requires 2 element(s) but source may have fewer.
  - pairLoose 내부에 [boolean, number]가 있는 것을 볼 수 있지만, 타입스크립트는 더 일반적인 (boolean | number)[] 타입으로 유추한다.
  - pairLoose가 [boolean, number] 자체로 선언 된 경우 pairTupleLoose에 대한 값 할당이 허용되었을 것.
  - 하지만 타입스크립트는 튜플타입의 튜플에 얼마나 많은 멤버가 있는지 알고 있기 떄문에 길이가 다른 튜플은 서로 할당할 수 없다.

</code></pre>
</li>
</ul>
<p>//2.
const tupleThree: [boolean, number, string] = [false, 1583, &#39;Nzinga&#39;];
const tupleTwoExact: [boolean, number] = [tupleThree[0], tupleThree[1]];
const tupleTwoExtra: [boolean, number] = tupleThree;
//Error: Type &#39;[boolean, number, string]&#39; is not assignable to typt &#39;[boolean, number]&#39;.
//        Source has 3 element(s) but target allows only 2.</p>
<pre><code>
** 나머지 매개변수로서의 튜플 **
- 튜플은 구체적인 길이와 요소타입 정보를 가지는 배열로 간주되므로 함수에 전달할 인수를 저장하는데 특히 유용함.
- 타입스크립트는 ...나머지 매개변수로 전달된 튜플에 정확한 타입검사를 제공할 수 있다.
```javascript
//1.
const logPair = (name: string, value: number) =&gt; {
  console.log(&#39;${name} has ${value}&#39;)
}
const pairArray = [&#39;Amage&#39;, 1]
logPair(...pairArray);
const pairTupleCorrect: [string, number] = []


.
.
.

.
..
.. 미완성.
..
..
.
..
..
.</code></pre><p>** 명시적 튜플 타입 **</p>
<ul>
<li>함수에 대한 반환타입 애너테이션처럼 튜플타입도 타입애너테이션에 사용할 수 있다.</li>
<li>함수가 튜플타입을 반환하다고 선언되고 배열 리터럴을 반환한다면 해당 배ㄹ 리터럴은 일반적인 가변ㅣㄹ이의 배열대신 튜플로 간주됨.<pre><code class="language-javascript">//1.
//반환타입: [string, number]
const firstCharAndSizeExplicit = (input: string): [string, number] =&gt; {
return (
  [input[0], input.length]
);
}
//firstChar타입: string
//size타입: number
const [firstChar, size] = firstCharAndSizeExplicit(&#39;Cathay Williams&#39;);</code></pre>
</li>
</ul>
<p>** const 어서션 **</p>
<ul>
<li>명시적타입 애너테이션에 튜플타입을 입력하는 작업은 명시적타입 애너테이션을 입력할 때와 동일한 이유로 좋지않을 수 있다.</li>
<li>즉, 코드 변경에 따라 작성 및 수정이 필요한 구문을 추가해야함.</li>
<li>타입스크립트의 대안 : 값 뒤에 넣을 수 있는 const 어서션인 as const 연산자를 제공한다.</li>
<li>const 어서션은 타입스크립트에 타입을 유추할 때 읽기 전용이 가능한 값 형식을 사용하도록 한다.<pre><code class="language-javascript">//1.
//타입: (string | number)[]
const unionArray = [1157, &#39;Tomoe&#39;];
</code></pre>
</li>
</ul>
<p>//타입: readonly [1157, &#39;Tomoe&#39;]
const readonlyTuple = [1157, &#39;Tomoe&#39;] as const;
    - 배열리터럴 뒤에 as const 가 배치되면, 배열이 튜플로 처리되어야함을 나타냄.
    - const 어서션은 유연한 크기의 배열을 고정된 크기의 튜플로 전환하는 것을 넘어서, 해당 튜플이 읽기 전용이고 값 수정이 예상되는 곳에서 사용할 수 없음을 나타냄</p>
<p>//2.
const pairMutable: [number, string] = [1157, &#39;Tomoe&#39;];
pairMutable[0] = 1247;
const pairAlsoMutable: [number, string] = [1157, &#39;Tomoe&#39;] as const
//Error: The type &#39;readonly [1157, &#39;Tomoe&#39;]&#39; is &#39;readonly&#39;
//        and cannot be assigned to the mutable type &#39;[number, string]&#39;.
const pairConst = [1157, &#39;Tomoe&#39;] as const;
pairConst[0] = 1247;
//Error: Cannot assign to &#39;0&#39; because it is a read-only property.
    - pairMutable은 전형적인 명서적 튜플 타입이므로 수정될 수 있다.
    - 그러나 as const 는 값이 변경될 수 있는 pairAlsoMutable에 할당할 수 없도록하고, 상수 pairConst의 멤버는 수정을 허용하지 않는다.
    - 실제로 읽기전용 튜플은 함수반환에 편리하다.
    - 튜플을 반환하는 함수로부터 반환된 값은 보통 즉시 구조화되지 않으므로 읽기전용인 튜플은 함수를 사용하는데에 방해가 되지않음.</p>
<p>//3.
//반환타입: readonly[string, number]
const forstCharAndSizeAsConst = (input: string) =&gt; {
  return (
    [input[0], input.length] as const;
  )
}
//firstChar타입: string
//size타입: number
const [firstChar, size] = firstCharAndSizeAsConst(&#39;Ching Shih&#39;);
    - firstCharAndSizeAsConst는 읽기전용 [string, number]를 반환하지만, 이를 사용하는 코드는 해당 튜플에서 값을 찾는 것에만 관심을 둔다.</p>
<pre><code>
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[(작성중) next.js]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-next.js-ozkamych</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-next.js-ozkamych</guid>
            <pubDate>Tue, 11 Apr 2023 00:08:07 GMT</pubDate>
            <description><![CDATA[<h3 id="nextjs-기본개념">next.js 기본개념</h3>
<ul>
<li>next.js는 React로 만드는 서버 사이드 렌더링 프레임워크. Server Side Rendering을 함으로 얻는 이득은 다음의 두가지를 해결하는 것이다.
  (1) 클라이언트 렌더링의 경우 모든 js 파일을 로드하고 사용자는 웹을 보게 된다. 이때까지 사용자는 많은 시간을 대기해야 한다.
  → 서버에서 jsc를 로딩함으로 클라이언트 측에서는 jsc를 로딩하는 시간이 줄어들게 된다.
  (2) seo문제 : 클라이언트 사이드의 경우 jsc가 로드 되지 않은 경우 아무런 정보를 보이지 않는다, 구글의 검색엔진의 경우 jsc가 로드되지 않은 페이지를 검색엔진으로 스캔함으로 결론적으로 검색에 아무 페이지도 걸리지 않게 된다.
  → 검색엔진이 jsc를 읽는 것이 아닌 서버 측에서 jsc, html, css를 만들어 컨텐츠를 직접 업로드 함으로 검색엔진에 게시글이 걸리게 된다, 또한 meta 태그를 자유롭게 추가함으로 seo를 용이하게 할 수 잇따.</li>
</ul>
<p>** next.js가 제공하는 주요기능 **</p>
<pre><code class="language-javascript">* hot reloding
    : 개발 중 저장되는 코드는 자동으로 새로고침된다.

* automatic routing
    : pages폴더에 있는 파일은 해당 파일 이름으로 라우팅 된다. (pages/page1.tsx -&gt; localhost:3000/page1)
    : public 폴더도 pages의 폴더와 동일하게 라우팅 할 수 있다.
* single file components
    : style jsx 를 사용함으로 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들 수 있다.
    : &lt;style jsx global&gt; 를 사용하면 글로벌로 스타일정의 가능

    // styled-jsx
    const Heading = (props) =&gt; {
      const variabe = &quot;red&quot;;
      return (
        &lt;div className=&quot;title&quot;&gt;
          &lt;h1&gt;{props.heading}&lt;h1&gt;
          &lt;style jsx&gt;
            { ... }
      )
    }
    export default function Home() {
      return (
        &lt;div&gt;
          (...)
        &lt;div&gt;
      )
    }
</code></pre>
<pre><code class="language-javascript">* Link 사용하기 
- 보통, 페이지 간 이동은 a 태그를 사용하지만 next에서는 사용하지 않는다.
- a 태그를 사용하면 처음 페이지에 진입시 번들</code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[(작성중) next.js]]></title>
            <link>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-next.js</link>
            <guid>https://velog.io/@heejeen-choi/%EC%9E%91%EC%84%B1%EC%A4%91-next.js</guid>
            <pubDate>Tue, 11 Apr 2023 00:08:07 GMT</pubDate>
            <description><![CDATA[<h3 id="nextjs-기본개념">next.js 기본개념</h3>
<ul>
<li>next.js는 React로 만드는 서버 사이드 렌더링 프레임워크. Server Side Rendering을 함으로 얻는 이득은 다음의 두가지를 해결하는 것이다.
  (1) 클라이언트 렌더링의 경우 모든 js 파일을 로드하고 사용자는 웹을 보게 된다. 이때까지 사용자는 많은 시간을 대기해야 한다.
  → 서버에서 jsc를 로딩함으로 클라이언트 측에서는 jsc를 로딩하는 시간이 줄어들게 된다.
  (2) seo문제 : 클라이언트 사이드의 경우 jsc가 로드 되지 않은 경우 아무런 정보를 보이지 않는다, 구글의 검색엔진의 경우 jsc가 로드되지 않은 페이지를 검색엔진으로 스캔함으로 결론적으로 검색에 아무 페이지도 걸리지 않게 된다.
  → 검색엔진이 jsc를 읽는 것이 아닌 서버 측에서 jsc, html, css를 만들어 컨텐츠를 직접 업로드 함으로 검색엔진에 게시글이 걸리게 된다, 또한 meta 태그를 자유롭게 추가함으로 seo를 용이하게 할 수 잇따.</li>
</ul>
<p>** next.js가 제공하는 주요기능 **</p>
<pre><code class="language-javascript">* hot reloding
    : 개발 중 저장되는 코드는 자동으로 새로고침된다.

* automatic routing
    : pages폴더에 있는 파일은 해당 파일 이름으로 라우팅 된다. (pages/page1.tsx -&gt; localhost:3000/page1)
    : public 폴더도 pages의 폴더와 동일하게 라우팅 할 수 있다.
* single file components
    : style jsx 를 사용함으로 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들 수 있다.
    : &lt;style jsx global&gt; 를 사용하면 글로벌로 스타일정의 가능

    // styled-jsx
    const Heading = (props) =&gt; {
      const variabe = &quot;red&quot;;
      return (
        &lt;div className=&quot;title&quot;&gt;
          &lt;h1&gt;{props.heading}&lt;h1&gt;
          &lt;style jsx&gt;
            { ... }
      )
    }
    export default function Home() {
      return (
        &lt;div&gt;
          (...)
        &lt;div&gt;
      )
    }
</code></pre>
<pre><code class="language-javascript">* Link 사용하기 
- 보통, 페이지 간 이동은 a 태그를 사용하지만 next에서는 사용하지 않는다.
- a 태그를 사용하면 처음 페이지에 진입시 번들</code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[running TSC : part1]]></title>
            <link>https://velog.io/@heejeen-choi/running-TSC-part1</link>
            <guid>https://velog.io/@heejeen-choi/running-TSC-part1</guid>
            <pubDate>Thu, 06 Apr 2023 02:11:44 GMT</pubDate>
            <description><![CDATA[<h3 id="ch1">ch1.</h3>
<p>** 바닐라자바스크립트 **</p>
<ul>
<li>중요한 언어 확장이나 프레임워크 없이 자바스크립트를 사용하는 것을 말한다</li>
<li>순수자바스크립트 vanilla</li>
<li>tsc가 자바스크립트에 있는 주요함정을 극복하기 위해 적절한 기능추가한 이유, 주요함정의 치명성</li>
</ul>
<pre><code class="language-javascript">* 코드의 문서화

interface Painter {
  // Painter에 세가지 속성이 있다, 그중 2가지는 메서드이다.
  finish(): boolean;
  ownMaterials: Material[];
  paint(
    painting: string, 
    materials: Material[]
  ): boolean;
}
function paintPainting(
  painter: Painter, 
  painting: string
): boolean{ (...) }
</code></pre>
<ul>
<li><p>많은 자바스크립트 프로젝트는 소스코드를 실행할 수 있는 자바스크립트로 변환하기 위해서 타입스크립트의 자체 컴파일러 대신 &#39;바벨&#39;같은 전용변환기를 사용한다.</p>
</li>
<li><p>tsconfig.json 파일을 생성할 때의 또다른 이점은 편집기에서 특정 폴더를 열었을 때 편집기가 해당폴더를 타입스크립트 프로젝트로 인식한다는 것, vscode에서 폴더를 열면 타입스크립트 코ㅡ를 분석하는데 사용하는 설정은 해당 폴더의 tsconfig.json을 따르게 된다.</p>
</li>
<li><p>vscode는 타입스크립트를 지원하고 자체적으로 타입스크립트로 빌드된다. </p>
</li>
</ul>
<p>** 타입스크립트의 제약 **</p>
<ul>
<li>자바스크립트 코드를 구조화하는데는 도움이 되지만 타입 안정성 강화를 제외하고는 해당 구조가 어떻게 보여야 하는지에 대해서는 어떤 강요도 없다.</li>
<li>특정 대상만을 위한 독단적인 프로엠워크가 아님, 자바스크립트에서 사용해썬 아키텍처 패턴 중 뭐든 사용해서 코드를 작성할 수 있고, 타입스크립트가 이를 지원한다.</li>
<li>설계목표
  (1) 현재와 미래의 ECMA스크립트 제안에 맞춘다.
  (2) 모든 자바스크립트 코드의 런타임 동작을 유지한다.</li>
<li>tsc코드는 브라우저나 node.js와 같은 환경에서 실행되기 전에 자바스크립트로 컴파일 되어야 한다. 빌드 파이프라인은 대부분 성능 저하를 무시하도록 설정된다. 코드에서 발생할 수 있는 오류를 분석하는 느린 tsc기능은 실행 가능한 애플리케이션 코드 파일 생성하는 것과는 분리된 채로 수행됨.</li>
</ul>
<h3 id="ch2">ch2.</h3>
<p><strong>* 타입시스템 *</strong></p>
<ul>
<li>타입 : jsc에서 다루는 값의 형태에 대한 설명 </li>
<li>형태 : 값에 존재하는 속성과 메서드 그리고 내장되어있는 typeof 연산자가 설명하는 것을 의미</li>
<li>타입시스템의 작동 
  (1) 코드를 읽고 존재하는 모든 타입과 값을 이해한다
  (2) 각 값이 초기선언에서 가질 수 있는 타입을 확인한다.
  (3) 각 값이 추후 코드에서 어떻게 사용 될 수 있는지 모든 방법을 확인
  (4) 값의 사용법이 타입과 일치하지 않으면 사용자에게 오류표시</li>
</ul>
<p>** 오류종류 **</p>
<ul>
<li>구문오류 : 타입스크립트가 자바스크립트로 변환되는 것을 차단한 경우<ul>
<li>타입스크립트가 코드로 이해할 수 없는 잘못된 구문을 감지할 때 발생</li>
</ul>
</li>
<li>타입오류 : 타입 검사기에 따라 일치하지 않는 것이 감지 된 경우<ul>
<li>타입오류가 있음에도 ㅂ루구하고 자바스크립트 코드를 ㅊㄹ력할 수 있지만, 출력 된 jsc코드가 원하는대로 실행되지않을 가능성이 있다는 신호를 타입오류로 알려준다</li>
</ul>
</li>
</ul>
<pre><code class="language-javascript">* 할당 가능성

let firstName = &quot;Carole&quot;;
firstName = &#39;Joan&#39;;

할당가능성 : 타입스크립트에서 함수호출이나 변수에 값을 제공할 수 있는지 여부를 확인하는 것. 즉, 전달된 값이 예상된 타입으로 할당 가능한지의 여부를 확인한다.

&#39;Type ...is not assignable to type...&#39; 형태의 가장 일반적인 오류
- 해당 오류메시지에 언급된 첫번째 typt은 : 코드에서 변수에 할당하려고 시도하는 값이다.
- 두번째 type은 : 첫번째 타입. 즉, 값이 할당되는 변수이다.

- 변수에 타입스크립트가 읽어야 할 초기값이 없는 경우도 있다. tsc는 나중에 사용할 변수의 초기 타입을 파악하려고 시도하지 않난다. 그리고 기본적으로 변수를 암묵적인 any 타입으로 간주한다. 즉, 변수는 세상의 모든 것이 될 수 있음을 나타냄.
- 초기타입을 유추할 수 없는 변수는 `진화하는 any` 라고 부름. 특정 타입을 강제하는 대신 새로운 값이 할당 될 때마다 변수타입에 대한 이해를 발전시킨다.


// 타입: any
let rocker;
// 타입: string
rocker = &quot;John Jett&quot;
// Ok
rocker.toUpperCase();
// 타입: number
rocker = 19.58;
// Ok
rocker.toPrecision(1);
// Error: &#39;toUpperCase&#39;does not exist on type &#39;number&#39;.
rocker.toUpperCase();

tsc는 초기값을 할당하지 않고도 변수의 타입을 선언할 수 있는 구문인 타입애너테이션typeAnnotation을 제공한다. 이것은 변수 이름의 뒤에 배치되며 콜론(:)과 타입 이름을 차례대로 기재함. 

변수에 타입애너테이션으로 정의한 타입 외의 값을 할당하면 타입 오류발생한다.

* 불필요한 타입애너테이션
    - tsc가 자체적으로 수집할 수 없는 정보를 타입스크립트에 제공할 수 이싸. 타입을 즉시 유추할 수 있는ㄴ 변수에도 애너테이션을 사용할 수 있다.

    let firstName: string = &quot;Tina&quot;;

- 아무것도 변하지않는 변수에는 타임에너ㅔ이션을 ㅊ가하지않는것을 선호한다.
- 코드를 명확하게 문서화하거나 실수로 변수타입이 변경되지 않도록 타입스크립트를 보호하기 위해 변수에 명시적으로 타입애너테이션을 포함하는 것이 경우에 따라서는 유용할 수 있다. 

- 타입스크립트는 변수에 할당 된 값이 원래 타입과 일치하는지 확인하는 것 이상을 수행


import {value} from &#39;./values&#39;;
export const doubled = value * 2;

scope : 변수에 접근할 수 있는 범위
모듈 : export 또는 import가 있는 파일
스크립트 : 모듈이 아닌 모든 파일

- 파일이 스크립트면 타입스크립트는 해당 파일을 전역 스코프로 간주하므로 모든 스크립트가 파일의 내용에 접근가능. 즉, 스크립트 파일에 선언 된 변수는 다른 스크립트 파일에 선언된 변수와 동일한 이름을 가질 수 없다.

- 타입스크립트 파일에 Cannot redeclare... 라는 오류 : 파일에 아직 export 또는 import 문을 추가하지 않았기 때문일 수 있다. ECMA스크립트 사양에 따라 export 또는 import 문 없이 파일을 모듈로 만들어야 한다면 파일 아무곳에나 export{};를 추가해서 강제로 모듈이 되도록 만들며 ㄴ됨.</code></pre>
<h3 id="ch3">ch3.</h3>
<blockquote>
<p>상수를 제외한 모든 것은 변한다.
시간이 지나면서 값도 변할 수 있다.</p>
</blockquote>
<ul>
<li>tsc가 해당 값을 바탕으로 추론을 수행하는 두가지 핵심개념
  (1) 유니언union : 값에 허용 된 타입을 두개 이상의 가능한 타입으로 확장하는 것
  (2) 내로잉narrowing : 값에 허용 된 타입이 하나 이상의 가능한 타입이 되지 않도록 좁히는 것</li>
</ul>
<pre><code class="language-javascript">1. 유니언타입

let mathematician = Math.random() &gt; 0.5 ? undefined : &quot;Mark Goldberg&quot;;

- mathematician은 어떤 타입??
  - 둘 다 잠재적 타입이긴 하지만 무조건 undefined거나 혹은 무조건 string도 아니다. mathematician은 undefined 아거나 string 일 수 있다. &#39;이거 혹은 저거&#39; 와 같은 타입을 말한다. 

let mathmatician: string | undefined

let thinker: string | null = null;
if (Math.random() &gt; 0.5) {
  thinker = &quot;Susanne Langer&quot;;
}

- 유니언타입 선언의 순서는 중요하지 않다.
- 모든 유니언 타입에 존쟇지 안ㅇㅎ는 속성에 대한 접근을 제한하는 것은 안전조치에 해당한다.
- 유니언타입으로 정의된 여러 타입 중 하나의 타입으로 된 값의 속성을 사용하려면 코드에서 값이 보다 구체적인 타입 중 하나라는 것을 tsc에 알려야 한다. 이러한 과정을 : 내로잉


2. 내로잉

- 값이 정의,선언 혹은 이전에 유추 된 것보다 더 구체적인 타입임을 코드에서 유추하는 것이다. 
- tsc가 값의 타입이 이전에 알려진 거솝다 더 좁혀졌다는 것을 알게 되면 값을 더 구체적인 타입으로 취급한다. 타입을 좁히는데 사용할 수 있는 노리적 검사를 `타입가드` 라고 한다. 


- typeof 연산자를 사용한 내로잉
let researcher = Math.random() &gt; 0.5 ? &quot;Rosalind Frankie&quot; : 51;
if (typeof researcher === &#39;string&#39;) {
  researcher.toUpperCase();
}</code></pre>
<pre><code class="language-javascript">* 리터럴 타입

- philosopher 변수를 보자
const philosopher = &quot;Hypatia&quot;;
- philosopher 의 타입은 ?
  String타입이다.
  philosopher 는 원시타입 값 중 어떤 것이 아닌 특정 원시값으로 알려진 타입. 이것이 리터럴 타입
- 원시타입 string 은 존재할 수 있는 모든 가능한 문자열의 집합을 나타내지만, 리터럴 타입인 &quot;Hypatia&quot;는 하나의 문자열만 나타냄
- 변수를 const 로 선언하고 직접 리터럴 값을 할당하면 tsc는 해당 변수를 할당 된 리터럴 값으로 유추한다.
- 각 원시타입은 해당 타입이 가질 수 있는 가능한 모든 리터럴 값의 전체 조합으로 생각할 수 있다. 즉, 원시타입은 해당 타입의 가능한 모든 리터럴 값의 집합이다.


const lifespan: number | &quot;ongoing&quot; | &quot;uncertain&quot;;

lifespan = 89;
lifespan = &quot;ongoing&quot;;
lifespan = true;


// Error : Type &#39;true&#39; is not assignable to type &#39; number|&quot;ongoing&quot;|&quot;uncertain&quot; &#39;
</code></pre>
<pre><code class="language-javascript">* 리터럴 할당 가능성

const specificallyAda: &quot;Ada&quot;;

specificallyAda = &quot;Ada&quot;;
// Error: Type &#39;&quot;Byron&quot;&#39; is not assignable to type &#39;&quot;Ada&quot;&#39;.
specificallyAda = &quot;Byron&quot;;

// 타입: string
const someString = &quot;&quot;;
// Error : Type &#39;string&#39; is not assignable to type &#39;Ada&#39;
specificallyAda = someString;


0과 1처럼 동일한 원시타입이어도 서로 다른 리터럴 타입은 서로 할당 할 수 없다.
specificallyAda 는 리터럴타입 Ada 로 선언했으므로 값에 Ada를 할당할 수 있지만,
Byron 이나 string 타입 값은 할당할 수 없다.
그러나 리터럴 타입은 그 값이 해당하는 원시 타입에 할당할 수 있다. </code></pre>
<pre><code class="language-javascript">* null검사
- &#39;잠재적으로 정의되지않은 undefined 값&#39;
- 다른 타입이 필요한 위치에서 null값을 사용하도록 허용하는 많은 타입시스템을 가리키는 용어
- 엄격한 null검사를 활성화해야만 코드가 null 또는 undefined 값으로 인한 오류로부터 안전한지 여부를 쉽게 파악할 수 있다.

* 참 검사를 통한 내로잉
    - jsc에서 참 또는 truthy(boolean문맥에서 참으로 평가되는것)는 &amp;&amp; 연산자 또는 if 문처럼 boolean 문맥에서 true로 간주된다.
    - jsc에서 false, 0, -0, 0n, &quot;&quot;, null, undefined, NaN 처럼 falsy 로 정의된 값을 제외한 모든 값은 참이다.
    - tsc는 잠재적인 값 중 truthy로 확인 된 일부에 한해서만 변수의 타입을 좁힐 수 있다. 

let geneticist = Math.random() &gt; 0.5 ? &quot;Barbara McClintock&quot; : undefined;

- geneticist 는 string|undefined 타입이며, undefined 는 항상 falsy 이므로 tsc는 if 문의 코드블록에서는 geneticist 가 string 타입이 되어야 한다고 추론가능.

* 초기값이 없는 변수
- jsc에서 초기값이 업ㅇㅅ는 변수는 기본적으로 undefined 가 된다. 만약 undefined를 포함하지 않는 타입으로 변수를 선언한 다음, 값을 할당하기 전에 사용하려고 시도한다면 다음과 같은 오류메시지가 나타난다.

const mathematician: string;
mathematician?.length;
//Error: Variable &#39;mathematician&#39; is used before being assigned
</code></pre>
<pre><code class="language-javascript">* 타입스크립트에는 재사용하는 타입에 더 쉬운 이름을 할당하는 타입별칭이 있다. 
* 타입별칭은 : type 새로운이름 = 타입  의 형태를 갖는다.

type MyName = ...;

타입 별칭은 자바스크립트가 아니다. 런타임 코드에서는 참조할 수 없다. 타입스크립트는 런타임에 존재하지 않는 항목에 접근하려고 하면 타입 오류로 알려준다.

type SomeType = string | undefined;
console.log(SomeType);
//Error: &#39;SomeType&#39; only refers to a type, but is being used a value here.


* 타입별칭은 다른 타입 별칭 참조가 가능하다.
* 유니언 타입인 타입 별칭 내에 또다른 유니언 타입인 타입 별칭을 포함하고 있다면 다른 타입 별칭을 참조하는 것이 좋다.

type Id = number | string;
type IdMaybe = Id | undefined | null;
// IdMaybe 타입은 다음과 같음. number | string | undefined | null
</code></pre>
<h3 id="ch4">ch.4</h3>
<ul>
<li><p>객체 리터럴은 각자의 타입이 있는 키와 값의 집합이다.
복잡한 객체형태를 설명하는 방법과 타입스크립트가 객체의 할당가능성을 확인하는방법에 대해 .</p>
</li>
<li><p>객체타입</p>
</li>
<li><p>{...}구문을 사용해서 객체 리터럴을 생성하면 타입스크립트는 해당 속성을 기반으로 새로운 객체 타입 또는 타입형태를 고려한다.</p>
</li>
<li><p>해당 객체타입은 객체의 값과 동일한 속성명과 원시 타입을 갖는다. </p>
</li>
<li><p>값의 속성에 접근하려면 <code>value.멤버</code> 또는 <code>value[&#39;멤버&#39;]</code> 구문을 사용한다.</p>
</li>
<li><p>다음 poet 변수의 타입은 number 타입인 born 과 string 타입인 name 으로 이루어진 두개의 속성을 갖는 객체이다. 이 두개의 속성에 접근하는 것은 허용되지만, 다른 속성 이름으로 접근하려고 하면 해당 이름이 존재하지 않는다는 타입오류가 발생한다.</p>
<pre><code class="language-javascript">const poet = {
born: 1935,
name: &quot;Mary Oliver&quot;
};
poet[&#39;born&#39;];
poet.name;
poet.end;
// Error: Property &#39;end&#39; does not exist on type &#39;{born: number; name: string;}&#39;</code></pre>
</li>
<li><p>객체타입은 타입스크립트가 자바스크립트 코드를 이해하는 방법에 대한 핵심개념이다.</p>
</li>
<li><p>null 과 undefined 를 제외한 모든 값은 그 값에 대한 실제타입의 멤버집합을 가지므로 타입스크립트는 모든 값의 타입을 확인하기 위해 객체타입을 이해해야한다.</p>
</li>
<li><p>객체타입 선언</p>
</li>
<li><p>기존 객체에서 직접 타입을 유추하는 것도 좋지만, 결국에는 객체의 타입을 명시적으로 선언하는게 좋다.</p>
</li>
<li><p>명시적으로 타입이 선언된 객체와는 별도로 객체의 형태를 설명하는 방법필요.</p>
</li>
<li><p>객체타입은 객체리터럴과 유사하게 보이지만 필드값 대신 타입을 사용해 설명한다. </p>
</li>
</ul>
<p>const poetLater: {
  born: number;
  name: string;
};
poetLater = {
  born: 1935,
  name: &quot;Mary Oliver&quot;;
}
poetLater = &quot;Sappho&quot;;
// Error: Type &#39;string&#39; is not assignable to type &#39;{born: number; name:string;}&#39;</p>
<ul>
<li>별칭객체 타입
{...} → 이렇게 객체타입을 계속 작성하지 말고 각 객체 타입에 타입 별칭을 할당해 사용하자, 타입스크립트의 할당 가능성 오류 메시지를 좀더 직접적으로 읽기 쉽게 만드는 추가이점있음.</li>
</ul>
<p>type Poet = {
  born: number;
  name: string;
};
const poetLater: Poet;
poetLater = {
  born: 1935,
  name: &quot;sara Teasdale&quot;
};
poetLater = &quot;Emily Dickinson&quot;;
// Error: Type &#39;string&#39; is not assignable to &quot;Poet&#39;</p>
<ul>
<li>대부분의 타입스크립트 프로젝트는 객체 타입을 설명할 때 인터페이스 키워드를 사용하는 것을 선호한다. 별칭 객체타입과 인터페이스는 거의 동일하다.</li>
<li>타입스크립트가 객체 리터럴을 해석하는 방법을 이해하자</li>
</ul>
<pre><code>
** 구조적 타이핑 **
```javascript
- 타입스크립트의 타입시스템은 구조적으로 타입화 되어있다. 즉, 타입을 충족하는 모든 값을 해당 타입으 ㅣ값으로 사용할 수 있다. 매개변수나 변수가 특정 객체 타입으로 선언되면 타입스크립트에 어떤 객체를 사용하든 해당 속성이 있어야 한다고 말해야한다.

type WithFirstName = {
  firstName: string;
};
type WithLastName = {
  lastName: string;
};
const hasBoth = {
  firstName: &quot;Lucille&quot;,
  lastName: &quot;Clifton&quot;
};
const withFirstName: WithFirstName = hasBoth;
const withLastName: WithLastName = hasBoth;

    - 별칭객체타입인 WithFirstName 과 WithLastName 은 오직 string 타입의 단일멤버만 선언한다.
    - hasBoth 변수는 명시적으로 선언되지 않았음에도 두개의 별칭 객체타입을 모두 가지므로 두개의 별칭 객체 타입 내에 선언된 변수를 모두 제공할 수 있다.
    - 덕타이핑 : 동적 타이핑의 한 종류로 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 의미한다. 
</code></pre><p>** 사용검사 **</p>
<ul>
<li>객체타입으로 애너테이션 된 위치에 값을 제공할 때 타입스크립트는 값을 해당 깨체타입에 할당할 수 있는지 확인한다. 할당하는 값에는 객체타입의 필수속성이 있어야한다. 객체 타입에 필요한 멤버가 객체에 없다면타입스크립트는 타입 오류를 발생시킨다. </li>
</ul>
<pre><code class="language-javascript">type FirstAndLastNames = {
  first: string;
  last: string;
};
consst hasBoth: FirstAndLastNames = {
  first: &quot;Sarojini&quot;,
  last: &quot;Naidu&quot;
};
const hasOnlyOne: FirstAndLastNames = {
  // 두가지 속성이 모두 없는 객체는 사용할 수 없다
  //Error: Property &#39;last&#39; is missing in type &#39;{first: string}&#39;
  // but required in type &#39;FirstAndLastNames&#39;.
  first: &quot;Sappho&quot;
}

    - 별칭객체타입인 FirstAndLastNames 는 first와 last 속성이 모두 있어야 한다.
    - 두가지 속성을 모두 포함한 객체는 FirstAndLastNames 타입으로 선언된 변숭 ㅔ사용할 수 있지만  두가지 속성이 모두 없는 객체는 사용할 수 없다.
    - 둘 사이에 일치하지 않는 타입도 허용되지 않는다. 객체 타입은 필수속성 이름과 해당 속성이 예상되는 타입을 모두 지정한다. 객체의 속성이 일치하지 않으면 타입스크립트는 타입 오류를 발생시킨다.



type TimeRange = {
  start: Date;
};
const hasStartString: TimeRange = {
  start: &quot;1879-02-13&quot;;
  //Error: Type &#39;string&#39; is not assignable to type &#39;Date&#39;.
}

    - TimeRange타입은 start속성을 Date타입으로 예상한다. 하지만, hasStartString객체의 start속성이 Date가 아니라 string타입이므로 타입오류 발생.

</code></pre>
<p>** 초과속성검사 **</p>
<ul>
<li>변수가 객체타입으로 선언되고, 초기값에 객체타입에서 정의된 것보다 많은 필드가 있다면 타입오류 발생.</li>
<li>변수를 객체타입으로 선언하는 것은 타입검사기가 해당 타입에 예상되는 필드만 있는지 확인하는 방법이기도 하다.</li>
</ul>
<pre><code class="language-javascript">//1.
type Poet = {
  born: number;
  name: string;
};
const poetMatch: Poet = {
  born: 1928,
  name: &quot;Maya Angelou&quot;
};
const extraProperty: Poet = {
  activity: &quot;walking&quot;,
  //Error: Type &#39;{activity: string; born: number; name: string;}&#39;
  // is not assignable to type &#39;Poet&#39;.
  // Object literal may only specify known properties,
  // and &#39;activity&#39; does not exist in type &#39;Poet&#39;.
  born: 1935,
  name: &quot;Mary Oliver&quot;
};

    - poetMatch변수는 별칭객체타입에 정의 된 필드가 Poet에 정확히 있지만, 초과 속성이 있는 extraProperty는 타입오류 발생시킴.
    - 초과 속성검사는 객체타입으로 선언된 위치에서 생성되는 객체 리터럴에 대해서만 일어난다. 기존 객체 리터럴을 제공하면 초과 속성 검사를 우회한다. 


//2.
const existingObject = {
  activity: &quot;walking&quot;,
  born: 1935,
  name: &quot;Mary Oliver&quot;
};
const extraPropertyButOk: Poet = existingObjext;

    - extraPropertyButOk변수는 초기값이 구조적으로 Poet와 일치하기 때문에 타입오류가 발생하지 않음.
</code></pre>
<p>** 중첩된 객체타입 **</p>
<pre><code class="language-javascript">//1.
type Poem = {
  author: {
    firstName: string;
    lastName: string;
  };
  name: string;
};
const poemMatch: Poem = {
  author: {
    firstName: &quot;Sylvia&quot;,
    lastName: &quot;Plath&quot;
  },
  name: &quot;Lady Lazarus&quot;
};
const poemMismatch: Poem = {
  author: {
    name: &quot;Sylvia Plath&quot;
    // Error: Type &#39;{name: string;}&#39; is not assignable
    //  to type &#39;{firstName: string; lastName: string;}&#39;
    //  Object literal may only specify known properties, and &#39;name&#39;
    //  does not exist in type &#39;{firstName: string; lastName: string;}&#39;
  },
  name: &quot;Tulips&quot;
};

    - Poem타입은 author 속성이 firstName:string 과 lastName:string인 객체로 선언되었다.
    - poemMatch변수는 구조가 Poem과 일치하기 떄문에 Poem을 할당할 수 있지만,
    - poemMismatch는 author 속성에 firstName과 lastName 대신 name을 포함하니까 할당할 수 없음.


//2.
type Author = {
    firstName: string;
    lastName: string;
};

type Poem = {
    author: Author;
    name: string;
};

const poemMismatch: Poem = {
  author: {
    name: &quot;Sylvia Plath&quot;
    // Error: Type &#39;{name: string;}&#39; is not assignable to type &#39;Author&#39;
    //  Object literal may only specify known properties,
    //  and &#39;name&#39; does not exist in type &#39;Author&#39;.
  },
  name: &quot;Tulips&quot;
};

    - 이렇게, 중첩된 객체타입을 고유한 타입이름으로 바꿔서 사용하면 코드와 오류메시지가 더 읽기 쉬워짐.
</code></pre>
<p>** 선택적속성 **</p>
<ul>
<li>모든객체에 객체타입 속성이 필요하진않다. 타입의 속성 애너테이션에서 : 앞에 ? 추가하면 선택적 속성.<pre><code class="language-javascript">//1.
type Book = {
author?: string;
pages: number;
};
const ok: Book = {
author: &quot;Rita Dove&quot;,
pages: 80,
};
const missing: Book = {
// Error: Property &#39;pages&#39; is missing in type
//    &#39;{pages: number;}&#39; but required in type &#39;Book&#39;.
author: &quot;Rita Dove&quot;
}
  - `선택적속성과 undefined를 포함함 유니언타입의 속성 사이에는 차이가 있다!!`
  - ?를 사용해 선택적으로 선언된 속성은 존재하지않아도 된다.
  - 필수로 선언된 속성과 | undefined 는 그 값이 undefined일지라도 반드시 존재해야함.
</code></pre>
</li>
</ul>
<p>//2.
type Writers = {
  author: string | undefined;
  editor?: string;
};
const hasRequired: Writers = {
  author: undefined
};
const missingRequired: Writers = {};
//Error: Property &#39;author&#39; is missing in type &#39;{}&#39; but required in type &#39;Writers&#39;;
    - Writers 타입의 editor 속성은 ? 를 사용해서 선언했으므로 변수를 선언할 때 생략이 가능하다.
    - author속성은 ? 가 없으므로 값이 undefined여도 반드시 존재해야한다.</p>
<pre><code>
### 객체타입유니언
- 타입스크립트 코드에서는 속성이 조금다른, 하나 이상의 서로 다른 객체타입이 될 수 있는 타입을 설명할 수 있어야 한다. 또한 속성값을 기반으로 해당 객체 타입 간에 타입을 좁혀야 할수도 있다.

** 유추 된 객체 타입 유니언 **
- 변수에 여러객체타입 중 하나가 될 수 있는 초기값이 주어지면 타입스크립트는 해당 타입을 객체 타입 유니언으로 유추한다. 
(유니언타입: 삼항연산자로 이거 아니면 저거인 타입)
- 유니언타입은 가능한 각 객체 타입을 구성하고 있는 요솔ㄹ 모두 가질 수 ㅇ씨다. 객체 타입에 정의된 각각의 가능한 속성으 ㄴ비로 ㄱ 초기값이 없는 선택적 타입이지만 각 객체 타입의 구성요소로 주어진다.
```javascript
//1.
const poem = Math.random() &gt; 0.5 ? {
  name: &quot;The Double Image&quot;, pages: 7
} : {
  name: &quot;Her kind&quot;, rhymes: true
};
// 타입:
// {
//   name: string;
//   pages: number;
//   rhymes?: undefined;
// } | {
//   name: string;
//   pages?: undefined;
//   rhymes: boolean;
// }
poem.name; //string;
poem.pages; //number | undefined
poem.rhymes; //booleans | undefined
</code></pre><p>** 명시 된 객체타입 유니언 **</p>
<ul>
<li>객체타입의 조합을 명시하면 객체타입을 더 명확히 정의할 수 있다.</li>
<li>코드를 더 작성해야하지만 객체타입을 더 많이 제어가능.</li>
<li>특히, 값의 타입이 객체타입으로 구성 된 유니언이라면 타입스크립트의 타입시스템은 이런 모든 유니언 타입에 존재하는 속성에 대한 접근만 허용.<pre><code class="language-javascript">//1.
type PoemWithPages = {
naem: string;
pages: number;
};
type PoemWithRhymes = {
name: string;
rhymes: boolean;
};
type Poem = PoemWithPages | PoemWithRhymes;
const poem: Poem = Math.random() &gt; 0.5
? {name: &quot;The Double Image&quot;, pages: 7}
: {name: &quot;Her Kind&quot;, rhymes: true};
poem.name;
poem.pages;
//Error: Property &#39;pages&#39; does not exist on type &#39;Poem&#39;.
//    Property &#39;rhymes&#39; does not exist on type &#39;PoemWithRhymes&#39;.
poem.rhymes;
//Error: Property &#39;rhymes&#39; does not exist on type &#39;Poem&#39;.
//    Property &#39;rhymes&#39; does not exist on type &#39;PoemWithPages&#39;.
  - 잠재적으로 존재하지 않는 객체의 멤버에 대한 접근을 제한하면 코드의 안전을 지킬 수 있다.
  - 값이 여러타입 중 하나인 경우, 모든 타입에 존재하지 않는 속성이 객체에 존재할거라 보장할 수 없다. 
</code></pre>
</li>
</ul>
<pre><code>

** 객체타입내로잉 **
- 타입검사기가 유니언 타입 값에  특정 속성이 포함된 경우에만 코드 영역을 실행할 수 있음을 알게 되면 , 값의 타입을 해당 속성을 포함하는 구성요소로만 좁힌다. 즉, 코드에서 객체의 형태를 확인하고 타입내로잉이 객체에 적용된다.

```javascript
//1.
if (&quot;pages&quot; in poem) {
  poem.pages;
} else {
  poem.rhymes;
}
    - poem의 pages가 타입스크립트의 타입가드 역할을 해서 PoemWithPages임을 나타내는지 확인한다.
    - 만일, Poem이 PoemWithPages가 아니라면 PoemWithRhymes이어야 함.
    - 타입스크립트는 if(poem.pages)와 같은 형식으로 참 여부를 확ㅇ니하는 것을 허용하지 않음.
    - 존재하지 않는 객체의 속성에 접근하려고 시도하면 타입 가드처럼 작동하는 방식으로 사용되더라도 타입오류로 간주됩니다. 
</code></pre><p>** 판별된 유니언 **</p>
<ul>
<li>자바스크립트와 타입스크립트에서 유니언 타입으로 된 객체의 또다른 인기있는 형태는 객체의 속성이 객체의 형태를 나타내도록하는 것.</li>
<li>이런 타입형태를 판별된 유니언이라 부르고,</li>
<li>객체의 타입을 가리키는 속성이 : 판별값 이다.</li>
<li>타입스_는 코드에서 판별 속성을 사용해 타입내로잉을 수행한다.</li>
</ul>
<pre><code class="language-javascript">//1. 
type PoemWithPages = {
  name: string;
  pages: number;
  type: &#39;pages&#39;;
};
type PoemWithRhymes = {
  name: string;
  rhymes: boolean;
  type: &#39;rhymes&#39;;
};
type Poem = PremWithPages | PoemWithRhymes;
const poem: Poem = Math.random() &gt; 0.5
  ? {name: &quot;the double Image&quot;, pages: 7, type: &quot;pages&quot;}
  : {name: &quot;Her kind&quot;, rhymes: true, type: &quot;rhymes&quot;};
if (poem.type === &quot;pages&quot;) {
  console.log(&#39;It&#39;s got pages: ${poem.pages}&#39;);
} else {
  console.log(&#39;It rhymes: ${poem.rhymes}&#39;);
}
poem.type;
poem.pages;
    - Poem타입은 PoemWithPages타입 또는 PoemWithRhymes타입 둘 다 될 수 있는 객체를 설명하고,
    - type 속성으로 어느 타입인지를 나타낸다.
    - 만일 poem.type이 pages이면, 타입스_는 poem을 PoemWithPages로 유추한다.
    - 타입내로잉 없이는 값에 존재하는 속성을 보장할 수 없다.
</code></pre>
<p>** 교차타입 **</p>
<ul>
<li>타입스크립트 유니언 타입은 둘 이상의 다른 타입 중 하나의 타입이 될 수 있다는 것을 나타낸다.</li>
<li>자바스크립트의 런타임 | 연산자가 &amp;연산자에 대응하는 역할을 하는 것처럼, 타입스크립트에서도 <code>&amp; 교차타입 intersection type</code>을 사용해 여러타입을 동시에 나타낸다. </li>
<li>교차타입은 일반적으로 여러 기존객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성한다.</li>
</ul>
<pre><code class="language-javascript">//1.
type Artwork = {
  genre: string;
  name: string;
};
type Writing = {
  pages: number;
  name: string;
};
type WrittenArt = Artwork &amp; Writing;
// 다음과 같음:
// {
//   genre: string;
//   name: string;
//   pages: number;
// }
    - Artwork 와 Writing 타입은 genre, name, pages 속성을 결합한 WrittenArt 타입을 형성하는데 사용됨.
    - 교차타입은 유니언타입과 결합할 수 있으며, 이는 하나의 타입으로 판별된 유니언타입을 설명하는데 유용.


//2.
type ShortPoem = {author: string} &amp; (
  | {kigo: string; type: &quot;haiku&quot;;}
  | {meter: number; type: &quot;villanelle&quot;;}
);
const morningGlory: ShortPoem = {
  author: &quot;Fukuda Chiyo-ni&quot;,
  kigo: &quot;Morning Glory&quot;,
  type: &quot;haiku&quot;
};
const oneArt: ShortPoem = {
  //Error: Type &#39;{author: string, type: &quot;villanelle&quot;}&#39;
  //    is not assignable to type &#39;ShortPoem&#39;.
  //    Type &#39;{author: string, type: &quot;villanelle&quot;}&#39; is not assignable to
  //    type &#39;{author: string;} &amp; {meter: number; type: &quot;villanelle&quot;}&#39;
  //    Property &#39;meter&#39; is missing in type &#39;{author: string, type: &quot;villanelle&quot;}&#39;
  //    but required in type &#39;{meter: string, type: &quot;villanelle&quot;}&#39;
  author: &quot;Elizabeth Bishop&quot;,
  type: &quot;villanelle&quot;
};
    - ShortPoem 타입은 항상 author 속성을 가지며 하나의 type 속성으로 판별된 유니언타입이다.
</code></pre>
<p>** 긴 할당 가능성 오류 **</p>
<pre><code class="language-javascript">//1.

type ShortPoemBase = {author: string;};
type Haiku = ShortPoemBase &amp; {kigo: string; type: &#39;haiku&#39;};
type Villanelle = ShortPoemBase &amp; {meter: number; type: &#39;villanelle&#39;};
type ShortPoem = Haiku | Villanelle;

const onArt: ShortPoem = {
  //Error: Type &#39;{author: string, type: &quot;villanelle&quot;}&#39;
  //    is not assignable to type &#39;ShortPoem&#39;.
  //    Type &#39;{author: string, type: &quot;villanelle&quot;}&#39; is not assignable to type &#39;Villanelle&#39;.
  //    Property &#39;meter&#39; is missing in type &#39;{author: string, type: &quot;villanelle&quot;}&#39;
  //    but required in type &#39;{meter: number, type: &quot;villanelle&quot;}&#39;
  author: &quot;Elizabeth Bishop&quot;,
  type: &quot;villanelle&quot;
};</code></pre>
<p>** never **</p>
<ul>
<li>교차타입은 잘못 사용하기 쉽고 불가능한 타입을 생성한다. 원시타입의 값은 동시에 여러 타입이 될 수 없기 때문에 교차타입의 구성요소로 함께 결합할 수 없다. 두개의 원시타입을 함께 시도하면 never키워드로 표시되는 never타입이 된다.<pre><code class="language-javascript">type NotPossible = number &amp; string;
  - never키워드와 never타입은 프로그래밍 언어에서 bottom타입 또는 empty타입을 뜻한다.
  - bottom타입은 값을 가질 수 없고 참조할 수 없는 타입이므로 bottom 타입에 그 어떠한 타입도 제공못함.
</code></pre>
</li>
</ul>
<p>const notNumber: NotPossible = 0;
// Error: Type &#39;number&#39; is not assingnable to type &#39;never&#39;.
const notString: never = &#39;&#39;;
// Error: Type &#39;string&#39; is not assignable to type &#39;never&#39;.
    - 대부분의 타입스크립트 프로젝트는 never타입을 거의 사용하지 않지만 코드에서 불가능한 상태를 나타내기 위해 가끔 등장.
    - 하지만 대부분 교차타입을 잘못 사용해 발생한 실수일 가능성이 높다.</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[reactquery1]]></title>
            <link>https://velog.io/@heejeen-choi/reactquery1</link>
            <guid>https://velog.io/@heejeen-choi/reactquery1</guid>
            <pubDate>Mon, 03 Apr 2023 00:46:45 GMT</pubDate>
            <description><![CDATA[<h3 id="reactquery">ReactQuery</h3>
<ul>
<li><p>서버에서 데이터를 받아와서 캐싱하고 동기화하는 작업을 쉽게 만들어주는 서버상태관리 라이브러리</p>
</li>
<li><p><a href="https://tanstack.com/query/v3/">https://tanstack.com/query/v3/</a></p>
</li>
<li><p>Context API, 리덕스나 리코일 같은 상태관리 라이브러리는 클라이언트의 데이터 관리에는 적합하지만, 서버 데이터 관리에는 적합하지 않다, 이러한 단점을 보완하는 것이 <code>리액트쿼리</code></p>
</li>
<li><p>서버와 통신하는 코드를 간결하게 만들 수 있다.</p>
</li>
<li><p>서버값을 클라이언트에 가져오거나 캐싱,값업데이트,에러핸들링 등 비동기 과정을 편하게하는데에 사용</p>
</li>
<li><p>서버로부터 값을 가져오거나 업데이트하는 로직을 store내부에 개발하는 경우가 많다,</p>
</li>
<li><p>store는 클라이언트 state를 유지해야 하는데 어느순간부터 클라이언트데이터와 서버데이터가 공존하게 되고 그 데이터가 서로 상호작용하면서 서버데이터도 클라이언트데이터도 아닌............끔혼탄</p>
</li>
<li><p>그래서, react-query를 사용함으로 서버, 클라이언트 데이터를 분리한다.</p>
</li>
</ul>
<h3 id="react-query-장점">react-query 장점</h3>
<pre><code class="language-javascript">- 캐싱
- get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행한다. (예: 게시판의 글을 가져왔을때 게시판의 글을 생성하면 게시판 글을 get하는 api를 자동으로 실행)
- 데이터가 오래됐다고 판단되면 다시 get (invalidateQueries)
- 동일 데이터 여러번 요청하면 한번만요청한다. (옵션에 따라 중복 호출 허용 시간 조절 가능)
- 무한스크롤 (Infinite Queries)
- 비동기 과정을 선언적으로 관리할 수 있다.
- react hook과 사용구조비슷

$ yarn install react-query

1. 먼저 react의 가장 기본이 되는 곳에 react-query를 사용하도록 세팅</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Error]]></title>
            <link>https://velog.io/@heejeen-choi/Error</link>
            <guid>https://velog.io/@heejeen-choi/Error</guid>
            <pubDate>Fri, 31 Mar 2023 07:32:45 GMT</pubDate>
            <description><![CDATA[<ul>
<li>ReferenceError : 현재 범위에서 존재하지 않거나 초기화되지 않은 변수를 참조했을 때 발생</li>
<li>TypeError : 변수나 인자가 참조하고 있는 인스턴스를 잘못 사용할 때 발생<pre><code>  (코드에서 기대한 값이 변수에 들어있지않을때. 즉, 기대했던 타입의 값이 아닐 때 발생함)</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[announcement-manage]list,detail.tsx_React-query]]></title>
            <link>https://velog.io/@heejeen-choi/announcement-managelistdetail.tsxReact-query%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@heejeen-choi/announcement-managelistdetail.tsxReact-query%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Thu, 30 Mar 2023 12:12:51 GMT</pubDate>
            <description><![CDATA[<h3 id="listtsx">List.tsx</h3>
<pre><code class="language-javascript">// announctment-manage/src/pages/List.tsx


import React, {PropsWithChildren} from &#39;react&#39;;
import {Link} from &quot;react-router-dom&quot;;
import {Layout, Pagenator} from &#39;@dscience/layout&#39;;
import {useQuery} from &quot;react-query&quot;;
import axios from &quot;axios&quot;;
import {Announcement} from &quot;../types/Types&quot;;


const List = (props: PropsWithChildren) =&gt; {
    //
    let appName = &quot;/-/announcement&quot;;
    let appIndex = document.location.pathname.indexOf(&quot;/-/announcement&quot;)
    let workspace = document.location.pathname.substring(0, appIndex);
    //
    // const [announcements, setAnnouncements] = useState&lt;Announcement[]&gt;([]);

    // useEffect(() =&gt; {
    //     queryAnnouncements();
    // }, []);

    // const queryAnnouncements = async () =&gt; {
    //     const response = await fetch(&#39;/api/announcement/dsstore/dssdlabs/-/announcement&#39;, {
    //         method: &#39;GET&#39;,
    //         headers: {
    //             &#39;Content-Type&#39;: &#39;application/json&#39;,
    //             &#39;service&#39;: &#39;announcement&#39;,
    //             &#39;query&#39;: &#39;QueryAnnouncements&#39;
    //         }
    //     });
    //     if (403 === response.status) {
    //         alert(&#39;error&#39;);
    //     }
    //     if (200 === response.status) {
    //         setAnnouncements(await response.json());
    //     }
    // };

    // ** const {} = useQuery()
    const {
        isLoading,
        error,
        data,
        isFetching
    } = useQuery({
        queryKey: [&quot;queryAnnouncements&quot;],
        queryFn: () =&gt;
            axios.get(&#39;/api/announcement/dsstore/dssdlabs/-/announcement&#39;, {
                method: &#39;GET&#39;,
                headers: {
                    &#39;Content-Type&#39;: &#39;application/json&#39;,
                    &#39;service&#39;: &#39;announcement&#39;,
                    &#39;query&#39;: &#39;QueryAnnouncements&#39;
                }
            }).then((response) =&gt;
                response.data as Announcement[]
            )
    })
    if (isLoading) console.log(&quot;Loading...&quot;);
    if (isFetching) console.log(&quot;isFetching...&quot;);
    if (error) console.log(&quot;Error&quot;);

    return &lt;Layout&gt;
        &lt;div className=&#39;container text-end&#39;&gt;
            &lt;Link className=&quot;btn btn-sm btn-primary&quot; to={`${document.location.pathname}/new`}&gt;New&lt;/Link&gt;
        &lt;/div&gt;
        &lt;table className=&quot;table table-striped table-hover&quot;&gt;
            &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;제목&lt;/th&gt;
                &lt;th&gt;내용&lt;/th&gt;
                &lt;th&gt;작성자&lt;/th&gt;
            &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;

            {data &amp;&amp; (
                &lt;&gt;
                    {data.map((item, index) =&gt; {
                        return (
                            &lt;tr key={index}&gt;
                                &lt;td&gt;&lt;Link to={`${workspace}${appName}/${item.id}`}&gt;{item.title}&lt;/Link&gt;&lt;/td&gt;
                                &lt;td&gt;{item.content}&lt;/td&gt;
                                &lt;td&gt;{item.tenancy.name}&lt;br/&gt;({item.tenancy.email})&lt;/td&gt;
                            &lt;/tr&gt;
                        )
                    })}
                &lt;/&gt;
            )}
            &lt;/tbody&gt;
            // &lt;tbody&gt;

            // {announcements.map(function(item, index){ 
            //return (
            //    &lt;tr key={index}&gt;
            //        &lt;td&gt;&lt;Link to={`${workspace}${appName}/${item.id}`}&gt;{item.title}&lt;/Link&gt;&lt;/td&gt;
            //        &lt;td&gt;{item.content}&lt;/td&gt;
            //        &lt;td&gt;{item.tenancy.name}&lt;br/&gt;({item.tenancy.email})&lt;/td&gt;
            //    &lt;/tr&gt;
            //)
            //})}
            // &lt;/tbody&gt;
        &lt;/table&gt;
        &lt;Pagenator totalOfItems={23} page={1} totalOfPages={3}/&gt;
    &lt;/Layout&gt;
        ;
}

export default List;
</code></pre>
<h2 id="1querykey란">1.queryKey란??</h2>
<pre><code class="language-javascript">* 위 코드 분석

const {
        isLoading,
        error,
        data,
        isFetching
    } = useQuery({
        queryKey: [&quot;queryAnnouncements&quot;],
        queryFn: () =&gt;
            axios.get(&#39;/api/announcement/dsstore/dssdlabs/-/announcement&#39;, {
                method: &#39;GET&#39;,
                headers: {
                    &#39;Content-Type&#39;: &#39;application/json&#39;,
                    &#39;service&#39;: &#39;announcement&#39;,
                    &#39;query&#39;: &#39;QueryAnnouncements&#39;
                }
            }).then((response) =&gt;
                response.data as Announcement[]
            )
    })

- useQuery: React Query를 이용해 서버로부터 데이터를 조회해올때 사용(select)
    (useMutation: 데이터 조회말고 데이터 변경 작업을 할 때 사용)
- useQuery를 코드로 작성하여 구현하려면 2가지 개념을 알아야한다.
    (1) queryKey
    (2) queryFn
- 다음과 같은 형태로 사용된다.
    (1) const res = useQuery(queryKey, queryFn);
    (2) const res = useQuery({
            queryKey: queryKey,
          queryFn: queryFn
           })
    - qyeryKey : useQuery마다 부여되는 고유 Key값이다, 해당 Key값은 단순하게 문자열로 사용될 수도 있고 또한 배열의 형태로도 사용될 수 있기 때문에 실제로 사용 될 때는 다음과 같은 방식으로 코드가 작성된다.
        //문자열: 이렇게 문자열로 작성된 경우에는 자동으로 길이가 1인 배열로 인식하기 때문에 결과적으로는
        const res = useQuery(&#39;persons&#39;, queryFn);
        //배열1: 배열1과 동일한 queryKey로 작성된것이다, 또한,
        const res = useQuery([&#39;persons&#39;], queryFn);
        //배열2: 배열2와
        const res = useQuery([&#39;persons&#39;, &#39;addId&#39;], queryFn);
        //배열3: 배열3은 queryKey가 동일해보이지만 리액쿼리에게는 동일하지않은 쿼리키인데 이유는, queryKey가 할당될때 배열에 입력되는 순서도 보장해주기 때문이다.
        const res = useQuery([&#39;addId&#39;, &#39;persons&#39;], queryFn);
        //배열4,
        const res = useQuery([
          &#39;persons&#39;,
          {
            type: &#39;add&#39;,
            name: &#39;Id&#39;
          }
        ], queryFn);
</code></pre>
<h2 id="2querykey역할">2.queryKey역할??</h2>
<pre><code class="language-javascript">* queryKey의 역할은 : React Query가 query캐싱을 관리할 수 있도록 도와준다. 간단한 예를들면,

import React from &#39;react&#39;;
import axios from &#39;axios&#39;;
import {useQuery} from &#39;react-query&#39;;

const Query = (): JSX.Element =&gt; {
  const getPersons1 = () =&gt; {
    const res1 = useQuery([&#39;persons&#39;], queryFn1);
  }
  const getPersons2 = () =&gt; {
    const res2 = useQuery([&#39;persons&#39;], queryFn2);
  }

  return (
    &lt;div&gt;
      {getPersons1()}
      {getPersons2()}
    &lt;/div&gt;
  )
}

export default Query;


- res1과 res2가 동일한 queryKey를 사용하며 서버에 있는 데이터를 조회해오려고 한다.
- 일반적인 상황에서는 res1과 res2에 대한 모든 요청이 이루어지게 되므로 서버에 2개의 request가 전달될것,
- 하지만 위 코드에서는 서버에 1개의 request만 전달된다.
- 왜냐하면, res1에서 request를 서버에 전달하게 되면 res2에서는 이미 동일한 queryKey에 대한 결과값이 있기 때문에 추가요청을 하지않고 res1의 결과를 그대로 가져와 사용하기 때문이다.
- 또한 queryFn에 대해서는, queryFn이 다르게 정의되어있더라도 res2에서는 res1의 결과를 그대로 전달받기 때문에 queryFn1이 처리된 결과를 확인할 수 있다. 결국 위의 코드는 다음의 코드와 동일한 결과.

const Query = (): JSX.Elemtnt =&gt; {
  const getPersons1 = () =&gt; {
    const res1 = useQuery([&#39;persons&#39;], queryFn1);
  }
  const getPersons2 = () =&gt; {
    const res2 = useQuery([&#39;persons&#39;]);
  }

  return (
    &lt;div&gt;
      {getPersons1()}
      {getPersons2()}
    &lt;/div&gt;
  )
}

export default Query;</code></pre>
<h2 id="3queryfn">3.queryFn???</h2>
<ul>
<li>queryFn : query Function으로 <code>promis 처리가 이루어지는 함수</code></li>
<li>axios를 이용해서 서버에 API를 요청하는 코드라고 생각할 수 있고 다음과 같은 형태로 코드가 작성된다.</li>
</ul>
<pre><code class="language-javascript">//1.
const res = useQuery([
  &#39;person&#39;
], () =&gt; axios.get(&#39;http://localhost:8080/persons&#39;));

//2.
const res = useQuery({
  queryKey: [&#39;persons&#39;],
  queryFn: () =&gt; axios.get(&#39;http://localhost:8080/persons&#39;)
});
</code></pre>
<pre><code class="language-javascript">* 코드를 작성해보자

import React from &#39;react&#39;;
import axios from &#39;axios&#39;;
import styled from &#39;styled-components&#39;;
import {useQuery} from &#39;react-query&#39;;

interface Iperson {
  id: number;
  name: string;
  phone: string;
  age: number;
}

const Query = (): JSX.Element =&gt; {
  const getPersons = () =&gt; {
    const res = useQuery({
      queryKey: [&#39;persons&#39;],
      queryFn: () =&gt; axios.get(&#39;http://localhost:8080/persons&#39;)
    })
    //로딩 중일 경우
    if (res.isLoading) {
      return (
        &lt;LoadingText&gt; 로딩 중입니다. &lt;/LoadingText&gt;
      )
    }
    //결과값이 전달되었을 경우
    if (res.data) {
      const persons: Iperson[] = res.data.date;
      return (
        &lt;Person.Container&gt;
           {persons.map((person) =&gt; {
              return (
                &lt;Person.Box key={person.id}&gt;
                   &lt;Person.Title&gt; {person.id}. &lt;/Person.Title&gt;
                     &lt;Person.Text&gt; {person.name}. &lt;/Person.Text&gt;
                     &lt;Person.Text&gt; {person.age}. &lt;/Person.Text&gt;
                &lt;/Person.Box&gt;
              )
             })}
        &lt;/Person.Container&gt;
      )
    }
  }

  return (
    &lt;Wrapper&gt;
       {getPersons()}
    &lt;/Wrapper&gt;
  );
}

export default Query;


const Wrapper = styled.div`
    max-width: 728px;
    margin: 0 auto;`;
const LoadingText = styled.h3`text-align: center;`;
const Person = {
    Container: styled.div`padding: 8px;`,
    Box: styled.div`border-bottom: 2px solid olive;`,
    Title: styled.h2`
        display: inline-block;
        margin: 0 12px;
        line-height: 48px;`,
    Text: styled.span`margin: 0 6px;`
}
</code></pre>
<ul>
<li>코드를 실행해서 Network탭 확인해보면 : 단순히 페이지 전환만 했어도 지속적으로 persons를 호출하고 있다.</li>
<li>그 이유는, 자동으로 refetch가 이루어지고 있기 때문</li>
<li>refetch가 발생되는 이유는 : 해당 queryKey에 매핑되는 데이터가 fresh하지 않고 stale 해졌기 떄문이다.</li>
<li>stale의 의미 : 오래된 데이터</li>
<li>ReactQuery는 계속해서 refetch를 수행한다. default 값으로 staleTime은 0초이기 때문.</li>
<li>한번 데이터를 조회해오면 그 순간 바로 해당 데이터는 stale한 데이터이기 때문에 refetch가 계속 발생되는 것.</li>
<li>cacheTime : staleTime과 유사한 역할. <code>캐싱처리가 이루어지는 시간</code>을 의미<ul>
<li>default값 : 5분</li>
<li>그래서 queryKey에 매핑되는 데이터가 사용되지 않는 시점을 기준으로 5분이 지나지 않은 상태에서 해당 queryKey를 다시 호출할 경우, 이전에 가져왔던 데이터를 다시 보여준다.</li>
<li>하지만 5분 지나면 캐시가비지콜렉터 타이머가 실행되며 기존 데이터삭제처리, queryKey를 다시 호출, 다시 데이터요청하게 됨.</li>
</ul>
</li>
<li>즉, useQuery에는 staleTime, cacheTime 두 개념이 모두 존재하므로 둘중 하나라도 만족되지 않으면 서버에 다시 데이터 요청한다. 그래서 두 설정을 모두 고려하며 코드 구현해야함</li>
<li>useQuery를 작성할 때 staleTime, cacheTime을 설정한다. 1000=1초</li>
</ul>
<pre><code class="language-javascript">//1.
const res = useQuery(
  [&#39;person&#39;], 
  () =&gt; axios.get(&#39;http://localhost:8080/persons&#39;),
  {
    staleTime: 5000,  //5초
    cacheTime: Infinity      //제한없음
  });

//2.
const res = useQuery({
  queryKey: [&#39;persons&#39;],
  queryFn: () =&gt; axios.get(&#39;http://localhost:8080/persons&#39;),
  staleTime: 5000,
  cacheTime: Infinity
});</code></pre>
<h2 id="4refetch-window-focus-설정">4.refetch window focus 설정</h2>
<ul>
<li>단순 페이지전환만으로 refetch가 수행되는 이유 : default로 <code>window focus 설정</code>이 <code>true</code>로 되어있기 때문이다.</li>
<li>이러한 기능은 상황에 따라서 효율적이거나 필요없을 수도 있다.</li>
<li>그럴때는, <code>window focus</code> 설정을 <code>false</code>로 변경하여 staleTime이 지났더라도 focus가 다시 되는 것만으로 refetch가 발생되지 않게 설정가능</li>
</ul>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript">// announcement-manage/src/pages/Detail.tsx


import React, {useEffect, useState} from &#39;react&#39;;
import {Link, useParams} from &#39;react-router-dom&#39;;
import {Layout} from &#39;@dscience/layout&#39;;
import {TenantAuthority} from &#39;@dscience/language&#39;;
import {Announcement} from &#39;../types/Types&#39;;
import {plainToClass} from &#39;class-transformer&#39;;
import axios from &quot;axios&quot;;
import {useMutation, useQuery, useQueryClient} from &quot;react-query&quot;;

const Detail = () =&gt; {
    //
    let appName = &quot;/-/announcement&quot;;
    let appIndex = document.location.pathname.indexOf(&quot;/-/announcement&quot;)
    let workspace = document.location.pathname.substring(0, appIndex);

    //
    //const [announcement, setAnnouncement] = useState&lt;Announcement&gt;();
    const [authority, setAuthority] = useState&lt;TenantAuthority&gt;();
    //
    let {id} = useParams();
    //document.addEventListener(&quot;AutorityReady&quot;, (event) =&gt; {
    //  setAuthority(plainToClass(TenantAutority, (event as CustomEvent).detail));
    //});

    useEffect(() =&gt; {
        document.addEventListener(&quot;AuthorityReady&quot;, (event) =&gt; {
            setAuthority(plainToClass(TenantAuthority, (event as CustomEvent).detail));
        });
    }, [])


    const queryClient = useQueryClient();

    const {data} = useQuery({
        queryKey: [&quot;queryAnnouncement&quot;],
        queryFn: () =&gt; queryAnnouncement()
    });

    const queryAnnouncement = async () =&gt; {
        // const response = await fetch(`/api/announcement${workspace}${appName}?id=${id}`, {
        //     method: &quot;GET&quot;,
        //     headers: {
        //         &quot;Content-Type&quot;: &quot;application/json&quot;,
        //         &quot;service&quot;: &quot;announcement&quot;,
        //         &quot;query&quot;: &quot;QueryAnnouncement&quot;
        //     }
        // });
        // if (200 === response.status) {
        //     setAnnouncement(await response.json() as Announcement);
        // }
        const response = await axios.get(`/api/announcement${workspace}${appName}?id=${id}`, {
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;,
                &quot;service&quot;: &quot;announcement&quot;,
                &quot;query&quot;: &quot;QueryAnnouncement&quot;
            }
        })
        return response.data as Announcement;
    }

    const announcementMutation = useMutation({
        mutationFn: (variables: {
            command: string
        }) =&gt; mutateAnnouncement(variables.command === &#39;show&#39; ? &#39;ShowAnnouncement&#39; : &#39;HideAnnouncement&#39;),
        onSuccess: () =&gt; queryClient.invalidateQueries({queryKey: &#39;queryAnnouncement&#39;}),
        onError: ({response}) =&gt; {
            if (403 === response.status) alert(&#39;권한이 없습니다.&#39;)
        }
    });


    const mutateAnnouncement = async (command: &quot;ShowAnnouncement&quot; | &quot;HideAnnouncement&quot;) =&gt; {
        return await axios.put(`/api/announcement${workspace}${appName}`, {
            announcementId: id,
            version: data?.version
        }, {
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;,
                &quot;service&quot;: &quot;announcement&quot;,
                command
            }
        });
    }


   // const showAnnouncement = async() =&gt; {
   //     const response = await fetch(`/api/announcement${workspace}${appName}`, {
   //         method: &quot;PUT&quot;,
   //         headers: {
   //             &quot;Content-Type&quot;: &quot;application/json&quot;,
   //             &quot;service&quot;: &quot;announcement&quot;,
   //             &quot;command&quot;: &quot;ShowAnnouncement&quot;
   //         },
   //         body: JSON.stringify({
   //             announcementId: id,
   //             version: announcement?.version
   //         })
   //     });
   //     if (200 === response.status) {
   //         queryAnnouncement();
   //     }
   // }
   // const hideAnnouncement = async() =&gt; {
   //     const response = await fetch(`/api/announcement${workspace}${appName}`, {
   //         method: &quot;PUT&quot;,
   //         headers: {
   //             &quot;Content-Type&quot;: &quot;application/json&quot;,
   //             &quot;service&quot;: &quot;announcement&quot;,
   //             &quot;command&quot;: &quot;HideAnnouncement&quot;
   //         },
   //         body: JSON.stringify({
   //             announcementId: id,
   //             version: announcement?.version
   //         })
   //     });
   //     if (200 === response.status) {
   //         queryAnnouncement();
   //     }
   // }
   //
   // useEffect(() =&gt; {
   //     queryAnnouncement();
   // },[]);



    return &lt;Layout&gt;

       {/* &lt;p&gt;Detail&lt;/p&gt;
            {announcement?.title}&lt;br /&gt;
            {announcement?.content}&lt;br /&gt;
            {announcement?.tenancy.id}&lt;br /&gt;
            {announcement?.tenancy.name}&lt;br /&gt;
            {announcement?.tenancy.email}&lt;br /&gt;
            &lt;div className=&quot;row&quot;&gt;&lt;br /&gt;&lt;/div&gt;
            &lt;div className=&quot;row&quot;&gt;
                &lt;div className=&quot;col-6 text-start&quot;&gt;
                    {
                        announcement?.show
                            ? &lt;button onClick={hideAnnouncement} className=&quot;btn btn-sm btn-warning&quot;&gt;비공개&lt;/button&gt;
                            : &lt;button onClick={showAnnouncement} className=&quot;btn btn-sm btn-primary&quot;&gt;공개&lt;/button&gt;
                    }
                &lt;/div&gt;
                &lt;div className=&quot;col-6 text-end&quot;&gt;
                    {
                        //hasRole(&quot;Manager&quot;) 

                        authority &amp;&amp; authority.hasAuthority(workspace + appName, &quot;Manager&quot;)
                        ? announcement?.show
                            ? &lt;button onClick={hideAnnouncement} className=&quot;btn btn-sm btn-warning&quot;&gt;비공개&lt;/button&gt;
                            : &lt;button onClick={showAnnouncement} className=&quot;btn btn-sm btn-primary&quot;&gt;공개&lt;/button&gt;
                        : &lt;&gt;&lt;/&gt;
                    }
                    &lt;Link to={`${workspace}${appName}`} className=&quot;btn btn-sm btn-secondary&quot;&gt;목록&lt;/Link&gt;
                &lt;/div&gt;
            &lt;/div&gt; */}

        {data &amp;&amp; (&lt;&gt;
            &lt;p&gt;Detail&lt;/p&gt;
            {data.title}&lt;br/&gt;
            {data.content}&lt;br/&gt;
            {data.tenancy.id}&lt;br/&gt;
            {data.tenancy.name}&lt;br/&gt;
            {data.tenancy.email}&lt;br/&gt;
            &lt;div className=&quot;row&quot;&gt;&lt;br/&gt;&lt;/div&gt;
            &lt;div className=&quot;row&quot;&gt;
                &lt;div className=&quot;col-6 text-start&quot;&gt;
                    {
                  // data?.show ? &lt;button&gt; 비공개 &lt;/button&gt; : &lt;button&gt; 공개 &lt;/button&gt;
                        data?.show
                            ? &lt;button onClick={() =&gt; announcementMutation.mutate({
                                command: &#39;hide&#39;
                            })}
                                      className=&quot;btn btn-sm btn-warning&quot;&gt;비공개&lt;/button&gt;
                            : &lt;button onClick={() =&gt; announcementMutation.mutate({
                                command: &#39;show&#39;
                            })}
                                      className=&quot;btn btn-sm btn-primary&quot;&gt;공개&lt;/button&gt;
                    }
                &lt;/div&gt;
                &lt;div className=&quot;col-6 text-end&quot;&gt;
                    {
                        //hasRole(&quot;Manager&quot;)
                          // authority?.hasAuthority(workspace+appName, &quot;Manager&quot;) ? () : &lt;&gt;&lt;/&gt;
                        authority?.hasAuthority(workspace + appName, &quot;Manager&quot;)
                            ? (&lt;&gt;{data.show        // data.show ? &lt;button&gt;비공개&lt;/button&gt; : &lt;button&gt;공개&lt;/button&gt;
                            ? &lt;button onClick={() =&gt; announcementMutation.mutate({
                                command: &#39;hide&#39;
                            })}
                                      className=&quot;btn btn-sm btn-warning&quot;&gt;비공개&lt;/button&gt;
                            : &lt;button onClick={() =&gt; announcementMutation.mutate({
                                command: &#39;show&#39;
                            })}
                                      className=&quot;btn btn-sm btn-primary&quot;&gt;공개&lt;/button&gt;
                            }&lt;/&gt;) : &lt;&gt;&lt;/&gt;
                    }
                    &lt;Link to={`${workspace}${appName}`} className=&quot;btn btn-sm btn-secondary&quot;&gt;목록&lt;/Link&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/&gt;)}
    &lt;/Layout&gt;
}

export default Detail;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[javaScript]]></title>
            <link>https://velog.io/@heejeen-choi/javaScript</link>
            <guid>https://velog.io/@heejeen-choi/javaScript</guid>
            <pubDate>Thu, 30 Mar 2023 01:27:51 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">//047, p105

* 배열.forEach(콜백함수) : 배열의 요소 데이터 콜백함수로 실행
    - 주어진 콜백함수를 사용해 배열의 요소를 순서대로 처리한다.
    - 콜백함수는 해당요소 데이터, 인덱스, 기존배열정보를 가져온다.
    - 콜백함수에서 인덱스와 기존배열정보는 생략가능

const array = [&#39;a&#39;, &#39;d&#39;, &#39;v&#39;];

array.forEach((value, index) =&gt; {
  // 인덱스와 값을 순서대로 출력
  console.log(index, value);
});

// 결과 : 0 &#39;a&#39;, 1 &#39;d&#39;, 2 &#39;v&#39;

* forEach()는 for, for...of의 루프와 달리 map(), filter() 등의 반환값을 그대로 루프처리 할 수 있다.</code></pre>
<pre><code class="language-javascript">// 061, p128

* 배열.map(콜백함수) : 콜백함수로 새로운 배열생성
    - map()함수는 배열에서 요소를 추출하여 새로운 배열을 생성한다. 또한, 배열요소를 하나씩 처리하기 때문에 배열의 루프 처리 작업에도 활용되며 각 요소는 인수로 전달된 콜백함수에 의해 처리된다. map()은 forEach()와 비슷하지만 반환값이 존재한다.

const idList = [4, 10, 20];
const userIdList = idList.map((value) =&gt; `userid_${value}`);

console.log(userIdList);

// 결과 : [&quot;userid_4&quot;, &quot;userid_10&quot;, &quot;userid_20&quot;]


const apiResponseData = [
  {
    id: 10,
    name: &#39;aa&#39;
  },
  {
    id: 21,
    name: &#39;dd&#39;
  },
  {
    id: 31,
    name: &#39;gg&#39;
  }
];
const idList = apiResponseData.map((value) =&gt; value.id);
// const idList = apiResponseData.map(value =&gt; {return (value.id)}); 이렇게도 작성가능
console.log(idList);
// 결과: [10,21,31]

</code></pre>
<pre><code class="language-javascript">// 062, p130

* 배열.filter(콜백함수) : 콜백함수 조건을 만족하는 데이터의 배열생성
    - filter()는 콜백함수 조건에 만족하는 요소들을 새로운 배열로 생성한다.

const newArray = [10,20,30,40].filter((value) =&gt; value &gt;= 30);
console.log(newArray);

// [30,40]</code></pre>
<pre><code class="language-javascript">// 161, p332

* 텍스트박스값 읽어오기 input, text
- input요소의 type 속성을 text로 설정하면 &#39;텍스트입력폼이 표시&#39;된다.
- 텍스트 입력 폼은 유저로부터 임의의 텍스트를 입력받는다, 자바스크립트는 value 속성으로 요소를 참조하여 데이터값을 확인할 수 있고 value 속성은 string


//HTML
&lt;input
  id=&quot;myText&quot;
  type=&quot;text&quot;
  value=&quot;안녕하세요&quot;
/&gt;

//JavaScript
const element = document.querySelector(&#39;#myText&#39;);
const value = element.value;
console.log(value);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[context API , Provider]]></title>
            <link>https://velog.io/@heejeen-choi/context-API-Provider</link>
            <guid>https://velog.io/@heejeen-choi/context-API-Provider</guid>
            <pubDate>Wed, 29 Mar 2023 13:22:02 GMT</pubDate>
            <description><![CDATA[<h2 id="context-api를-사용한-전역-상태-관리-흐름">Context API를 사용한 전역 상태 관리 흐름</h2>
<ul>
<li>프로젝트 내에서 환경설정, 사용자정보같은 전역적으로 필요한 상태는 어떻게 관리??</li>
<li>리액트 애플리케이션은 컴포넌트 간에 데이터를 props로 전달하기 때문에 컴포넌트 여기저기서 필요한 데이터가 있을 때는 주로 최상위 컴포넌트인 App</li>
</ul>
<p><img src="https://velog.velcdn.com/images/heejeen-choi/post/20ab9231-c80a-4b9a-9e2e-7f3615f3ebb3/image.png" alt=""></p>
<ul>
<li>G컴포넌트 : 전역상태를 업데이트 시킨다.</li>
<li>F, J컴포넌트 : 업데이트 된 상태를 렌더링 한다.</li>
<li>즉, App컴포넌트에서는<pre><code class="language-javascript">const [value, setValue] = useState(&#39;hello&#39;);
const onSetValue = useCallback(value =&gt; setValue(value), []);</code></pre>
</li>
<li>이렇게 상태 및 업데이트함수를 정의해야한다.</li>
</ul>
<h3 id="context-api">Context API</h3>
<pre><code class="language-javascript">// contexts/color.js

import {createContext} from &#39;react&#39;;

const ColorContext = createContext({color: &#39;black&#39;});

export default ColorContext;
</code></pre>
<ul>
<li>ColorBox 라는 컴포넌트를 만들어서 ColorContext 안에 있는 색상을 보여준다</li>
<li>이때 색상은, props로 받아오는 것이 아니라 ColorContext 안에 들어있는 Consumer 라는 컴포넌트를 통해 색상을 조회<pre><code class="language-javascript">// components/ColorBox.js
</code></pre>
</li>
</ul>
<p>import ColorContext from &#39;../contexts/color&#39;;</p>
<p>const ColorBox = () =&gt; {</p>
<p>  return (
    &lt;ColorContext.Consumer&gt;
      {value =&gt; (
         &lt;div 
               style={{
                width: &#39;64px&#39;,
                 height: &#39;64px&#39;,
                 background: value.color
              }}
         /&gt;
       )}
    &lt;/ColorContext.Consumer&gt;
  );
};</p>
<pre><code>
```javascript
** Render Props 예제 **

  const RenderPropsSample = ( {children} ) =&gt; {

    return (
      &lt;div&gt;
        결과: {children(5)}
      &lt;/div&gt;
    );
  };

  export default RenderPropsSample;

    - 위와 같은 컴포넌트가 있다면 추후 사용할 때,

  &lt;RenderPropsSample&gt;{value =&gt; 2*value}&lt;/RenderPropsSample&gt;

    - 이렇게 사용할 수 있는데, RenderPropsSample 에게 children props로 파라미터에 2를 곱해서 반환하는 함수를 전달하면 해당 컴포넌트에서는 이 함수에 5를 인자로 넣어서 &quot;결과: 10&quot; 을 렌더링한다.
</code></pre><ul>
<li>ColorBox 컴포넌트를 App에서 렌더링하자,<pre><code class="language-javascript">import ColorBox from &#39;./components/ColorBox&#39;;
</code></pre>
</li>
</ul>
<p>const App = () =&gt; {</p>
<p>  return (
    <div>
      <ColorBox />
    </div>
  );
};</p>
<p>export default App;</p>
<pre><code>
```javascript
// App.js

import ColorBox from &#39;./components/ColorBox&#39;;
import ColorContext from &#39;./contexts/color&#39;;

const App = () =&gt; {

  return (
    &lt;ColorContext.Provider
      value={{color: &#39;red&#39;}}
      &gt;
      &lt;div&gt;
        &lt;ColorBox /&gt;
      &lt;/div&gt;
    &lt;/ColorContext.Provider&gt;
  );
};

export default App;


- Provider를 사용하면 Context의 value를 변경할 수 있다.
- 기존 createContext함수 사용했을 때는 파라미터로 Context의 기본값을 넣었고,
- 이 디폴트값은 Provider를 사용하지 않았을 때만 사용된다.
- Provider 사용했는데 value를 명시하지 않았다면 오류발생</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Techniques for dealing with REACT_immer]]></title>
            <link>https://velog.io/@heejeen-choi/Techniques-for-dealing-with-REACTimmer</link>
            <guid>https://velog.io/@heejeen-choi/Techniques-for-dealing-with-REACTimmer</guid>
            <pubDate>Wed, 29 Mar 2023 12:30:10 GMT</pubDate>
            <description><![CDATA[<p>$ yarn creat react-app immer-tutorial
$ cd immer-tutorial
$ yarn add immer</p>
<h2 id="immer를-사용하지-않고-불변성-유지">immer를 사용하지 않고 불변성 유지</h2>
<pre><code class="language-javascript">//App.js

import {useRef, useCallback, useState} from &#39;react&#39;;

const App = () =&gt; {


  return (
    &lt;div&gt;
       &lt;form onSubmit={onSubmit}&gt;
             &lt;input
             name=&quot;userName&quot;
             placeholder=&quot;아이디&quot;
             value={form.userName}
             onChange={onChange}
          /&gt;
          &lt;input
             name=&quot;name&quot;
             placeholder=&quot;이름&quot;
             value={form.name}
             onChange={onChange}
          /&gt;
          &lt;button type=&quot;submit&quot;&gt;등록&lt;/button&gt;
       &lt;/form&gt;
       &lt;div&gt;
             &lt;ul&gt;
            {data.array.map(info =&gt; (
               &lt;li 
                  key={info.id}
                 onClick={() =&gt; onRemove(info.id)}
               &gt;
                  {info.userName} ({info.name})
               &lt;/li&gt;
             ))  
          &lt;/ul&gt;     
       &lt;/div&gt;
    &lt;/div&gt;
  )
};

export default App;
</code></pre>
<h3 id="axios-로-api-호출해서-데이터-받아오기">axios 로 API 호출해서 데이터 받아오기</h3>
<ul>
<li>axios : 자바스크립트 HTTP 클라이언트</li>
<li>이 라이브러리의 특징 : HTTP요청을 Promise 기반으로 처리한다.</li>
</ul>
<pre><code class="language-javascript">$ yarn create react-app news-viewer
$ cd news-viewer
$ yarn add axios

// .prettierrc (코드 스타일을 자동으로 정리하고 싶을 때 프로젝트 루트에 파일 수동생성 입력)

{
  &quot;singleQuote&quot;: true,
  &quot;semi&quot;: true,
  &quot;useTabs&quot;: false,
  &quot;tabWidth&quot;: 2,
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;printWidth&quot;: 80
}


// App.js
// 불러오기 버튼을 누르면 JSONPlaceholder의 가짜 API 호출하고 응답을 컴포넌트 상태에 넣어서 보여주는 예제코드

import React, {useState} from &#39;react&#39;;
import axios from &#39;axios&#39;;

const App = () =&gt; {

  return (
    &lt;div&gt;
      &lt;div&gt;
        // onClick함수에서는 axios.get 함수를 사용
        // 이 함수는 파라미터로 전달된 주소에 GET요청을 해주고 결과는 .then을 통해 비동기적으로 확인
        &lt;button onClick={onClick}&gt; 불러오기 &lt;/button&gt;
      &lt;/div&gt;
//      {data &amp;&amp; &lt; /&gt;}
      {data &amp;&amp; 
        &lt;textarea 
               rows={7} 
               value={JSON.stringify(data, null, 2)} 
            readOnly={true} 
        /&gt;
      }
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<ul>
<li>위 코드 + async 적용<pre><code class="language-javascript">// App.js
</code></pre>
</li>
</ul>
<p>import React, {useState} from &#39;react&#39;;
import axios from &#39;axios&#39;;</p>
<p>const App = () =&gt; {
  const [data, setDate] = useState(null);
  // 화살표함수에 async/await 적용할 때는, async () =&gt; {} 형식으로 적용함.
  const onClick = async () =&gt; {
    try {
      const response = await axios.get(&#39;<a href="http://jsonplaceholder.typicode.com/todos/1&#39;">http://jsonplaceholder.typicode.com/todos/1&#39;</a>);
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };</p>
<p>  return (
    <div>
      <div>
        // onClick함수에서는 axios.get 함수를 사용
        // 이 함수는 파라미터로 전달된 주소에 GET요청을 해주고 결과는 .then을 통해 비동기적으로 확인
        <button onClick={onClick}> 불러오기 </button>
      </div>
//      {data &amp;&amp; &lt; /&gt;}
      {data &amp;&amp; 
        &lt;textarea 
               rows={7} 
               value={JSON.stringify(data, null, 2)} 
            readOnly={true} 
        /&gt;
      }
    </div>
  );
};</p>
<p>export default App;</p>
<pre><code>
```javascript
</code></pre><pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Router_SPA]]></title>
            <link>https://velog.io/@heejeen-choi/React-RouterSPA</link>
            <guid>https://velog.io/@heejeen-choi/React-RouterSPA</guid>
            <pubDate>Wed, 29 Mar 2023 12:29:28 GMT</pubDate>
            <description><![CDATA[<h3 id="라우팅">라우팅</h3>
<pre><code class="language-javascript">- 라우팅 : 사용자가 요청한 URL에 따라 알맞은 페이지를 보여주는 것
- 웹 애플리케이션을 만들 때 프로젝트를 하나의 페이지로 구성할 수도 있고 여러 페이지로 구성할 수도 있다.
- 여러페이지로 구성된 웹 애플리케이션을 만들 때 페이지 별로 컴포넌트들을 분리해가면서 프로젝트를 관리하기 위해 필요함
- 리액트에서 라우트 시스템을 구축하기 위해 사용할 수 있는 선택지2
    (1) React Router 라이브러리 : 컴포넌트 기반으로 라우팅 시스템을 설정할 수 있다.
    (2) Next.js : 리액트프로젝트의 프레임워크. creat-react-app처럼 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어시스템 지원, ssr 등 다양한 기능 제공, 파일경로 기반으로 작동함.
- 라우팅 관련 기능은 리액트 라이브러리에서 공식적으로 지원하는 것이 아니고 서드파티로 제공</code></pre>
<h3 id="싱글-페이지-애플리케이션">싱글 페이지 애플리케이션</h3>
<pre><code class="language-javascript">- 하나의 페이지로 이루어진 애플리케이션 (이전: 멀티 페이지 애플리케이션)
- MPA : 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고 페이지를 로딩 때마다 서버에서 css, js, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여줌. 각 페이지마다 다른 html파일을 만들어서 제공하거나, 데이터에 따라 유동적 html 생성해주는 템플릿 엔진 사용하기도 함
- 사용자 인터렉션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방식이 적합하지 않다. 서버의 자원, 트래픽 문제
- 리액트 같은 라이브러리 사용해서 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고 새로운 데이터 필요하면 서버 API 호출해서 필요 데이터만 새로 불러옴
- SPA : html을 한번만 받아와서 웹 애플리케이션 실행 시킨 후 이후에는 필요한 데이터만 받아와서 화면에 업데이트하는 것
- 기술적으로는 한 페이지만 존재하지만, 사용자 경험으로는 여러 페이지가 존재하는 것처럼 느껴진다.
- 리액트 라우터 같은 라우팅 시스템은 : 사용자 브라우저 주소창의 경로에 따라 알맞은 페이지를 보여주는데 링크를 눌러서 다른 페이지로 이동시 다른 페이지의 html을 새로 요청하는 것이 아니라 브라우저의 History API를 사용하여 브라우저의 주소창의 값만 변경하고 기존에 페이지에 띄웠던 웹 애플리케이션 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여줌</code></pre>
<h3 id="리액트라우터-적용-및-기본-사용">리액트라우터 적용 및 기본 사용</h3>
<pre><code class="language-javascript">$ yarn create react-app 새로운프로젝트
$ cd 새로운프로젝트
$ yarn add react-router-dom

- 프로젝트에 리액트 라우터 적용할 때는 src/index.js 에서 react-router-dom에 내장되어 있는 `BrowserRouter` 라는 컴포넌트를 사용하여 감싸면 된다.
- 페이지 새로 불러오지 않아도 HTML5의 History API 사용하여 주소변경, 현재 주소경로에 관련 정보를 리액트 컴포넌트에서 사용할 수 있게 해준다.


//src/index.js

import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import {BrowserRouter} from &#39;react-router-dom&#39;;

ReactDOM.render(
  &lt;BrowserRouter&gt;
       &lt;App /&gt;
  &lt;/BrowserRouter&gt;
  document.getElementById(&#39;root&#39;)
);
</code></pre>
<ul>
<li>페이지 컴포넌트 만들기<ul>
<li>리액트라우터를 통해 여러 페이지로 구성된 웹 애플리케이션 만들기 위해 각 페이지에서 사용할 컴포넌트를 만들자.</li>
<li>사용자가 웹사이트 처음 들어왔을 때 가장 먼저 보여지게 될 (1)Home 페이지 컴포넌트, 웹사이트 소개하는 (2)About 페이지 컴포넌트</li>
<li>src 디렉토리에 pages 경로를 만들고 그 안에 파일들 생성<pre><code class="language-javascript">// src/pages/Home.js
</code></pre>
</li>
</ul>
</li>
</ul>
<p>const Home = () =&gt; {
  return (
    <div>
      <h1> 홈 </h1>
      <p> 이 페이지는 가장 먼저 보여지는 화면입니다. </p>
    </div>
  )
}</p>
<pre><code>
``` javascript
// src/pages/About.js

const About = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h1&gt; 페이지 소개 &lt;/h1&gt;
      &lt;p&gt; 리액트라우터를 사용해보는 페이지입니다. &lt;/p&gt;
    &lt;/div&gt;
  );
};

export default About;


- 페이지로 사용할 이러한 컴포넌트들을 꼭 pages경로에 넣을 필요는 없다.
- 이것은 단순히, 페이지를 위한 컴포넌트들을 다른 파일들과 구분하기 위함이고, routes라는 이름을 써도 되고 그냥 src 경로에 바로 생성해도 문제가 되지는 않음.</code></pre><h3 id="route-컴포넌트로-특정-경로에-원하는-컴포넌트-보여주기">Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기</h3>
<ul>
<li>사용자의 브라우저 주소 경로에 따라 우리가 원하는 컴포넌트를 보여주려면 : Route 라는 컴포넌트를 통해 라우트설정을 해줘야 한다.</li>
<li>Route 컴포넌트는   <Route path="주소규칙" element={보여줄컴포넌트JSX} />
이렇게 사용하고, Route컴포넌트는 Routes컴포넌트 내부에서 사용되어야 한다.

</li>
</ul>
<pre><code class="language-javascript">// src/App.js

import {Route, Routes} from &#39;react-router-dom&#39;;
import About from &#39;./pages/About&#39;;
import Home from &#39;./pages/Home&#39;;

const App = () =&gt; {
  return (
    &lt;Routes&gt;
      &lt;Route path=&quot;/&quot; elements={&lt;Home /&gt;} /&gt;
      &lt;Route path=&quot;/about&quot; element={&lt;About /&gt;} /&gt;
    &lt;/Routes&gt;
  );
};

export default App;</code></pre>
<h3 id="link컴포넌트">Link컴포넌트</h3>
<ul>
<li>웹페이지에서는 원래 링크를 보여줄 때 a태그를 사용하는데, 리액트라우터를 사용하는 프로젝트에서는 a태그를 바로 사용하면 안된다. 왜냐하면 a태그를 클릭하여 페이지를 이동할 때 브라우저에서는 페이지를 새로불러오기때문,</li>
<li>Link컴포넌트도 a태그를 사용하긴 하지만, 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 있음  <Link to="경로"> 링크이름 </Link>
  이렇게 사용함

</li>
</ul>
<pre><code class="language-javascript">- Home페이지에서 About페이지로 이동할 수 있도록 Link 컴포넌트를 Home페이지컴포넌트에서 사용해보기,

// src/pages/Home.js

import {Link} from &#39;react-router-dom&#39;;

const Home = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h1&gt; 홈 &lt;/h1&gt;
      &lt;p&gt; 가장먼저 보여지는 페이지입니다. &lt;/p&gt;
      &lt;Link to=&quot;/about&quot;&gt; 소개 &lt;/Link&gt;
    &lt;/div&gt;
  )
}</code></pre>
<h3 id="url-파라미터와-쿼리스트링">URL 파라미터와 쿼리스트링</h3>
<ul>
<li><p>URL 파라미터 예시 : /profile/ikjoonhee</p>
</li>
<li><p>쿼리스트링 예시 : /articles?page=1&amp;keyword=react</p>
</li>
<li><p>URL파라미터는, 주소의 경로에 유동적인 값을 넣는 형태이고,</p>
</li>
<li><p>쿼리스트링은, 주소의 뒷부분에 ? 문자열 이후에 key=value 로 값을 정의하며 &amp; 로 구분하는 형태이다.</p>
</li>
<li><p>URL파라미터는 주로, ID 또는 이름을 사용하여 특정 데이터를 조회할 때 사용,</p>
</li>
<li><p>쿼리스트링(Querystring)은 키워드검색, 페이지네이션, 정렬방식 등 데이터조회에 필요한 옵션을 전달할 때 사용</p>
</li>
</ul>
<h3 id="1-url파라미터">1. URL파라미터</h3>
<pre><code class="language-javascript">// src/pages/Profile.js

import {useParams} from &#39;react-router-dom&#39;;

const data = {
  velopert: {name: &#39;kim&#39;, description: &#39;reactDev&#39;},
  gildong: {name: &#39;lee&#39;, description: &#39;hongName&#39;}
};

const Profile = () =&gt; {
  // URL파라미터는 useParams라는 Hook을 사용하여 객체 형태로 조회할 수 있다.
  // URL파라미터의 이름은, 라우트설정을 할 때 Route컴포넌트의 path props를 통해 설정한다.
  const params = useParams();
  const profile = data[params.username];

  return (
    &lt;div&gt;
      &lt;h1&gt; 사용자프로필 &lt;/h1&gt;
      // 삼항연산자 {profile ? () : ()}
      {profile ? (
        &lt;div&gt;
          &lt;h2&gt; {profile.name} &lt;/h2&gt;
          &lt;p&gt; {profile.description} &lt;/p&gt;
        &lt;/div&gt;
        ) : (
        &lt;p&gt; 존재하지 않는 프로필입니다.&lt;/p&gt;
        )}
    &lt;/div&gt;
  );
}

- 여기서는, data객체에 예시프로필 정보들을 key-value 형태로 담았고,
- Profile컴포넌트에서는 username URL파라미터를 통하여 프로필을 조회한 뒤에 true, false 결과값 보여지도록
</code></pre>
<pre><code class="language-javascript">// src/App.js

import {Route, Routes} from &#39;react-router-dom&#39;;
import About from &#39;./pages/About&#39;;
import Home from &#39;./pages/Home&#39;;
import Profile from &#39;./pages/Profile&#39;;

const App = () =&gt; {
  return (
    &lt;Routes&gt;
      &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
      &lt;Route path=&quot;/about&quot; element={&lt;About /&gt;} /&gt;
      &lt;Route path=&quot;/profiles/:username&quot; element={&lt;Profile /&gt;} /&gt;
    &lt;/Routes&gt;
  );
};

export default App;


- URL파라미터는, /profiles/:username 처럼 경로에 :를 사용하여 설정
- 만약, URL파라미터가 여러개면, /profiles/:username/:field 같은 형태로 설정할 수 있다.
</code></pre>
<ul>
<li>Profile페이지로 이동할 수 있도록 Home 페이지에 Link를 더 만들기,</li>
<li>링크가 여러개라서 ul태그를 사용하여 리스트로 보여주자,<pre><code class="language-javascript">// src/pages/Home.js
</code></pre>
</li>
</ul>
<p>import {Link} from &#39;react-router-dom&#39;;</p>
<p>const Home = () =&gt; {
  return (
    <div>
      <h1> 홈 </h1>
      <p> 가장먼저 보여지는 페이지입니다. </p></p>
<pre><code>  &lt;ul&gt;
    &lt;li&gt;
      &lt;Link to=&quot;/about&quot;&gt; 소개 &lt;/Link&gt;
    &lt;/li&gt;

    &lt;li&gt;
      &lt;Link to=&quot;/profiles/velopert&quot;&gt; velopert님의 프로필 &lt;/Link&gt;
    &lt;/li&gt;

    &lt;li&gt;
      &lt;Link to=&quot;/about&quot;&gt; 소개 &lt;/Link&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</code></pre><p>  );
};</p>
<p>export default Home;</p>
<pre><code>
### 쿼리스트링

``` javascript
// src/pages/About.js

import {useLocation} from &#39;react-router-dom&#39;;

const About = () =&gt; {
  const location = useLocation();

  return (
    &lt;div&gt;
      &lt;h1&gt; 소개 &lt;/h1&gt;
      &lt;p&gt; 리액트 라우터를 사용해보는 프로젝트입니다. &lt;/p&gt;
      &lt;p&gt; 쿼리스트링: {location.search} &lt;/p&gt;
    &lt;/div&gt;
  );
};

export default About;

- useLocation 이라는 Hook : location 객체를 반환한다.
- 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있다. 
    * pathname : 현재주소의 경로(쿼리스트링 제외)
    * search : 맨 앞의 ? 문자를 포함한 쿼리스트링 값
    * hash : 주소의 # 문자열 뒤의 값 (주로 History API가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시라우터에서 사용)
    * state : 페이지로 이동할 때 임의로 넣을 수 있는 상태 값
    * key : location 객체의 고유값, 초기에는 default 이며 페이지가 변경될때마다 고유의 값이 생성 됨.</code></pre><h3 id="usenavigate">useNavigate</h3>
<ul>
<li>Link컴포넌트를 사용하지 않고 다른 페이지로 이동해야하는 상황에 사용하는 Hook</li>
</ul>
<pre><code class="language-javascript">// src/Layout.js

import {Outlet, useNavigate} from &#39;react-router-dom&#39;;

const Layout = () =&gt; {

  const navigate = useNavigate();

  const goBack = () =&gt; {
    navigate(-1);
    // 이전페이지로 이동
  };

  const goArticle = () =&gt; {
    navigate(&#39;/articles&#39;);
  };

  return (
    &lt;div&gt;
      &lt;header style={{background: &#39;lightgray&#39;, padding: 16, fontSize: 24}}&gt;
        &lt;button onClick={goBack}&gt; 뒤로가기 &lt;/button&gt;
        &lt;button onClick={goArticle}&gt; 게시글 목록 &lt;/button&gt;
      &lt;/header&gt;
      &lt;main&gt;
        &lt;Outlet /&gt;
      &lt;/main&gt;
    &lt;/div&gt;
  )
}


// replace : 페이지 이동시 현재페이지를 페이지기록에 안남긴다.
const goArticles = () =&gt; {
  navigate(&#39;/articles&#39;, {replace: true});
};</code></pre>
<h3 id="콜백함수">콜백함수</h3>
<pre><code class="language-javascript">- 파라미터 값이 주어지면 1초 뒤에 10을 더해서 반환하는 함수가 있다고 가정,
- 그리고 `해당 함수가 처리된 직후 어떠한 작업을 하고 싶다`면 콜백함수를 활용한다.

const increase = (number, callback) =&gt; {
  setTimeout(() =&gt; {
    const result = number + 10;
    if (callback) {
      callback(result);
    }
  }, 1000)
}

increase (0, result =&gt; {
  console.log(result);
});


- 1초에 걸쳐서 10, 20, 30, 40 과 같은 형태로 여러번 순차적으로 처리하고 싶다면 콜백함수를 중첩하여 구현한다.</code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
<pre><code class="language-javascript"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[build custormize]]></title>
            <link>https://velog.io/@heejeen-choi/build-custormize</link>
            <guid>https://velog.io/@heejeen-choi/build-custormize</guid>
            <pubDate>Wed, 29 Mar 2023 02:25:25 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">//vite.config.ts

import { defineConfig } from &#39;vite&#39;
import react from &#39;@vitejs/plugin-react&#39;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) =&gt; {
          return &#39;cart/assets/[name][extname]&#39;;
        },
        chunkFileNames: &#39;cart/assets/[name].js&#39;,
        entryFileNames: &#39;cart/assets/[name].js&#39;
      }
    }
  },
  server: {
    proxy: {
      &#39;/api&#39;: {
        target: &#39;http://127.0.0.1:8080&#39;,
        //rewrite: path =&gt; path.replace(&#39;/api&#39;, &#39;&#39;),
        changeOrigin: true,
        secure: false
      }
    }
  }
})</code></pre>
<pre><code class="language-javascript">// 2. vite.config.ts

import { defineConfig } from &#39;vite&#39;
import react from &#39;@vitejs/plugin-react&#39;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      &#39;/api&#39;: {
        target: &#39;http://127.0.0.1:8080&#39;,
        rewrite: path =&gt; path.replace(&#39;/api&#39;, &#39;&#39;),
        changeOrigin: true,
        secure: false
      }
    }
  }
})
</code></pre>
]]></description>
        </item>
    </channel>
</rss>