<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>시간이 멈추는 장소</title>
    <link>https://narup.tistory.com/</link>
    <description>기은P의 블로그
일상과 개발 관련 포스팅
#React #Typescript #Next #Nest
https://github.com/kimdongjang</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 23:49:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>기은P</managingEditor>
    <image>
      <title>시간이 멈추는 장소</title>
      <url>https://tistory1.daumcdn.net/tistory/3754451/attach/8d92ab87636f440982954f0fcc3350af</url>
      <link>https://narup.tistory.com</link>
    </image>
    <item>
      <title>[AWS] 로드밸런서의 SSL를 EC2 스프링부트와 연동하기</title>
      <link>https://narup.tistory.com/281</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;3 3 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트 사이트(https)에서 API 서버(http)로 연결하려면 Mixed Content Block 정책으로 인해 연결이 되지 않음&lt;/li&gt;
&lt;li&gt;Selfed Sign SSL을 이용하면 PC에서는 연결이 되지만 모바일에서는 API 호출이 안되는 문제가 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-pm-slice=&quot;3 3 []&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 Nginx를 프록시 서버로 사용해서 https의 요청을 http로 사용했었는데, Selfed Sign SSL문제로 인해 다시 nginx를 제거.&lt;/li&gt;
&lt;li&gt;AWS의 로드밸런서에서 사용중인 SSL 인증서가 있다는 것을 관리자에게 들음&lt;/li&gt;
&lt;li&gt;따라서 AWS의 로드밸런서를 사용해서 nginx의 역할을 대체하기로함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbysk2/btsLoe0ncNq/WkRRkqwOaR1vVKMxXLMIK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbysk2/btsLoe0ncNq/WkRRkqwOaR1vVKMxXLMIK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbysk2/btsLoe0ncNq/WkRRkqwOaR1vVKMxXLMIK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbysk2%2FbtsLoe0ncNq%2FWkRRkqwOaR1vVKMxXLMIK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;228&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;조치 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-pm-slice=&quot;3 3 []&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;http(80)요청이 오면 443으로 리다이렉트&lt;/li&gt;
&lt;li&gt;https(443)요청이 오면 EC2가 포함된 그룹의 리스너로 리다이렉트&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJVyN/btsLpDd1j7H/6Vtq8CgdddqFV9oR0ckI2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJVyN/btsLpDd1j7H/6Vtq8CgdddqFV9oR0ckI2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJVyN/btsLpDd1j7H/6Vtq8CgdddqFV9oR0ckI2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJVyN%2FbtsLpDd1j7H%2F6Vtq8CgdddqFV9oR0ckI2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;357&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;리스너 규칙은 아래와 같이 API에서 사용중인 스프링부트 서버의 포트로 설정한다. 이렇게 되면 443요청이 올 때 자동적으로 8080포트로 리다이렉트가 된다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cApWGw/btsLpIe648w/GWvm5ZM4aCOhApwcsZ1BHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cApWGw/btsLpIe648w/GWvm5ZM4aCOhApwcsZ1BHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cApWGw/btsLpIe648w/GWvm5ZM4aCOhApwcsZ1BHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcApWGw%2FbtsLpIe648w%2FGWvm5ZM4aCOhApwcsZ1BHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;329&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;추가적으로 AWS 로드밸런서에서 주기적으로 헬스체크를 하기 때문에 해당 경로의 화이트리스트 추가가 필요하다.&lt;br /&gt;내 경우에는 스프링의 화이트리스트에 &amp;ldquo;/actuator/health/**&amp;rdquo;를 추가하고, AWS 리스너 규칙의 상태검사의 경로를 똑같이 지정해주었다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://trello.com/1/cards/6694d84b04ee7dd4e2e15351/attachments/6763a504dca5fb4d0dc6f3b0/download/image.png&quot; data-phocus=&quot;https://trello.com/1/cards/6694d84b04ee7dd4e2e15351/attachments/6763a504dca5fb4d0dc6f3b0/download/image.png&quot;&gt;&lt;img src=&quot;https://trello.com/1/cards/6694d84b04ee7dd4e2e15351/attachments/6763a504dca5fb4d0dc6f3b0/download/image.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ftrello.com%2F1%2Fcards%2F6694d84b04ee7dd4e2e15351%2Fattachments%2F6763a504dca5fb4d0dc6f3b0%2Fdownload%2Fimage.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;270&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre id=&quot;code_1734653454689&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static final String[] AUTH_WHITELIST = {
            &quot;/barocert/**&quot;,&quot;/api/**&quot;, &quot;/swagger-ui/**&quot;, &quot;/swagger-ui.html&quot;,
            &quot;/actuator/health/**&quot;
    };


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        //CSRF, CORS
        httpSecurity.csrf((csrf) -&amp;gt; csrf.disable());
        httpSecurity.cors(Customizer.withDefaults());

        //세션 관리 상태 없음으로 구성, Spring Security가 세션 생성 or 사용 X
        httpSecurity.sessionManagement(sessionManagement -&amp;gt; sessionManagement.sessionCreationPolicy(
                SessionCreationPolicy.STATELESS));

        //FormLogin, BasicHttp 비활성화
        httpSecurity.formLogin((form) -&amp;gt; form.disable());
        httpSecurity.httpBasic(AbstractHttpConfigurer::disable);


        //JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가
//        httpSecurity.addFilterBefore(new JwtRequestFilter(adminService), UsernamePasswordAuthenticationFilter.class);

        // 권한 규칙 작성
        httpSecurity.authorizeHttpRequests(authorize -&amp;gt; authorize
                .requestMatchers(AUTH_WHITELIST).permitAll()
                //@PreAuthrization을 사용할 것이기 때문에 모든 경로에 대한 인증처리는 Pass
                //.anyRequest().permitAll()
                .anyRequest().authenticated()
        );

        return httpSecurity.build();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;스프링부트의 application.properties에 부가적인 설정을 추가해준다&lt;br /&gt;
&lt;pre id=&quot;code_1734653518272&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 서버가 요청의 헤더(예: X-Forwarded-For, X-Forwarded-Proto 등)를 처리하는 방식 설정
# &quot;native&quot;로 설정하면, 애플리케이션이 프록시 서버(예: Nginx, AWS ELB)의 헤더를 신뢰하여 처리
server.forward-headers-strategy=native

# Spring Boot Actuator에서 노출할 웹 엔드포인트를 설정
# &quot;health&quot;로 설정하면 /actuator/health 엔드포인트만 노출
management.endpoints.web.exposure.include=health

# /actuator/health 엔드포인트에서 상태의 세부 정보를 항상 포함하도록 설정
# 이 설정은 상태 확인 시 모든 세부 정보를 반환하도록 함 (예: 디스크 상태, DB 상태 등)
management.endpoint.health.show-details=always

# Spring Boot Actuator의 health 상태 확인에서 DB 상태 체크를 비활성화
# DB 연결 실패 여부에 상관없이 /actuator/health 응답에서 DB 상태를 제외
management.health.db.enabled=false

# Spring Boot Actuator와 관련된 로깅 레벨을 DEBUG로 설정
# Actuator 관련 디버깅 정보를 로그에 출력하여 문제를 분석하거나 동작을 확인할 수 있도록 설정
logging.level.org.springframework.boot.actuate=DEBUG&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;이후 헬스 체크를 진행해본다. EC2 SSH로 접속하고 아래 curl을 호출하는데 상태가 UP이면 정상, DOWN이면 문제가 있는 상태이다. 상세한 에러 내역도 나오니까 확인해서 고치자.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1734653599858&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl http://localhost:8080/actuator/health&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/AWS</category>
      <category>AWS</category>
      <category>LoadBalancer</category>
      <category>springboot</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/281</guid>
      <comments>https://narup.tistory.com/281#entry281comment</comments>
      <pubDate>Fri, 20 Dec 2024 09:15:35 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Antd es module 사용시 이슈(nextjs antd syntaxerror cannot use import statement outside a module)</title>
      <link>https://narup.tistory.com/280</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;01. Antd&amp;nbsp;es&amp;nbsp;module&amp;nbsp;사용시&amp;nbsp;이슈&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K950k/btsIez6VToE/l9HpP3TGJ2RTefwD0lweC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K950k/btsIez6VToE/l9HpP3TGJ2RTefwD0lweC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K950k/btsIez6VToE/l9HpP3TGJ2RTefwD0lweC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK950k%2FbtsIez6VToE%2Fl9HpP3TGJ2RTefwD0lweC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;288&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkHAtT/btsIfhEJpIf/6D8RWdYwQG7oLwEMqfRiOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkHAtT/btsIfhEJpIf/6D8RWdYwQG7oLwEMqfRiOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkHAtT/btsIfhEJpIf/6D8RWdYwQG7oLwEMqfRiOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkHAtT%2FbtsIfhEJpIf%2F6D8RWdYwQG7oLwEMqfRiOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;815&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;antd의 es라고 붙은 컴포넌트를 사용할때 &amp;nbsp;cannot use import statement outside a module 이런 이슈가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 고치다가 잘 안되서 포기했는데, 다시 잘 찾아보니 해결 방법이 굉장히 간단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;02. 해결방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) yarn add next-transpile-modules 설치&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) next.config.mjs에 코드 추가&lt;/h4&gt;
&lt;pre id=&quot;code_1719473250159&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** @type {import('next').NextConfig} */
import withTM from 'next-transpile-modules';
 
...

// antd는 무조건 들어가야하고, 나머지는 옵션
export default withTM(['antd', 'rc-picker', 'rc-util', '@ant-design/icons', 'rc-pagination', 'rc-notification'])({
    nextConfig
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;03. 발생이유&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이브러리 크기와 불러오기 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;antd&lt;/b&gt;: 기본적으로 모든 Ant Design 컴포넌트와 스타일을 포함한 완전한 버전입니다. 이 버전을 불러올 때는 모든 Ant Design 컴포넌트와 스타일이 번들에 포함됩니다. 따라서 파일 크기가 크고 초기 로딩 시간이 더 걸릴 수 있습니다. 일반적으로 import { Button } from 'antd';와 같이 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;antd/es&lt;/b&gt;: ES 모듈 형식으로 제공되며, 필요한 컴포넌트만 개별적으로 불러와서 사용할 수 있습니다. 이 방식은 트리 쉐이킹(tree shaking)이 가능하므로 번들 크기를 줄일 수 있습니다. 예를 들어 import Button from 'antd/es/button';와 같이 필요한 컴포넌트를 직접 지정하여 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 트리 쉐이킹과 번들 최적화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;antd/es의 장점&lt;/b&gt;: ES 모듈 형식으로 제공되기 때문에 webpack과 같은 모듈 번들러에서 트리 쉐이킹을 활용할 수 있습니다. 이는 사용하지 않는 컴포넌트와 스타일을 번들에 포함시키지 않고 제거하여 번들 크기를 최소화할 수 있습니다. 따라서 대규모 애플리케이션에서 특히 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;antd의 단점&lt;/b&gt;: 전체 라이브러리를 한 번에 불러오기 때문에 트리 쉐이킹이 제한적이며, 번들 크기가 크고 초기 로딩 속도가 느릴 수 있습니다. 작은 규모의 프로젝트에서는 큰 문제가 되지 않을 수 있지만, 큰 규모의 프로젝트에서는 번들 최적화가 중요한 요소가 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 사용 시 고려사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;antd&lt;/b&gt;: 모든 컴포넌트와 스타일을 포함하기 때문에, 간편하게 모든 기능을 사용할 수 있으며 초기 설정이 간단합니다. 하지만 번들 크기와 성능에 대한 영향이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;antd/es&lt;/b&gt;: 필요한 컴포넌트만 선택적으로 불러와서 사용할 수 있습니다. 번들 크기를 최적화하고자 할 때 유용하지만, 초기 설정과 관리가 다소 복잡할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하여, 번들 크기를 최소화, 경량화해서 사용하기 위해 나온게 es 모듈 컴포넌트인데, 이게 SSR을 사용하는 nextjs에서는 외부의 모듈을 사용하는 거기 때문에 문제가 발생하는 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드할 때는 나중에 따로 조치를 해줘야할지도?&lt;/p&gt;</description>
      <category>FrontEnd/Next.js</category>
      <category>Next.js</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/280</guid>
      <comments>https://narup.tistory.com/280#entry280comment</comments>
      <pubDate>Thu, 27 Jun 2024 16:31:37 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Docker compose 설치</title>
      <link>https://narup.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 로직에 최적화가 필요해서 메시지 큐를 써야할 것 같은 상황에, Kafka를 쓰면 어떨까해서 고민해보다가 시간이 생겨 구현을 해보았습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 구현이 될줄 알았는데, 생각보다 카프카 쪽 설정이 신경쓸 것들이 많더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 구성환경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddFG8Y/btsG4VW5mFg/kQPsNQ81DBRQBxPIr6f7FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddFG8Y/btsG4VW5mFg/kQPsNQ81DBRQBxPIr6f7FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddFG8Y/btsG4VW5mFg/kQPsNQ81DBRQBxPIr6f7FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddFG8Y%2FbtsG4VW5mFg%2FkQPsNQ81DBRQBxPIr6f7FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;574&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 같은 환경에서 자유롭게 진행하고자 &lt;b&gt;카프카 컨테이너를 도커 안에 배치&lt;/b&gt;하도록 구성환경을 짰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 환경이 저랑 같을 수는 없겠지만, 저는 이런 시퀀스 구조로 카프카를 사용하도록 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 웹 사용자가 요청하면 Next.js 서버 사이드 단에서 Kafkajs를 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) kafkajs는 도커 컨테이너에 있는 kafka broker에 publish&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 같은 도커 컨테이너에 있는 Express.js에서 kafka를 subcribe하고 있는 상황에서 퍼블리싱이 일어나면 Express.js에서 정의한 이벤트 발생(Hello, Kafka! 텍스트 호출)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 물 흐르듯이 되었어야했는데, 그림과 같이 로컬환경에 있는 next.js에서 도커 컨테이너의 kafka 브로커까지 접속이 안되는 이슈가 있더라구요... 삽질을 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. kafka docker-compose&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 카프카를 동작시킬 수 있는 docker-compose의 코드 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1714458090275&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose.yml
version: '2'

services:      
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    hostname: zookeeper
    container_name: zookeeper
    ports:
      - &quot;2181:2181&quot;
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

  kafka:
    image: confluentinc/cp-kafka:latest
    hostname: kafka
    container_name: kafka
    ports:
      - &quot;9093:9093&quot;
    environment:
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092, PLAINTEXT_HOST://localhost:9093 #INTERNAL에 kafka:9092는 고정
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT  #내부:PLAINTEXT 외부:PLAINTEXT_HOST
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 # Replication factor: 3 larger than available brokers: 1 에러 발생 방지
    depends_on:
      - zookeeper&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka쪽 environment를 보면 구성 설정이 많이 있는데, 이것들을 일단 그대로 써보시는 것을 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점이 SECURITY_PROTOCOL_MAP 부분이 있는데, 이 뒤에 있는 변수들은 제가 임의로 지정한 변수가 아니고 kafka에서 지정한 변수더라구요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INTERNAL: (내부용인것 같음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PLAINTEXT: (평문이라는 의미)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PLAINTEXT_HOST: (평문 호스트 라는 의미같은데... 일단 그대로 씁시다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3개의 변수는 변경하지 마시고 그대로 가져다 쓰시면서 어떤 의미인지 확인해 보시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너에 사용되는 이미지는 confluentinc/cp-kafka:latest 이것을 사용했는데, 다른 것보다는 이 이미지가 사용하기가 편하더라구요. 밑에서 설명할 GUI하고 연동도 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. UI&amp;nbsp;for&amp;nbsp;Apache&amp;nbsp;Kafka&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhLRKG/btsG4TZe7vk/wF0d69IBbPgEQqzOp5taYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhLRKG/btsG4TZe7vk/wF0d69IBbPgEQqzOp5taYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhLRKG/btsG4TZe7vk/wF0d69IBbPgEQqzOp5taYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhLRKG%2FbtsG4TZe7vk%2FwF0d69IBbPgEQqzOp5taYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;368&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진과 같이 현재 카프카 클러스터의 상태를 보여주는 GUI인데, docker-compose에 추가해서 kafka와 같이 사용하면 편리하더라구요. 현재 토픽 상태나 브로커 상태에 대해 한 눈에 알아보기 쉽게 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 docker-compose.yml 파일에 아래 kafka-ui 컨테이너를 추가해주시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1714459287327&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...

  kafka-ui:
    image: provectuslabs/kafka-ui
    container_name: kafka-ui
    ports:
      - &quot;8090:8080&quot; # 8080은 많이들 사용하니 8090으로 접속하시길
    restart: always
    environment:
      - KAFKA_CLUSTERS_0_NAME=local # 임의의 이름 아무렇게나 지으셔도 됩니다
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
      - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 링크는 여기입니다.&lt;br /&gt;&lt;a href=&quot;https://docs.kafka-ui.provectus.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.kafka-ui.provectus.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714459490298&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;About | UI for Apache Kafka&quot; data-og-description=&quot;About Kafka-UI&quot; data-og-host=&quot;docs.kafka-ui.provectus.io&quot; data-og-source-url=&quot;https://docs.kafka-ui.provectus.io/&quot; data-og-url=&quot;https://docs.kafka-ui.provectus.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/C2HN1/hyVZpBxry5/JrIATWXKon5yM0vWRQZ9kK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/JIrgl/hyVVGStQn7/cxKTVSyTAYX6BCwXczE8j0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://docs.kafka-ui.provectus.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.kafka-ui.provectus.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/C2HN1/hyVZpBxry5/JrIATWXKon5yM0vWRQZ9kK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/JIrgl/hyVVGStQn7/cxKTVSyTAYX6BCwXczE8j0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;About | UI for Apache Kafka&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;About Kafka-UI&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.kafka-ui.provectus.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실행결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ozYhV/btsG13voVnw/VltQWhyEV8Tl5cQoI40ByK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ozYhV/btsG13voVnw/VltQWhyEV8Tl5cQoI40ByK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ozYhV/btsG13voVnw/VltQWhyEV8Tl5cQoI40ByK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FozYhV%2FbtsG13voVnw%2FVltQWhyEV8Tl5cQoI40ByK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;185&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 컨슈머에서 등록한 이벤트를 발생시킬 수 있는 것을 확인했습니다.&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Docker</category>
      <category>docker</category>
      <category>docker-compose</category>
      <category>Kafka</category>
      <category>kafka-ui</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/279</guid>
      <comments>https://narup.tistory.com/279#entry279comment</comments>
      <pubDate>Tue, 30 Apr 2024 15:45:12 +0900</pubDate>
    </item>
    <item>
      <title>[리눅스] docker, docker-compose 설치</title>
      <link>https://narup.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스(우분투) 설치 버전입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 도커 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스에 도커를 설치하려면 아래 명령어를 따라가야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 리눅스 SSH 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 패키지 최신 업데이트&lt;/p&gt;
&lt;pre id=&quot;code_1708495141068&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 도커 다운로드를 위한 https 관련 패키지 설치&lt;/p&gt;
&lt;pre id=&quot;code_1708495164185&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install apt-transport-https ca-certificates curl software-properties-common&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 도커 레포지토리 접근을 위한 GPG Key 설정&lt;/p&gt;
&lt;pre id=&quot;code_1708495187981&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 도커 레포지토리 등록&lt;/p&gt;
&lt;pre id=&quot;code_1708495206438&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 등록한 레포지토리를 포함해 다시 패키지를 최신으로 업데이트 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708495232856&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 도커 설치&lt;/p&gt;
&lt;pre id=&quot;code_1708495244831&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install docker-ce&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 도커 실행 상태 확인&lt;/p&gt;
&lt;pre id=&quot;code_1708495258650&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl status docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UbjlP/btsFaEJPWRX/x3KjWxlYVz3rhI0YP63NK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UbjlP/btsFaEJPWRX/x3KjWxlYVz3rhI0YP63NK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UbjlP/btsFaEJPWRX/x3KjWxlYVz3rhI0YP63NK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbjlP%2FbtsFaEJPWRX%2Fx3KjWxlYVz3rhI0YP63NK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1127&quot; height=&quot;310&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 도커 컴포즈 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈는 리눅스 패키지에 포함이 되어있지 않아서 별도로 웹(깃허브)에서 파일을 다운받아 리눅스에 등록하는 형태로 사용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 도커 컴포즈 설치&lt;/p&gt;
&lt;pre id=&quot;code_1708495390310&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo curl -L &quot;https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전을 변경하고 싶으면 가운데에 있는 v2.5.0을 변경하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈의 경로는 다들 /usr/local/bin/docker-compose 로 하는것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 도커 컴포즈 권한 부여&lt;/p&gt;
&lt;pre id=&quot;code_1708495440486&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chmod +x /usr/local/bin/docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 심볼릭 링크 연결&lt;/p&gt;
&lt;pre id=&quot;code_1708495458102&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 버전 확인&lt;/p&gt;
&lt;pre id=&quot;code_1708495486471&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Docker</category>
      <category>docker</category>
      <category>docker-compose</category>
      <category>도커</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/278</guid>
      <comments>https://narup.tistory.com/278#entry278comment</comments>
      <pubDate>Wed, 21 Feb 2024 15:05:53 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 구간 합, 누적 합</title>
      <link>https://narup.tistory.com/277</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;코드보다는 알고리즘 접근 방법에 대해 정리합니다!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;1. 누적 합이란?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;배열에서 0번째 인덱스부터 N번째 인덱스까지 탐색을 진행하면서 누적해서 값을 더한 결과를 말합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;array[1,3,5,6,7,9,13]가 있으면 1부터 13까지 순차적으로 탐색하면서 값을 더한 것을 말하는데, 다만 값을 단순하게 더하는 것이 아닌 누적해서 더한다는 것이 포인트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;1+, (1+3), (1+3+5), (1+3+5+6), ... 이런식으로 더해 나가야하는 거죠!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;코드로 단순하게 구현해 보면 아래와 같은 모습이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697792356386&quot; class=&quot;matlab&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;for(int i = 0; i&amp;lt;n; i++){
	for(int j = 0; j&amp;lt;=i; j++){
    	sum[i] += arr[j];
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 될 경우 시간 복잡도는 O(N^2)가 걸리게 되는데, sum_arr라는 새로운 배열을 만들고 계산한 값을 배열에 저장해서 사용하면 O(N)으로 누적합을 구할 수가 있게 됩니다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697792356391&quot; class=&quot;matlab&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;for(int i= 0; i&amp;lt;n; i++){
	sum_arr[i+1] = sum_arr[i] + arr[i];
}
console.log(sum_arr[arr.length])&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;누적이라는 단어가 나온다면, 위처럼 계산한 값을 저장할 새로운 배열을 만들어야겠다는 생각으로 접근하는 것이 좋습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 구간합이란?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;누적합의 알고리즘과 같은데, 인덱스만 조금 달라집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구간합 알고리즘은 현재 진행 단계의 인덱스까지의 합을 저장하는 sum[] 배열을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;sum[i-1]까지의 총합에 arr[i]를 더하면 sum[i]를 구할 수가 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 0번째부터 N까지를 구할때 i-1이라는 로직이 들어가기 때문에 sum[]배열을 사용할 경우 arr[]보다 +1 크게 만들고, 초기 값을 0으로 지정해주어야 합니다!&lt;/p&gt;</description>
      <category>알고리즘</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/277</guid>
      <comments>https://narup.tistory.com/277#entry277comment</comments>
      <pubDate>Fri, 20 Oct 2023 17:59:38 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 투 포인트(Two Point)</title>
      <link>https://narup.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코드보다는 알고리즘 접근 방법에 대해 정리합니다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;keypoint.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;리스트에 순차적으로 접근해야할 때 2개의 점의 위치를 기록하면서 처리하는 알고리즘&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;순차적, 일방향으로 되어있는 선형적인 구조에서 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 투 포인트란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;리스트에 순차적으로 접근해야할 때 2개의 점의 위치를 기록하면서 처리하는 알고리즘&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간을 설정하는 시작점과 끝점을 지정하는 방법이라고 생각하면 되는데, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;순차적, 일방향으로 되어있는 선형적인 구조에서 사용&lt;/span&gt;할 수 있는 방법입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정한 합을 가지는 부분 연속 수열을 찾는 문제가 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양의 정수로만 구성된 리스트[1,2,3,2,5]가 있을 때, 부분적으로 선택한 정수들의 합이 m(5)가 되는 수열의 개수를 출력해야 할때 위 투 포인트 알고리즘을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 투 포인트 알고리즘의 특징은 2개의 변수를 이용해 리스트 상의 시작점과 끝점을 기록한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제에서의 접근방법은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1) 시작점, 끝점이 첫 번째 원소의 인덱스(0)을 가리키도록 설정.&lt;br /&gt;2) 현재까지의 합(s)과 m이 같으면 카운트를 증가&lt;br /&gt;3) 현재까지의 합(s)이 m보다 작으면 end를 1 증가 시킴.&lt;br /&gt;4) 현재까지의 합(s)보다 크거나 같으면 start를 1 증가 시킴.&lt;br /&gt;5) 모든 경우를 확인할 때 까지 2~4번의 과정을 반복.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투 포인트 알고리즘의 특징은 위와 같은 방법이 아니더라도 문제를 풀 수 있는 접근방법이 많다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 리스트가 [1,5,2,3,4] 라고 한다면, 시작점이 m이 된 경우에는 카운트를 증가시키고 다음 시작점으로 넘어가게끔 하는 순서를 추가할 수도 있습니다. 그 이후의 end를 계속 더한들 m이 될수는 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 정렬되어 있는 두 리스트의 합집합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A리스트가 [1,3,5]이고 B리스트가 [2,4,6,8]일때, 두 리스트를 합쳐 1개의 리스트로 정렬하라는 문제가 주어졌을때, 2개의 포인터를 이용하여 각 리스트에서 처리되지 않은 원소 중 가장 작은 원소를 가리키게 하여 새 리스트로 원소를 추가하는 식으로 전개할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1) 정렬된 리스트 A에서 처리되지 않은 원소 중 가장 작은 원소를 i가 가리키도록 함.&lt;br /&gt;2) 정렬된 리스트 B에서 처리되지 않은 원소 중 가장 작은 원소를 j가 가리키도록 함.&lt;br /&gt;3) A[i]와 B[j] 중 더작은 원소를 결과 리스트에 추가한다.&lt;br /&gt;4) 리스트 A와 B에서 더이상 처리할 원소가 없을때까지 1~3번의 과정을 반복.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;포인터의 쓰임새에 중점을 두면 좋을 것 같습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/276</guid>
      <comments>https://narup.tistory.com/276#entry276comment</comments>
      <pubDate>Tue, 17 Oct 2023 14:06:27 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 소수판별, 에라토스테네스의체</title>
      <link>https://narup.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코드보다는 알고리즘 접근 방법에 대해 정리합니다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;keypoint.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;자연수는 가운데 약수를 기준으로 각 등식이 대칭적인 형태를 보인다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;제곱근이란 어떤 수를 두번 곱하여나오는 수에 대해, 그 수가 나오도록 두번 곱하는 수&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;소수에 2를 곱할 경우 그 수는 소수가 아니게 된다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 소수란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2보다 큰 자연수 중 1과 자기 자신을 제외한 자연수로는 나누어 떨어지지않는 자연수를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 1부터 N까지의 모든 소수를 출력해야 하는 문제가 나올 수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 소수 판별 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) X를 2부터 X-1까지의 모든 수로 나누어 보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 16이라는 수가 있다면 16을 2로 나누고, 3으로 나누고, 4로 나누고, ... 15까지 나누어 보는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 나머지가 0일 경우에는 소수가 아니게 되어서, 사실 16을 2로 나누었을때 이미 소수가 아님이 증명이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 시간 복잡도가 0(X)으로 비효율적인 방법입니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 모든 수를 확인하지 않고 제곱근까지만 확인하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;자연수는 가운데 약수를 기준으로 각 등식이 대칭적인 형태를 보인다&lt;/span&gt;는 특징이 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16이라는 숫자를 예를 들면,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1 2 4 8 16&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 약수가 되는데, 이를 가운데를 중심으로 양 끝 수를 2개씩 묶어서 곱하면 16이라는 수가 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1x16, 2x8, 4x4, 8x2, 16x1&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;제곱근이란 어떤 수를 두번 곱하여나오는 수에 대해, 그 수가 나오도록 두번 곱하는 수&lt;/span&gt;를 말하는데, 16이라는 숫자에서는 4가 제곱근이 되겠지요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 소수를 판별할 때에는 4 이상의 수까지 검사하지 않아도 결과는 동일하다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 에라토스테네스의 체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에라토스테네스의 체라는 알고리즘은 여러 개의 수가 소수인지 아닌지를 판별할때 사용하는 대표적인 알고리즘 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n=1000이라는 자연수가 주어지고, 1부터 n까지의 모든 소수를 출력하라는 문제가 주어졌을 때, 2번의 2)를 활용해서 1~1000까지의 모든 소수를 구하는 방법을 사용하면 될 테지만... 조금 더 효율적으로 시간을 아낄 수 있는 방법이 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 소수의 특징을 이용한 것인데, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;소수에 2를 곱할 경우 그 수는 소수가 아니게 됩니다&lt;/span&gt;. 또한, 그 수에 다시 2를 거듭해서 곱할 경우 그 수들도 소수가 아니게 되는 것이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에라토스테네스의 체의 동작원리는 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1) 2부터 N까지의 모든 자연수를 나열한다.&lt;br /&gt;2) 나열한 수 중에서 아직 처리하지 않은 가장 작은 소수 i를 찾는다.&lt;br /&gt;3) 나열한 수 중에서 소수 i의 배수를 모두 제거한다.&lt;br /&gt;4) 더 이상 반복할 수 없을 때까지 2와 3의 과정을 반복한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 알고리즘으로 처리한 값을 재활용 할 수 있다는 점이 특징입니다!&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>소수</category>
      <category>소수판별</category>
      <category>알고리즘</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/275</guid>
      <comments>https://narup.tistory.com/275#entry275comment</comments>
      <pubDate>Tue, 17 Oct 2023 10:54:09 +0900</pubDate>
    </item>
    <item>
      <title>[React] HOC(고차 컴포넌트) 함수형 예제</title>
      <link>https://narup.tistory.com/274</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술면접을 보았을때 HOC란 무엇인가?라고 물었을때 잘 모른다고 대답을 했었는데요, 살짝 찾아보니까 메인 다큐먼트에서는 클래스형 컴포넌트에서 사용되는 것이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술면접에서 물어볼 정도면 뭔가 중요한 내용이 있는게 아닐까 싶어서 HOC를 함수형으로 사용할 수 있는 방법을 찾아보았고, 어째서 HOC에서 물어보셨는지에 대해 깨닳게 되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제로 사용한 깃허브 주소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kimdongjang/hoc_test&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kimdongjang/hoc_test&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1668500253156&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kimdongjang/hoc_test&quot; data-og-description=&quot;Contribute to kimdongjang/hoc_test development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimdongjang/hoc_test&quot; data-og-url=&quot;https://github.com/kimdongjang/hoc_test&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hCQHJ/hyQA2E8Pkd/9SJklsxKTujufJpcmffIy0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/kimdongjang/hoc_test&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimdongjang/hoc_test&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hCQHJ/hyQA2E8Pkd/9SJklsxKTujufJpcmffIy0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kimdongjang/hoc_test&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to kimdongjang/hoc_test development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. HOC(Higher Order Component)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차 컴포넌트는 컴포넌트를 매개 변수로 받아 새로운 컴포넌트로 반환하는 함수를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 저희는 함수형 컴포넌트를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HOC는 이렇게 작성한 컴포넌트를 인자로 받아 Wrapping 해서 새로운 컴포넌트를 반환해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;컴포넌트의 재사용성&lt;/b&gt;&lt;/span&gt;을 늘리는 역할을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 사용방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HOC는 with로 시작하는 컨벤션으로 파일을 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 예제로 작성할 코드를 보면서 설명을 적겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HOC를 사용해서 여러 개의 이미지마다 공통적으로 마우스를 hover 할 때 반응하는 컴포넌트를 만들으려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차적으로 작성된 코드가 Image만 있는 컴포넌트이고, 2차적으로 Image마다 event를 추가한다고 했을 때, HOC를 사용해 이 Image들에게 Wrapping을 하면 Image마다 event를 작성할 필요없이 Wrap하는 것으로 코드 작성이 쉬워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HOC가 재사용성을 늘리기 위해 사용하는 것이기 때문에 이러한 관점에서 코드를 보는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exam1/ImageBox.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1668499691840&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import withHover from &quot;./withHover&quot;;

function ImageBox({
  imageUrl,
  imageTitle,
  isHovered,
  handleMouseEnter,
  handleMouseLeave
}) {
  return (
    &amp;lt;div&amp;gt;
      {isHovered &amp;amp;&amp;amp; &amp;lt;div id=&quot;hover&quot;&amp;gt;{imageTitle}&amp;lt;/div&amp;gt;}
      &amp;lt;img
        src={imageUrl}
        alt={imageTitle}
        width=&quot;400px&quot;
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      /&amp;gt;
      &amp;lt;h5&amp;gt;이미지에 마우스를 올리면 이미지 제목이 표시됩니다.&amp;lt;/h5&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default withHover(ImageBox);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exam1/withHover.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1668495389163&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

export default function withHover(InnerComponent) {
  // props는 App.jsx 부모에서 prop로 전달한 데이터
  return (props) =&amp;gt; {
    const [isHovered, setIsHovered] = useState(false);

    function handleMouseEnter() {
      setIsHovered(true);
    }

    function handleMouseLeave() {
      setIsHovered(false);
    }

    return (
      &amp;lt;InnerComponent
        {...{
          ...props,
          handleMouseEnter,
          handleMouseLeave,
          isHovered
        }}
      /&amp;gt;
    );
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 코드가 HOC 컴포넌트이고, 인자로 Wrap할 컴포넌트를 받아서 return값으로 컴포넌트를 반환합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유의할 점은 함수형으로 작성할 경우에는 위처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function withHOC() 내부에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return () =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;return (&amp;lt;innerComponent/&amp;gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 return을 2개를 작성해서 사용해야 한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.js&lt;/p&gt;
&lt;pre id=&quot;code_1668499872416&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import logo from './logo.svg';
import './App.css';
import ImageBox from './exam1/ImageBox';
import { useEffect, useState } from 'react';
import axios from 'axios';
import TodoList from './exam2/TodoList';
import UserList from './exam2/UserList';

function App() {
  const url = &quot;https://dog.ceo/api/breeds/image/random&quot;;
  const [title, setTitle] = useState('')
  const [image, setImage] = useState('');
  useEffect(() =&amp;gt; {
    axios.get(url).then(response =&amp;gt; { setTitle(response.data.message); setImage(response.data.message) })
  }, [])
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;ImageBox imageUrl={image} imageTitle={title} /&amp;gt;
      &amp;lt;UserList /&amp;gt;
      &amp;lt;TodoList /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.js에서는 랜덤 이미지를 호출해와 ImageBox에 이미지 주소와 title을 전달해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*userList와 todoList는 주석을 달아서 테스트하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 다른 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exam2/TodoList.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1668500053459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useEffect, useState } from 'react';
import withHOC from './withHoc';

function TodoLIst({ data }) {
  let renderTodos = data.map((todo) =&amp;gt; {
    return (
      &amp;lt;div key={todo.id}&amp;gt;
        &amp;lt;p&amp;gt;
          &amp;lt;strong&amp;gt;{todo.title}&amp;lt;/strong&amp;gt;
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{renderTodos}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default withHOC(TodoLIst, 'todos');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exam2/UserList.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1668500070204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useEffect, useState } from 'react';
import withHOC from './withHoc';

function UserList({ data }) {
  let renderUsers = data.map((user) =&amp;gt; {
    return (
      &amp;lt;div key={user.id}&amp;gt;
        &amp;lt;p&amp;gt;
          &amp;lt;strong&amp;gt;{user.name}&amp;lt;/strong&amp;gt;
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{renderUsers}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default withHOC(UserList, 'users');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exam2/withHoc.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1668500085540&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useEffect, useState } from 'react';

function withHOC(WrappedComponent, entityTitle) {
  return function () {
    const [data, setData] = useState([]);
    const [term, setTerm] = useState('');

    useEffect(() =&amp;gt; {
      const timer = setTimeout(() =&amp;gt; {
        let fetchData =
          entityTitle === 'users'
            ? [
              { name: 'yeji', id: 1 },
              { name: 'yoyo', id: 2 },
              { name: 'jojo', id: 3 },
              { name: 'aoao', id: 4 },
            ]
            : [
              { id: 1, userId: 1, title: 'hello', completed: true },
              { id: 2, userId: 2, title: 'bye', completed: true },
              { id: 3, userId: 3, title: 'mornign', completed: false },
              { id: 4, userId: 4, title: 'night', completed: false },
            ];

        setData([...fetchData]);

        clearTimeout(timer);
      }, 2000);
    }, []);

    let filterData = data.filter((d) =&amp;gt; {
      if (entityTitle === 'users') {
        const { name } = d;
        return name.indexOf(term) &amp;gt;= 0;
      }

      if (entityTitle === 'todos') {
        const { title } = d;
        return title.indexOf(term) &amp;gt;= 0;
      }
    });

    return (
      &amp;lt;div&amp;gt;
        &amp;lt;h2&amp;gt;{entityTitle}&amp;lt;/h2&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          value={term}
          onChange={(e) =&amp;gt; setTerm(e.target.value)}
        /&amp;gt;
        &amp;lt;WrappedComponent data={filterData}&amp;gt;&amp;lt;/WrappedComponent&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  };
}

export default withHOC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 있는 App.js 그대로 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ostarblog.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ostarblog.tistory.com/12&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ellie12/React-HOC-%EA%B3%A0%EC%B0%A8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ellie12/React-HOC-%EA%B3%A0%EC%B0%A8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8&lt;/a&gt;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>HOC</category>
      <category>react</category>
      <category>고차컴포넌트</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/274</guid>
      <comments>https://narup.tistory.com/274#entry274comment</comments>
      <pubDate>Tue, 15 Nov 2022 17:18:43 +0900</pubDate>
    </item>
    <item>
      <title>[React] useMemo와 useCallback의 차이</title>
      <link>https://narup.tistory.com/273</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo와 useCallback이라고 하는 hook 함수가 있지만 둘의 개념을 살펴보면 두 함수다 비슷한 역할을 가지고 있는 것 같아, 언제 어느 상황에 useMemo 또는 useCallback을 써야할지 판단을 하기 위해 정리를 해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(심지어 useCallback 안에 useMemo를 쓰는 경우도 있었음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Memoization&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;메모이제이션&lt;/b&gt;&lt;/span&gt;이라고 하는 개념에 대해 확실히 알아가야하는데, 메모이제이션은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용&lt;/b&gt;&lt;/span&gt;하는 프로그래밍 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 적절하게 활용하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;중복 연산&lt;/b&gt;&lt;/span&gt;을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. useMemo&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo는 메모이제이션된 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;값을 반환&lt;/b&gt;&lt;/span&gt;하는 함수입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667458856377&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useMemo(() =&amp;gt; fn, [deps])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 deps로 지정한 값이 변하게 된다면 () =&amp;gt; fn 함수를 실행하고, 그 함수의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;반환 값을 반환&lt;/b&gt;&lt;/span&gt;해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deps는 dependency의 약어로, 의존성을 뜻하며 useMemo가 이 deps에 의존하고 있다는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667458949375&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useCallback, useMemo } from &quot;react&quot;;

export default function App() {
  const [ex, setEx] = useState(0);
  const [why, setWhy] = useState(0);

  // useMemo 사용하기
  useMemo(() =&amp;gt; {console.log(ex)}, [ex]);

  // 두 개의 버튼을 설정했다. X버튼만이 ex를 변화시킨다.
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setEx((curr) =&amp;gt; (curr + 1))}&amp;gt;X&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setWhy((curr2) =&amp;gt; (curr2 + 1))}&amp;gt;Y&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 예로 위와 같이 useMemo를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 X라는 버튼을 클릭했을 때 setEx에 의해서 ex의 값을 1씩 증가시키는데, ex의 값이 변하기 때문에 useMemo에서&amp;nbsp; 의존성으로 등록한 ex가 변화된 것을 감지해 지정한 함수가 실행되고, console.log로 인해 ex값이 출력되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. useMemo를 쓰는 이유?&lt;/h3&gt;
&lt;pre id=&quot;code_1667459179270&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useCallback, useMemo } from &quot;react&quot;;

export default function App() {
  const [ex, setEx] = useState(0);
  const [why, setWhy] = useState(0);

  // 버튼 클릭시 ex값이 출력된다.
  console.log(ex); 
  
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setEx((curr) =&amp;gt; (curr + 1))}&amp;gt;X&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setWhy((curr2) =&amp;gt; (curr2 + 1))}&amp;gt;Y&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;state 값이 변하면 리렌더링이 일어나는 것&lt;/b&gt;&lt;/span&gt;은 모두 알고 계실 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*렌더링 과정에 대해 이해가 가지 않는다면 아래 링크를 참고해주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://narup.tistory.com/272&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667464902130&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[React] 렌더링(리렌더링)&quot; data-og-description=&quot;1. 개요 React를 공부하면서 어떻게 보면 가장 기초이고, 가장 중요한 부분인데 사실 이제야 포스팅한다는 것은 이제까지 리액트를 잘못 사용해왔다는 반증이기도 한데... 조금 더 심도있게 리액&quot; data-og-host=&quot;narup.tistory.com&quot; data-og-source-url=&quot;https://narup.tistory.com/272&quot; data-og-url=&quot;https://narup.tistory.com/272&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cRz3gU/hyQqP1KW1l/h5nZhZy9dc99gc7KFaqEF0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rz7lW/hyQqRkYjIk/8iHQ8uV1HSYdFmbQGajnmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bb7kvx/hyQr61RpAs/FpIZjT0YkX9UXOzRGRp5yK/img.png?width=293&amp;amp;height=365&amp;amp;face=0_0_293_365&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://narup.tistory.com/272&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cRz3gU/hyQqP1KW1l/h5nZhZy9dc99gc7KFaqEF0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rz7lW/hyQqRkYjIk/8iHQ8uV1HSYdFmbQGajnmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bb7kvx/hyQr61RpAs/FpIZjT0YkX9UXOzRGRp5yK/img.png?width=293&amp;amp;height=365&amp;amp;face=0_0_293_365');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[React] 렌더링(리렌더링)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 개요 React를 공부하면서 어떻게 보면 가장 기초이고, 가장 중요한 부분인데 사실 이제야 포스팅한다는 것은 이제까지 리액트를 잘못 사용해왔다는 반증이기도 한데... 조금 더 심도있게 리액&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;narup.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 클릭할 경우 setState로 인해 state값이 변화되어 리렌더링이 일어나면 console.log가 찍히도록 위처럼 코드를 작성하면 되는데, useMemo를 사용하는 이유는 뭘까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 위처럼 코드를 작성할 경우 X버튼, Y버튼, 혹은 컴포넌트가 부모 컴포넌트에 의해서 리렌더링이 될 경우 상태 값에 관계 없이 console.log가 찍히는 연산이 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 단순하게 console.log가 아닌 복잡한 연산, 연산할 때마다 약 0.3초씩 걸린다고 가정하면 어떨까요? 답답하겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ex값이 변할 경우에만 연산을 실행할 수 있도록 useMemo를 사용해 ex라는 변수에 의존하도록 등록&lt;/b&gt;&lt;/span&gt;하는 것이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과, 리렌더링이 발생할 경우, 특정 변수가 변할 때에만 useMemo에 등록한 함수가 실행되도록 처리하면 불필요한 연산을 하지 않게 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. useCallback&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 보았던 useMemo는 메모이제이션된 값을 반환해서 참 직관적이었는데, 이 useCallback은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;메모이제이션된 함수를 반환&lt;/b&gt;&lt;/span&gt;하는 특징을 가지고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667459714622&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useCallback(fn, [deps])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback 또한 deps, 의존성이 있는 값이 변하면 fn에 등록한 함수를 반환하는 기능을 가지고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667462465390&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useMemo(() =&amp;gt; console.log(), [test])
const memoizedCallback = useCallback(() =&amp;gt; console.log(), [test])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 useMemo와 useCallback의 모양은 위와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback이 함수를 반환하기 때문에 그 함수를 가지는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;const 변수에 초기화&lt;/b&gt;&lt;/span&gt;하는 것이 일반적인 모양입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo같은 경우는 deps값이 변하면 이 함수를 실행해라! 라는 느낌으로 활용이 가능한데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback은 어떨 때 사용해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback 사용처는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 자식 컴포넌트에 props로 함수를 전달할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 외부에서 값을 가져오는 api를 호출하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1) 자식 컴포넌트에 props로 함수를 전달하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;함수는 값이 아닌 참조로 비교된다!&lt;/b&gt;&lt;/span&gt;는 점을 알고 있어야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667462565631&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const functionOne = function() {
  return 5;
};
const functionTwo = function() {
  return 5;
};
// 서로의 참조가 다르기 때문에 false
console.log(functionOne === functionTwo);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 값을 반환하지만 참조가 다르기 때문에 false가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 컴포넌트에서 특정 함수를 정의할 경우 각각의 함수들은 모두 고유한 함수가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 고유한 함수가 생성될 경우, 부모를 통해 props에 함수를 전달받는 자식 컴포넌트에서는 props가 변경되었다고 판단해 리렌더링이 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667463689679&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  const [name, setName] = useState('');
  const onSave = () =&amp;gt; {};

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={name}
        onChange={(e) =&amp;gt; setName(e.target.value)}
      /&amp;gt;
      &amp;lt;Profile onSave={onSave} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;useCallback을 사용하지 않을 경우, &lt;/span&gt;name이 변경되어 리렌더링이 발생하면 onSave함수가 새로 만들어지고, Profile 컴포넌트의 props로 onSave함수가 새로 전달되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Profile 컴포넌트에서 useMemo를 사용해도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이전 onSave와 이후 onSave가 같은 값을 반환하지만 참조가 다른 함수&lt;/b&gt;&lt;/span&gt;가 되어버리기 때문에 리렌더링이 일어나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트만 수정하려고 했지만 연쇄적으로 하위 컴포넌트들 모두 렌더링이 일어나게 되어버리지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같이&lt;/p&gt;
&lt;pre id=&quot;code_1667463434537&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useCallback, useState } from 'react';
import Profile from './Profile';


function App() {
  const [name, setName] = useState('');
  const onSave = useCallback(() =&amp;gt; {
    console.log(name);
  }, [name]);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={name}
        onChange={(e) =&amp;gt; setName(e.target.value)}
      /&amp;gt;
      &amp;lt;Profile onSave={onSave} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback을 사용해서 onSave라는 함수를 재사용하는 것으로 자식 컴포넌트의 리렌더링을 방지할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2) 외부에서 값을 가져오는 api를 호출하는 경우&lt;/h3&gt;
&lt;pre id=&quot;code_1667463978940&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from &quot;react&quot;;

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = () =&amp;gt;
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) =&amp;gt; response.json())
      .then(({ user }) =&amp;gt; user);

  useEffect(() =&amp;gt; {
    fetchUser().then((user) =&amp;gt; setUser(user));
  }, [fetchUser]);

  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 fetchUser 함수가 변경될 때만 외부에서 api를 가져와 useEffect가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 코드는 정상적인 코드가 아닌데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말씀 드렸다시피 Profile이라는 컴포넌트가 리렌더링이 발생할 경우 fetchUser 함수에는 새로운 함수가 할당되게 됩니다. 그러면 useEffect()함수가 호출되어 user 상태값이 바뀌고, state 값이 바뀌었기 때문에 다시 리렌더링이 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;무한루프&lt;/b&gt;&lt;/span&gt;에 빠져버리게 되는 것이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;useCallback을 사용할 경우 fetchUser 함수의 참조값을 동일하게 유지&lt;/b&gt;&lt;/span&gt;시킬 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667464164475&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from &quot;react&quot;;

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = useCallback(
    () =&amp;gt;
      fetch(`https://your-api.com/users/${userId}`)
        .then((response) =&amp;gt; response.json())
        .then(({ user }) =&amp;gt; user),
    [userId]
  );

  useEffect(() =&amp;gt; {
    fetchUser().then((user) =&amp;gt; setUser(user));
  }, [fetchUser]);

  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api의 옵션으로 사용되는 userId가 변동될 때만 fetchUser에 새로운 함수가 할당되도록 설정하고, 그것이 아니면 동일한 함수가 실행되게 되서 무한 루프에 빠지지 않도록 할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>useCallback</category>
      <category>useMemo</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/273</guid>
      <comments>https://narup.tistory.com/273#entry273comment</comments>
      <pubDate>Thu, 3 Nov 2022 17:41:58 +0900</pubDate>
    </item>
    <item>
      <title>[React] 렌더링(리렌더링)</title>
      <link>https://narup.tistory.com/272</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 공부하면서 어떻게 보면 가장 기초이고, 가장 중요한 부분인데 사실 이제야 포스팅한다는 것은 이제까지 리액트를 잘못 사용해왔다는 반증이기도 한데... 조금 더 심도있게 리액트를 알아보기 위해서 렌더링이란 무엇이고, 렌더링의 조건이 어떻게 되는 건지 확인차 적어보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 렌더링이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 Welcome을 컴포넌트라고 부릅니다. (정확히는 함수 컴포넌트)&lt;/p&gt;
&lt;pre id=&quot;code_1667449262355&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Welcome(props) {
  return &amp;lt;h1&amp;gt;Hello, {props.name}&amp;lt;/h1&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;렌더링이란&lt;/b&gt;&lt;/span&gt; 컴포넌트가 현재 props와 state의 상태에 기초해 UI를 어떻게 구성할지, 컴포넌트에게 작업을 요청하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 렌더링 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. React의 렌더링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 리액트의 렌더링 개념은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;루트(&amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;) DOM&lt;/b&gt;&lt;/span&gt;부터 시작해 플래그가 지정되어 있는 모든 컴포넌트를 찾아서 렌더링을 진행하는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* root id는 public/index.html에 기본적으로 설정이 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* DOM은 HTML parser에 의해 생성된 트리 구조의 Node 객체 모델입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667449421711&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const root = ReactDOM.createRoot(
  document.getElementById('root')
);
const element = &amp;lt;h1&amp;gt;Hello, world&amp;lt;/h1&amp;gt;;
root.render(element);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 리액트의 렌더링은 이러한 DOM 엘리먼트를 ReactDOM.createRoot()에 전달한 다음, 그 React Element를 root.render()에 전달하는 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. React Element&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그럼 React Element는 무엇일까요? 리액트 엘리먼트는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;type&lt;/b&gt;&lt;/span&gt;과&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; props&lt;/b&gt;&lt;/span&gt;를 가지는&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; React만의 객체&lt;/b&gt;&lt;/span&gt;입니다. React.creatElement()를 이용해 만들 수 있으며, type으로 HTML 태그 이름을 가지고, 그 이외의 특징을 props로 관리하는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;객체 형태&lt;/b&gt;&lt;/span&gt;로 정의됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667451157432&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// createElement를 이용해서 React Element 만들기
React.createElement(
  'div',
  { className: 'name' },
  'React'
)
// 위와 같은 의미 (JSX 문법)
&amp;lt;div className='name'&amp;gt;React&amp;lt;/div&amp;gt;


// createElement를 이용해서 만들어진 React Element 객체
{
  type: 'div',
  props: {
    className: 'name',
    children: 'React'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위와 같은 createElement()라는 함수를 사용하기가 어렵기도 하고, 직관적이지도 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;JSX 문법&lt;/b&gt;&lt;/span&gt;을 사용해 보통 위의 객체를 DOM 형태(&amp;lt;div className='name'&amp;gt;React&amp;lt;/div&amp;gt;)로 정의하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 리액트 엘리먼트는 불변객체로 작용해서, 엘리먼트가 생성된 이후에는 해당 엘리먼트의 속성과 자식을 변경할 수 없다는 특징을 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. Virtual DOM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 특징 중 하나의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;가상 돔(Virtual DOM)&lt;/b&gt;&lt;/span&gt;은 실제 DOM의 구조와 비슷한 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;React 객체의 트리&lt;/b&gt;&lt;/span&gt;를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에서 사용자는 여러 가지 액션을 통해 웹 서버에 요청하고, 그에 따른 응답으로 DOM 구조가 빈번하게 바뀌는데, 이때마다 DOM이 수정되어 Render Tree가 생성되고, Reflow, Repaint의 과정을 계속 수행하게 된다면 굉장히 답답한 상황이 발생하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 React에서는 Virtual DOM을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;실제 DOM에 필요한 부분만 적절하게 반영&lt;/b&gt;&lt;/span&gt;해서 불필요한 수정이 일어나지 않도록 해주는데, 이 Virtual DOM의 가장 큰 장점은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;개발자가 직접 DOM을 조작하지 않아도 된다&lt;/b&gt;&lt;/span&gt;는 점이고, 이러한 과정을 모두 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;자동화&lt;/b&gt;&lt;/span&gt;해준다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 DOM의 수정을&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; batch로 한 번에 처리&lt;/b&gt;&lt;/span&gt;하기 때문에 리렌더링 연산을 최소화 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. 재조정(Reconciliation)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한 Virtual DOM에서 어떻게 리액트는 실제 DOM과 가상 DOM을 구별하는지 궁금할 텐데, 결과적으로 브라우저에 렌더링을 할 때 어떻게 기존의 컴포넌트와 변경이 되었는지 확인하기 위해 리액트에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;diffing 알고리즘&lt;/b&gt;&lt;/span&gt;을 사용하여&amp;nbsp; 이 알고리즘을 통해 컴포넌트의 갱신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 아래의 2가지 가정을 기반으로 O(n)의 시간복잡도를 가지는 &lt;u&gt;휴리스틱 알고리즘&lt;/u&gt;을 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어 낸다.&lt;/li&gt;
&lt;li&gt;개발자가&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; key prop&lt;/b&gt;&lt;/span&gt;을 통해 컴포넌트 인스턴스를 식별하여, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야할지 표시해 줄 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 재조정 단계를 거쳐 이전 elements와 새로 생성된 elements를 비교해 엘리먼트가 변경되었다면 렌더링을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 리렌더링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 렌더링을 설명했는데, 리액트에선 초기에 한번 렌더링을 진행하고, 그 이후에 특정 조건이 발생하면 다시 렌더링을 진행하는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;리렌더링&lt;/b&gt;&lt;/span&gt;이라고 하는 것이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 상태(state) 변경시&lt;/li&gt;
&lt;li&gt;부모에게 전달받은 값(props) 변경시&lt;/li&gt;
&lt;li&gt;중앙 상태값(Context value 혹은 redux store) 변경시&lt;/li&gt;
&lt;li&gt;부모 컴포넌트가 리렌더링 되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우가 컴포넌트가 리렌더링 되는 조건입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트가 아무리 최적화가 잘 되어있다고해도, 무분별하게 렌더링이 일어날 경우 성능 저하가 일어나게 되기 때문에, 이러한 조건들을 기준을 두고 코드를 작성하여 무분별하게 렌더링이 일어나지 않도록 주의하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;redux store 변경&lt;/b&gt;&lt;/span&gt;시 자동으로 리렌더링이 되는 이유는, 리덕스 스토어가 &amp;lt;Provider store={store}&amp;gt;로 컴포넌트를 감싸주었을 때, 스토어 상태가 변경될 때마다 이를 참조하는 컴포넌트들이 리렌더링이 될 수 있도록 react-redux 라이브러리가 자동적으로 컴포넌트 들의 렌더 함수들을 subscribe 해주기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 리렌더링 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위의 조건을 통해 컴포넌트 리렌더링&lt;/li&gt;
&lt;li&gt;구현부 실행 = props 취득, hook 실행, 내부 변수 및 함수 재 생성&lt;/li&gt;
&lt;li&gt;return 실행, 렌더링 시작&lt;/li&gt;
&lt;li&gt;렌더 단계(Render Phase): 새로운 가상 DOM 생성 후 이전 가상 DOM과 비교해 달라진 부분을 탐색하고 실제 DOM에 반영할 부분을 결정&lt;/li&gt;
&lt;li&gt;커밋 단계(Commit Phase): 달라진 부분만 실제 DOM에 반영&lt;/li&gt;
&lt;li&gt;useLayoutEffect: 브라우저가 화면에 Paint하기 전에 useLayoutEffect에 등록해둔 effect(부수 효과 함수)가 동기적으로 실행되며, 이때 state, redux store 등의 변경이 있다면 한번 더 리렌더링&lt;/li&gt;
&lt;li&gt;Paint: 브라우저가 실제 DOM을 화면에 그림. didUpdate 완료.&lt;/li&gt;
&lt;li&gt;useEffect: update되어 화면에 그려진 직후, useEffect에 등록해둔 effect(부수 효과 함수)가 비동기로 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>렌더링</category>
      <category>리렌더링</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/272</guid>
      <comments>https://narup.tistory.com/272#entry272comment</comments>
      <pubDate>Thu, 3 Nov 2022 15:09:40 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] Utility Type 정리(ReturnType, Required 등)</title>
      <link>https://narup.tistory.com/271</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Partial&amp;lt;Type&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type 집합의 모든 프로퍼티를 선택적으로 타입을 생성해줍니다. 그리고 이 유틸리티는 특정 타입의 부분 집합을 만족하는 타입을 정의할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022576&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Address {
  email: string;
  address: string;
}

type MyEmail = Partial&amp;lt;Address&amp;gt;;
const me: MyEmail = {}; // 가능
const you: MyEmail = { email: &quot;noh5524@gmail.com&quot; }; // 가능
const all: MyEmail = { email: &quot;noh5524@gmail.com&quot;, address: &quot;secho&quot; }; // 가능

interface Product {
  id: number;
  name: string;
  price: number;
  brand: string;
  stock: number;
}

// Partial - 상품의 정보를 업데이트 (put) 함수 -&amp;gt; id, name 등등 어떤 것이든 인자로 들어올수있습니다
// 현재 Product type은 인자에 type으로 Product를 넣으면 모든 정보를 다 넣어야하는데, 사실
// 아래와 같이 인터페이스를 정의하면 부분적인 데이터만 넣어도 정상적으로 작동합니다.
interface UpdateProduct {
  id?: number;
  name?: string;
  price?: number;
  brand?: string;
  stock?: number;
}
// 그러나 같은 인터페이스를 또 정의하는 것은 비용이 발생하기 때문에
// 이것을 피하기 위해서 Partial을 활용합니다.
function updateProductItem(prodictItem: Partial&amp;lt;Product&amp;gt;) {
  // Partial&amp;lt;Product&amp;gt;이 타입은 UpdateProduct 타입과 동일하다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Required&amp;lt;Type&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Required는 Partial과 반대로 모든 프로퍼티를 필수로 설정한 타입을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022577&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Props {
  a?: number;
  b?: string;
}
 
const obj: Props = { a: 5 };
 
const obj2: Required&amp;lt;Props&amp;gt; = { a: 5 };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Pick&amp;lt;Type, Keys&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pick 타입은 특정 타입에서 Keys라는 몇 개의 속성을 자유롭게 선택(title, completed)해 타입을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022577&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick&amp;lt;Todo, &quot;title&quot; | &quot;completed&quot;&amp;gt;;
 
const todo: TodoPreview = {
  title: &quot;Clean room&quot;,
  completed: false,
};
console.log(todo)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Omit&amp;lt;Type, Keys&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Omit 타입은 Pick과 반대로 모든 프로퍼티를 선택한 상태에서 특정 Key를 제외한 타입을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022578&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Omit&amp;lt;Todo, &quot;description&quot;&amp;gt;;
 
const todo: TodoPreview = {
  title: &quot;Clean room&quot;,
  completed: false,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Readonly&amp;lt;Type&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type 집합의 모든 프로퍼티를 읽기 전용으로 설정한 타입을 생성합니다. 이렇게 될 경우 생성된 타입의 프로퍼티는 재할당 될 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022578&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
}
 
const todo: Readonly&amp;lt;Todo&amp;gt; = {
  title: &quot;Delete inactive users&quot;,
};
 
 // Cannot assign to 'title' because it is a read-only property.
todo.title = &quot;Hello&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Record&amp;lt;Keys, Type&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;타입&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Type&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;의 프로퍼티&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;키&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;의 집합으로 타입을 생성합니다. 이 유틸리티는 타입의 프로퍼티를 다른 타입에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;매핑&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;시키는데 사용될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1667437022579&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface PageInfo {
  title: string;
}
 
type Page = &quot;home&quot; | &quot;about&quot; | &quot;contact&quot;;
 
const nav: Record&amp;lt;Page, PageInfo&amp;gt; = {
  about: { title: &quot;about&quot; },
  contact: { title: &quot;contact&quot; },
  home: { title: &quot;home&quot; },
};
 
console.log(nav.about);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*Page라는 키에 PageInfo라는 타입을 Record로 연결시켜 각각의 키의 title을 정의하도록 매핑했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. ReturnType&amp;lt;Type&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 Type의 반환 타입으로 구성된 타입을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667437022580&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;declare function f1(): { a: number; b: string };
 
type T0 = ReturnType&amp;lt;() =&amp;gt; string&amp;gt;;     
// type T0 = string

type T1 = ReturnType&amp;lt;(s: string) =&amp;gt; void&amp;gt;;     
// type T1 = void

type T2 = ReturnType&amp;lt;&amp;lt;T&amp;gt;() =&amp;gt; T&amp;gt;;     
// type T2 = unknown

type T3 = ReturnType&amp;lt;&amp;lt;T extends U, U extends number[]&amp;gt;() =&amp;gt; T&amp;gt;;     
// type T3 = number[]

type T4 = ReturnType&amp;lt;typeof f1&amp;gt;;     
/*
type T4 = {
    a: number;
    b: string;
}
*/


type T5 = ReturnType&amp;lt;any&amp;gt;;     
//type T5 = any

type T6 = ReturnType&amp;lt;never&amp;gt;;     
//type T6 = never&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>TypeScript</category>
      <category>UtilityType</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/271</guid>
      <comments>https://narup.tistory.com/271#entry271comment</comments>
      <pubDate>Thu, 3 Nov 2022 09:57:31 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 고차 함수 정리(array, reduce, filter 등)</title>
      <link>https://narup.tistory.com/270</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차함수는 외부 상태 변경이나 mutable(가변적인) 데이터를 피하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;불변성(Immutability)&lt;/b&gt;&lt;/span&gt;를 지향하는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;함수형 프로그래밍&lt;/b&gt;&lt;/span&gt;에 기반을 두고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;함수형 프로그래밍&lt;/b&gt;&lt;/span&gt;은 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거해 복잡성을 해결하고, 변수의 사용을 억제해 상태 변경을 피하려고 하는 프로그래밍 패러다임 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 해 가독성을 해치고, 변수의 값을 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적인 원인이 될 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 함수형 프로그래밍은 순수 함수를 통해 &lt;u&gt;side effect를 최대한 억제해 오류를 피하고 프로그램의 안정성을 높이는 방향&lt;/u&gt;으로 이어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Array.prototype.sort()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sort는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Timesort 알고리즘&lt;/b&gt;&lt;/span&gt;을 사용한 순수함수입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667291644082&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fruits = ['Banana', 'Orange', 'Apple'];

// ascending(오름차순)
fruits.sort();
console.log(fruits); // [ 'Apple', 'Banana', 'Orange' ]

// descending(내림차순)
fruits.reverse();
console.log(fruits); // [ 'Orange', 'Banana', 'Apple' ]

const points = [40, 100, 1, 5, 2, 25, 10];

points.sort();
console.log(points); // [ 1, 10, 100, 2, 25, 40, 5 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 단어, 숫자도 같이 오름순, 내림순으로 정리되며, 이때 원본 배열이 정렬된 배열로 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Array.prototype.foreach()&lt;/h2&gt;
&lt;pre id=&quot;code_1667291790061&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 2, 3];
let pows = [];

// for 문으로 순회
for (let i = 0; i &amp;lt; numbers.length; i++) {
  pows.push(numbers[i] ** 2);
}

console.log(pows); // [ 1, 4, 9 ]

pows = [];

// forEach 메소드로 순회
numbers.forEach(function (item) {
  pows.push(item ** 2);
});

// ES6 화살표 함수
// numbers.forEach(item =&amp;gt; pows.push(item ** 2));

console.log(pows); // [ 1, 4, 9 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foreach는 배열을 순회해 배열의 각 요소에 대해 인자로 주어진 콜백함수를 실행하는데, 반환값은 undefind입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foreach의 특징은 for문과 다르게 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;중간에서 break를 할 수 없다&lt;/b&gt;&lt;/span&gt;는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foreach는 원본 배열(this)를 변경하지 않지만, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;foreach에서 사용된 콜백 함수 function(item)은 원본 배열을 변경&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Array.prototype.map()&lt;/h2&gt;
&lt;pre id=&quot;code_1667291919184&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 4, 9];

// 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수를 실행
const roots = numbers.map(function (item) {
  // 반환값이 새로운 배열의 요소가 된다. 반환값이 없으면 새로운 배열은 비어 있다.
  return Math.sqrt(item);
});

// 위 코드의 축약표현은 아래와 같다.
// const roots = numbers.map(Math.sqrt);

// map 메소드는 새로운 배열을 반환한다
console.log(roots);   // [ 1, 2, 3 ]
// map 메소드는 원본 배열은 변경하지 않는다
console.log(numbers); // [ 1, 4, 9 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map은 배열을 순회해 각 요소에 대해 인자로 주어진 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;콜백함수의 반환값(결과값)으로 새로운 배열을 생성해 반환&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 원본 배열은 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Array.prototype.&lt;/span&gt;filter()&lt;/h2&gt;
&lt;pre id=&quot;code_1667291987137&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const result = [1, 2, 3, 4, 5].filter(function (item, index, self) {
  console.log(`[${index}] = ${item}`);
  return item % 2; // 홀수만을 필터링한다 (1은 true로 평가된다)
});

console.log(result); // [ 1, 3, 5 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter 메소드를 사용하면 if문을 대체할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 순회하며 각 요소에 대해 인자로 주어진 콜백함수의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;실행 결과가 true인 요소의 값만을 추출&lt;/b&gt;&lt;/span&gt;해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;새로운 배열을 반환&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열에서 특정 케이스만 필터링을 걸어 새로운 배열을 추출할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 원본 배열은 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Array.prototype.reduce()&lt;/h2&gt;
&lt;pre id=&quot;code_1667292090793&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3, 4, 5];

/*
previousValue: 이전 콜백의 반환값
currentValue : 배열 요소의 값
currentIndex : 인덱스
array        : 메소드를 호출한 배열, 즉 this
*/
// 합산
const sum = arr.reduce(function (previousValue, currentValue, currentIndex, self) {
  console.log(previousValue + '+' + currentValue + '=' + (previousValue + currentValue));
  return previousValue + currentValue; // 결과는 다음 콜백의 첫번째 인자로 전달된다
});

console.log(sum); // 15: 1~5까지의 합
/*
1: 1+2=3
2: 3+3=6
3: 6+4=10
4: 10+5=15
15
*/

// 최대값 취득
const max = arr.reduce(function (pre, cur) {
  return pre &amp;gt; cur ? pre : cur;
});

console.log(max); // 5: 최대값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 순회해 각 요소에 대해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이전의 콜백함수 실행 반환 값&lt;/b&gt;&lt;/span&gt;을 전달해 콜백함수를 실행하고 그 결과를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합산, 최소/최대값, 평균 등 계산식에 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpJiiz/btrP8YLANjN/DHGwMgtkYKcwB9QruP1Xgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpJiiz/btrP8YLANjN/DHGwMgtkYKcwB9QruP1Xgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpJiiz/btrP8YLANjN/DHGwMgtkYKcwB9QruP1Xgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpJiiz%2FbtrP8YLANjN%2FDHGwMgtkYKcwB9QruP1Xgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;445&quot; height=&quot;153&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 원본 배열은 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667292244993&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const sum = [1, 2, 3, 4, 5].reduce(function (pre, cur) {
  return pre + cur;
}, 5);

console.log(sum); // 20
// 5 + 1 =&amp;gt; 6 + 2 =&amp;gt; 8 + 3 =&amp;gt; 11 + 4 =&amp;gt; 15 + 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce는 위와 같이 두번째 파라미터에 초기값을 세팅해서 호출할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Array.prototype.some()&lt;/h2&gt;
&lt;pre id=&quot;code_1667292785554&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열 내 요소 중 10보다 큰 값이 1개 이상 존재하는지 확인
let res = [2, 5, 8, 1, 4].some(function (item) {
  return item &amp;gt; 10;
});
console.log(res); // false

res = [12, 5, 8, 1, 4].some(function (item) {
  return item &amp;gt; 10;
});
console.log(res); // true

// 배열 내 요소 중 특정 값이 1개 이상 존재하는지 확인
res = ['apple', 'banana', 'mango'].some(function (item) {
  return item === 'banana';
});
console.log(res); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 내 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;일부 요소가 콜백함수의 조건을 만족하는지 확인&lt;/b&gt;&lt;/span&gt;해 그 결과를 boolean 타입으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 배열의 원본은 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Array.prototype.every()&lt;/h2&gt;
&lt;pre id=&quot;code_1667292831845&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열 내 모든 요소가 10보다 큰 값인지 확인
let res = [21, 15, 89, 1, 44].every(function (item) {
  return item &amp;gt; 10;
});
console.log(res); // false

res = [21, 15, 89, 100, 44].every(function (item) {
  return item &amp;gt; 10;
});
console.log(res); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;배열 내 모든 요소&lt;/b&gt;&lt;/span&gt;가 콜백함수의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;조건을 만족하는지 확인&lt;/b&gt;&lt;/span&gt;해 그 결과를 boolean 타입으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 배열의 원본은 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Array.prototype.find()&lt;/h2&gt;
&lt;pre id=&quot;code_1667292900772&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소를 반환한다.
let result = users.find(function (item) {
  return item.id === 2;
});

// ES6
// const result = users.find(item =&amp;gt; item.id === 2;);

// Array#find는 배열이 아니라 요소를 반환한다.
console.log(result); // { id: 2, name: 'Kim' }

// Array#filter는 콜백함수의 실행 결과가 true인 배열 요소의 값만을 추출한 새로운 배열을 반환한다.
result = users.filter(function (item) {
  return item.id === 2;
});

console.log(result); // [ { id: 2, name: 'Kim' },{ id: 2, name: 'Choi' } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 순회해 각 요소에 대해 인자로 주어진 콜백함수를 실행해, 그 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;결과가 참인 첫번째 요소를 반환&lt;/b&gt;&lt;/span&gt;합니다. 만약, 참인 요소가 존재하지 않을 경우 undefined를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter와 다른 점은 find는 요소를 반환하는 것이고, filter는 배열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Array.prototype.findIndex()&lt;/h2&gt;
&lt;pre id=&quot;code_1667293015133&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다.
function predicate(key, value) {
  return function (item) {
    return item[key] === value;
  };
}

// id가 2인 요소의 인덱스
let index = users.findIndex(predicate('id', 2));
console.log(index); // 1

// name이 'Park'인 요소의 인덱스
index = users.findIndex(predicate('name', 'Park'));
console.log(index); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 순회해 각 요소에 대해 인자로 주어진 콜백함수를 실행해, 그 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;결과가 참인 첫번째 요소의 인덱스를 반환&lt;/b&gt;&lt;/span&gt;합니다. 만약, 참인 요소가 존재하지 않는다면 -1을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-array-higher-order-function&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://poiemaweb.com/js-array-higher-order-function&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667293025657&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Higher order function | PoiemaWeb&quot; data-og-description=&quot;배열(array)는 1개의 변수에 여러 개의 값을 순차적으로 저장할 때 사용한다. 자바스크립트의 배열은 객체이며 유용한 내장 메소드를 포함하고 있다.&quot; data-og-host=&quot;poiemaweb.com&quot; data-og-source-url=&quot;https://poiemaweb.com/js-array-higher-order-function&quot; data-og-url=&quot;https://poiemaweb.com/js-array-higher-order-function&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sg7vs/hyQpE6UaFC/icFocsY7z9qv7stMNBbBj1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/deVruo/hyQpC836wU/PX7tdT52OYaN1rMe7wP4Nk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bSVyor/hyQqZn1l9v/ePdMDR3D2kXwlbF2kjrSKk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-array-higher-order-function&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://poiemaweb.com/js-array-higher-order-function&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sg7vs/hyQpE6UaFC/icFocsY7z9qv7stMNBbBj1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/deVruo/hyQpC836wU/PX7tdT52OYaN1rMe7wP4Nk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bSVyor/hyQqZn1l9v/ePdMDR3D2kXwlbF2kjrSKk/img.png?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Higher order function | PoiemaWeb&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배열(array)는 1개의 변수에 여러 개의 값을 순차적으로 저장할 때 사용한다. 자바스크립트의 배열은 객체이며 유용한 내장 메소드를 포함하고 있다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;poiemaweb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/270</guid>
      <comments>https://narup.tistory.com/270#entry270comment</comments>
      <pubDate>Tue, 1 Nov 2022 17:59:25 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 깊이우선탐색(dfs) / 넓이우선탐색(bfs) (feat.javascript)</title>
      <link>https://narup.tistory.com/269</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 깊이우선탐색(dfs)&lt;/h2&gt;
&lt;pre id=&quot;code_1666687314188&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const graph = {
  A: [&quot;B&quot;, &quot;C&quot;],
  B: [&quot;A&quot;, &quot;D&quot;],
  C: [&quot;A&quot;, &quot;G&quot;, &quot;H&quot;, &quot;I&quot;],
  D: [&quot;B&quot;, &quot;E&quot;, &quot;F&quot;],
  E: [&quot;D&quot;],
  F: [&quot;D&quot;],
  G: [&quot;C&quot;],
  H: [&quot;C&quot;],
  I: [&quot;C&quot;, &quot;J&quot;],
  J: [&quot;I&quot;]
};

const DFS = (graph, startNode) =&amp;gt; {
  const visited = []; // 탐색을 마친 노드들
  let needVisit = []; // 탐색해야할 노드들

  needVisit.push(startNode); // 노드 탐색 시작

  while (needVisit.length !== 0) { // 탐색해야할 노드가 남아있다면
    const node = needVisit.shift(); // queue이기 때문에 선입선출, shift()를 사용한다.
    if (!visited.includes(node)) { // 해당 노드가 탐색된 적 없다면
      visited.push(node); 
      needVisit = [...graph[node], ...needVisit];
    }
  }
  return visited;
};

console.log(DFS(graph, &quot;A&quot;));
// [&quot;A&quot;, &quot;B&quot;, &quot;D&quot;, &quot;E&quot;, &quot;F&quot;, &quot;C&quot;, &quot;G&quot;, &quot;H&quot;, &quot;I&quot;, &quot;J&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 넓이우선탐색(bfs)&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1666687297876&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const graph = {
  A: [&quot;B&quot;, &quot;C&quot;],
  B: [&quot;A&quot;, &quot;D&quot;],
  C: [&quot;A&quot;, &quot;G&quot;, &quot;H&quot;, &quot;I&quot;],
  D: [&quot;B&quot;, &quot;E&quot;, &quot;F&quot;],
  E: [&quot;D&quot;],
  F: [&quot;D&quot;],
  G: [&quot;C&quot;],
  H: [&quot;C&quot;],
  I: [&quot;C&quot;, &quot;J&quot;],
  J: [&quot;I&quot;]
};

const BFS = (graph, startNode) =&amp;gt; {
  const visited = []; // 탐색을 마친 노드들
  let needVisit = []; // 탐색해야할 노드들

  needVisit.push(startNode); // 노드 탐색 시작

  while (needVisit.length !== 0) { // 탐색해야할 노드가 남아있다면
    const node = needVisit.shift(); // queue이기 때문에 선입선출, shift()를 사용한다.
    if (!visited.includes(node)) { // 해당 노드가 탐색된 적 없다면
      visited.push(node);
      needVisit = [...needVisit, ...graph[node]];
    }
  }
  return visited;
};

console.log(BFS(graph, &quot;A&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>알고리즘</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/269</guid>
      <comments>https://narup.tistory.com/269#entry269comment</comments>
      <pubDate>Tue, 25 Oct 2022 17:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[React] 불변성이란? 불변성을 지켜야 하는 이유</title>
      <link>https://narup.tistory.com/268</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 빠르게 배울 때 값을 변경할 때 &lt;u&gt;useState를 사용&lt;/u&gt;해야 한다, &lt;u&gt;불변성을 유지&lt;/u&gt;해야 한다, &lt;u&gt;immer를 사용&lt;/u&gt;해야 한다, &lt;u&gt;spread 연산자를 사용&lt;/u&gt;해야 한다, 공식처럼 생각하고 있었는데 javascript의 메모리 구조에 대해 알아보면서 react의 리렌더링이 일어나는 과정이나 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;불변성의 진짜 의미&lt;/b&gt;&lt;/span&gt;는 무엇일지 찾아보다가 괜찮은 블로그를 발견해 요점만 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. javascript 메모리 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5aBqc/btrPtiC4SbZ/7SsGSZftnYIjFSkQXn37S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5aBqc/btrPtiC4SbZ/7SsGSZftnYIjFSkQXn37S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5aBqc/btrPtiC4SbZ/7SsGSZftnYIjFSkQXn37S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5aBqc%2FbtrPtiC4SbZ%2F7SsGSZftnYIjFSkQXn37S0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;329&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;call stack&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;heap memory&lt;/b&gt;&lt;/span&gt; 2가지 메모리 공간을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콜스택&lt;/b&gt;: 실행 중인 함수를 추적해 계산을 수행하고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;지역변수를 저장&lt;/b&gt;&lt;/span&gt;하는 공간입니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이곳에 원시 타입들이 저장&lt;/b&gt;&lt;/span&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힙 메모리&lt;/b&gt;: &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;참조 타입들이 할당&lt;/b&gt;&lt;/span&gt;되는 곳입니다. 메모리 누수를 방지하기 위해 js 엔진의 메모리 관리자가 항상 관리하는 공간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;원시 타입&lt;/b&gt; : Boolean, String, Number, null, undefined, Symbol&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;참조 타입&lt;/b&gt; : Object, Array&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2가지의 타입을 꼭 구별해야 합니다! 왜냐면 이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;타입별로 데이터 저장방식과 할당 방식이 달라지기 때문&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 원시타입과 참조 타입의 데이터 저장방식과 재할당 비교&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 원시타입과 참조 타입의 데이터 저장방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzGrOi/btrPoQ2eegS/kKbxmKfTcPZGiIAxsRKVnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzGrOi/btrPoQ2eegS/kKbxmKfTcPZGiIAxsRKVnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzGrOi/btrPoQ2eegS/kKbxmKfTcPZGiIAxsRKVnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzGrOi%2FbtrPoQ2eegS%2FkKbxmKfTcPZGiIAxsRKVnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;793&quot; height=&quot;387&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원시타입&lt;/b&gt;: 변수 a에 값 10을 저장을 했을 경우 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;콜 스택의 변수값에 10이 그대로 저장&lt;/b&gt;&lt;/span&gt;됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조타입&lt;/b&gt;: 변수 b,c,d에 array, object를 저장할 경우 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;실제 값은 메모리 힙에 저장&lt;/b&gt;&lt;/span&gt;되고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;메모리의 힙의 주소가 콜 스택의 값에 저장&lt;/b&gt;&lt;/span&gt;이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 변수 할당과 재할당&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;원시 타입의 특징&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lkDcx/btrPsOoZSot/5ThnsZ51rLDrIEsVRQjtA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lkDcx/btrPsOoZSot/5ThnsZ51rLDrIEsVRQjtA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lkDcx/btrPsOoZSot/5ThnsZ51rLDrIEsVRQjtA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlkDcx%2FbtrPsOoZSot%2F5ThnsZ51rLDrIEsVRQjtA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;470&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시타입의 변수는 변수값을 변경하라는 명령을 받을 경우, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기존 콜스택의 값을 변경하지 않고 새로운 주소를 추가해 값을 저장하고 변수 b가 바라보게 합니다&lt;/b&gt;&lt;/span&gt;. 이것을 저희가 궁금해 하던 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;불변성&lt;/b&gt;&lt;/span&gt;이라고 합니다.(메모리 영역의 값은 변경되지 않는다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;* 여기서 더이상 참조되지 않는 데이터는 가비지 컬렉터에 의해 적절한 시점에서 메모리가 해제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;참조 타입의 특징&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EFMsz/btrPjGF55rh/MOXkpSVizo6se4cr8FgEE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EFMsz/btrPjGF55rh/MOXkpSVizo6se4cr8FgEE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EFMsz/btrPjGF55rh/MOXkpSVizo6se4cr8FgEE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEFMsz%2FbtrPjGF55rh%2FMOXkpSVizo6se4cr8FgEE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;793&quot; height=&quot;365&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a.에서 변수값을 할당하고, b.에서 push를 통해 값을 추가하게 되면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시타입처럼 불변성이 지켜질까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 변수 a,b가 바라보고 있는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;콜스택의 값이 변경되지 않고, 메모리 힙에 있는 데이터가 변경이 되어 불변성이 유지가 되지 않습니다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 리액트에서 불변성을 지켜야 하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불변성을 지킨다의 의미는 &lt;u&gt;메모리 영역에서 값을 변경할 수 없게 한다&lt;/u&gt;, 라는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;리액트의 state 변화 감지 기준은 콜 스택의 주소값&lt;/b&gt;&lt;/span&gt;이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 콜 스택의 주소값만을 비교해 상태 변화를 감지하는데, 이를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;얕은 비교&lt;/b&gt;&lt;/span&gt;라고 합니다. 이것이 리액트의 state를 빠르게 감지할 수 있는 장점이자 불변성을 유지해야 하는 이유입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; 원시타입&lt;/b&gt;&lt;/span&gt;의 경우 3-2처럼 값을 재할당할 경우 새로운 메모리가 할당되어 콜 스택의 주소 값이 감지가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 하지만 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;참조타입&lt;/b&gt;&lt;/span&gt;의 경우 참조타입의 값만 변경하면 실제로 콜스택의 주소값은 변경이 없어 state 감지가 되지 않아 리렌더링이 되지가 않습니다. 그래서 저희가 spread 연산자를 쓰고, immer 라이브러리를 사용해 새로운 배열과 오브젝트를 만들어 반환하는 이유가 그것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@badahertz52/%EC%B0%B8%EC%A1%B0%ED%83%80%EC%9E%85%EA%B3%BC-React%EC%9D%98-%EB%B6%88%EB%B3%80%EC%84%B1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@badahertz52/%EC%B0%B8%EC%A1%B0%ED%83%80%EC%9E%85%EA%B3%BC-React%EC%9D%98-%EB%B6%88%EB%B3%80%EC%84%B1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 이슈</category>
      <category>react</category>
      <category>불변성</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/268</guid>
      <comments>https://narup.tistory.com/268#entry268comment</comments>
      <pubDate>Mon, 24 Oct 2022 14:40:54 +0900</pubDate>
    </item>
    <item>
      <title>[Node.js] for 동기 처리(Promise, Mysql)</title>
      <link>https://narup.tistory.com/267</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 데이터베이스에서 데이터를 가지고 B 데이터베이스에 가공해서 Insert하는 프로그램을 짜는 과정에서 node.js 기반으로 프로그램을 짜보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에는 자바로 짯지만 node.js로 코드를 짜면 npm 패키지로 쉽게 의존성이 필요한 라이브러리를 다운받을 수 있고, Linux에서도 쉽게 배포할 수 있어서 node.js를 사용해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;*필요했던 의존성 라이브러리는 데이터베이스 커넥터와 스케쥴러 &lt;/span&gt;&lt;/span&gt;2개였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUrAfZ/btrO9CRiQUD/6FHIi1wZiigFi2FEI56usK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUrAfZ/btrO9CRiQUD/6FHIi1wZiigFi2FEI56usK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUrAfZ/btrO9CRiQUD/6FHIi1wZiigFi2FEI56usK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUrAfZ%2FbtrO9CRiQUD%2F6FHIi1wZiigFi2FEI56usK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;762&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 java와 다르게 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;node.js는 대부분 비동기 처리&lt;/b&gt;&lt;/span&gt;가 되어버려서 데이터를 가공하는데 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;순서&lt;/b&gt;&lt;/span&gt;를 맞추기가 굉장히 곤란했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A function output -&amp;gt; B function execute(x10)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 순차적으로 실행이 되는 로직으로 코드를 작성했고, java 기반에서는 로직이었는데 Node.jjs에서는 생각하는 방향대로 동작하지가 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. for 문 안의 Promise&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 아래 코드를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1666330207643&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (let i = 0; i &amp;lt; 10; i++) {
    new Promise((resolve, reject) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(i);
        }, 1000);          // 1초마다 resolve값으로 i가 넘어가서,
    })
    .then((data) =&amp;gt; {
    	console.log(data); // 0,1,2.. 이런 순서대로 나오겠지? 
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초마다 Promise의 settimeout가 작동해 i가 출력되는 것을 생각을 할텐데 결과는 그렇지가 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초뒤 0~9가 한꺼번에 출력되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 for문 내부에서 명령문인 new Promise가 실행되어 Promise 객체가 생성되고, 10개의 Promise 객체가 생성되면서 1초뒤에 resolve를 실행하는 명령문이 setTimeout 함수를 통해 예약됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 동기적인 작업이 for문이 끝나기 전까지는 Promise가 실행되지 않아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;for문이 종료될 때까지 Promise는 pending(대기)&lt;/b&gt;&lt;/span&gt;에 있다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문이 종료되면 1초 뒤 모든 Promise의 resolve를 실행해서 then으로 i값을 전달해서 1초 뒤에 0~9가 한꺼번에 출력되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://velog.io/@thms200/2020-01-28-2301-%EC%9E%91%EC%84%B1%EB%90%A8-fwk5xyifw7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@thms200/2020-01-28-2301-%EC%9E%91%EC%84%B1%EB%90%A8-fwk5xyifw7&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같이 Promise 객체를 밖으로 빼낸 상태에서 코드를 작성하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666330746256&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; async function test() {
  const promiseFunction = (i) =&amp;gt;
        new Promise((resolve) =&amp;gt; setTimeout(() =&amp;gt; resolve(i), 1000));

    let arr = Array(10).fill(0);
    
    for (let i = 0; i &amp;lt; arr.length; i++) {
        const result = await promiseFunction(i);
        console.log(result);
    }
  }
test()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Mysql Query 동기적 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선적으로 Mysql Query가 동기적으로 적용되는 코드를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js에서 사용할 수 있는 Mysql 패키지를 install합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666338958812&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i mysql promise-mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 적용한 전체 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리와 필요한 파라미터에 대한 처리는 자유롭게 하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.js&lt;/p&gt;
&lt;pre id=&quot;code_1666329158716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mysql = require('mysql2/promise');

const func = () =&amp;gt; {
  const pool = mysql.createPool({
    host: '주소',
    port: 포트,
    user: '아이디',
    password: '비번',
    database: '스키마'
  });
  
  const getSiteListQuery = async (agoDate, lastDate) =&amp;gt; {
    const connection = await pool.getConnection(async conn =&amp;gt; conn);
    try {
      const [rows] = await connection.query(&quot;SELECT deviceid, siteid  FROM FiveMinuteAccmofMeasureMentsMeter where createts BETWEEN ? and ? GROUP by deviceid, siteid order by siteid, createts desc;&quot;, [agoDate, lastDate]);
      connection.release();
      console.log('call')
      return rows
    } catch (err) {
      console.log('Query Error');
      connection.release();
      return false;
    }
  }

  const getSearchList = async (agoDate, lastDate, siteid, deviceid) =&amp;gt; {
    const connection = await pool.getConnection(async conn =&amp;gt; conn);
    try {
      const [rows] = await connection.query(&quot;SELECT * FROM FiveMinuteAccmofMeasureMentsMeter where createts BETWEEN ? and ? and siteid = ? and deviceid = ? order by siteid, createts desc;&quot;, [agoDate, lastDate, siteid, deviceid]);
      connection.release();
      return rows
    } catch (err) {
      console.log('Query Error : ' + err);
      connection.release();
      return false;
    }
  }

  let agoDate = new Date(2022, 9, 1);
  let currentDate = new Date(agoDate.getFullYear(), agoDate.getMonth(), 1);
  let currentLastDate = new Date(agoDate.getFullYear(), agoDate.getMonth() + 1, 0);


  getSiteListQuery(currentDate, currentLastDate).then((dataList) =&amp;gt; {
    // 주의할 점. foreach를 사용하면 동기적인 처리가 되지 않습니다! 이유는 다음에서...
    // 1. for of 문 사용
    for (const value of dataList) {
      getSearchList(currentDate, currentLastDate, value.siteid, value.deviceid).then(
        data =&amp;gt; console.log(data)
      );
    }

    // 2. entries를 사용해 index로 접근 가능
    for (const [index, value] of dataList.entries()) {
      getSearchList(currentDate, currentLastDate, dataList[index].siteid, dataList[index].deviceid).then(
        data =&amp;gt; console.log(data)
      );
    }
  })

}
func();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*dataList는 A 데이터베이스에서 받은 데이터리스트고, &lt;span&gt;getSearchList는 Promise 함수로, 파라미터를 받아서 B 데이터베이스에 db query를 실행시키는 함수입니다. 테스트용으로 확인용으로만 썼습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 for of또는 array.entries를 사용할 때에 for scope 바깥에서 promise객체를 생성해야하고, foreach를 사용해서 반복하면 안됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. foreach를 사용할 때 순차처리가 되지 않는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1666339144505&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;배열에 비동기 작업을 실시할 때 알아두면 좋은 이야기들&quot; data-og-description=&quot;프론트엔드 인턴 면접에서 비동기 작업 관련 질문 대답 못한 뒤 외양간 뚝딱뚝딱 고치는 이야기.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; data-og-url=&quot;https://velog.io/@hanameee/배열에-비동기-작업을-실시할-때-알아두면-좋을법한-이야기들&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SNB4Q/hyQbxM3JzQ/u5doPRr1Bdvj1xO7vRVUGK/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563,https://scrap.kakaocdn.net/dn/buIWiC/hyQbpuHEOM/xfZKjror6IoVYRz9oL9Vdk/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563,https://scrap.kakaocdn.net/dn/bHk7rC/hyQanqwnWC/MGo7PEhAe20IK4GHr4kHV0/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563&quot;&gt;&lt;a href=&quot;https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@hanameee/%EB%B0%B0%EC%97%B4%EC%97%90-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%8B%A4%EC%8B%9C%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EB%B2%95%ED%95%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0%EB%93%A4#for-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SNB4Q/hyQbxM3JzQ/u5doPRr1Bdvj1xO7vRVUGK/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563,https://scrap.kakaocdn.net/dn/buIWiC/hyQbpuHEOM/xfZKjror6IoVYRz9oL9Vdk/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563,https://scrap.kakaocdn.net/dn/bHk7rC/hyQanqwnWC/MGo7PEhAe20IK4GHr4kHV0/img.png?width=1080&amp;amp;height=563&amp;amp;face=0_0_1080_563');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;배열에 비동기 작업을 실시할 때 알아두면 좋은 이야기들&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 인턴 면접에서 비동기 작업 관련 질문 대답 못한 뒤 외양간 뚝딱뚝딱 고치는 이야기.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 요점 정리해서 다시 작성하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>for동기처리</category>
      <category>MySQL</category>
      <category>node.js</category>
      <category>Promise</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/267</guid>
      <comments>https://narup.tistory.com/267#entry267comment</comments>
      <pubDate>Fri, 21 Oct 2022 17:03:16 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] this 정리</title>
      <link>https://narup.tistory.com/266</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. this의 정의&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의&lt;span&gt; this&lt;/span&gt;는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;현재 실행 문맥&lt;/b&gt;&lt;/span&gt;이라고 합니다&lt;span&gt;. &lt;/span&gt;여기서 현재 실행 문맥이라는 건 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;호출자&lt;/b&gt;&lt;/span&gt;를 뜻하는데&lt;span&gt;, &lt;/span&gt;호출자는 크게 세 가지로 나뉘어집니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 메소드&lt;/li&gt;
&lt;li&gt;함수&lt;/li&gt;
&lt;li&gt;생성자 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약&lt;span&gt; this&lt;/span&gt;가 나온다면 세 가지 경우 중 하나인 함수호출방식에 따라 누가 호출했는지 보고&lt;span&gt; this&lt;/span&gt;가 어떤 의미를 가지는지를 따져보면 됩니다&lt;span&gt;. &lt;/span&gt;이를 함수 호출 패턴이라고 하며&lt;span&gt;, &lt;/span&gt;자바스크립트의&lt;span&gt; this &lt;/span&gt;는 &lt;u&gt;함수 호출 패턴에 따라 &lt;span&gt;this &lt;/span&gt;가 어떤 객체의&lt;span&gt; this &lt;/span&gt;가 될 지 정해지고&lt;span&gt;, &lt;/span&gt;이것을&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; this 바인딩&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;이라고 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&amp;nbsp;2. 사용방법&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1) 객체의 메소드 호출할 때의 &lt;span&gt;this &lt;/span&gt;바인딩&lt;/h3&gt;
&lt;pre id=&quot;code_1666239759080&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var obj = {
  name: &amp;lsquo;test&amp;rsquo;
  sayName: function() {
    console.log(this.name);
  }
};
 
var kim = obj;
kim.name = &quot;kim&quot;;
kim.sayName(); // &quot;kim&quot;
 
var lee = obj;
lee.name = &quot;lee&quot;;
lee.sayName(); // &quot;lee&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 함수가 객체의 프로퍼티 값이면&lt;span&gt;, &lt;/span&gt;자신을 호출한 객체에 바인딩이 되며&lt;span&gt;, &lt;/span&gt;메소드로서 호출됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2) 함수를 호출할 때의 &lt;span&gt;this &lt;/span&gt;바인딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 &lt;span&gt;this&lt;/span&gt;는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;전역 개체(window)&lt;/b&gt;&lt;/span&gt;에 바인딩 되서 아래 코드처럼 &lt;span&gt;this&lt;/span&gt;를 사용하면 전역에 있는 &lt;span&gt;val &lt;/span&gt;변수에 할당된 &lt;span&gt;Hello&lt;/span&gt;가 출력됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1666239768679&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var val = &quot;Hello&quot;;
 
var func = function() {
  console.log(this.val);
};
func(); // &quot;Hello&quot;
console.log(this.val); // &quot;Hello&quot;
console.log(window.val); //  &quot;Hello&quot;
console.log(this === window); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;콜백함수&lt;/b&gt;&lt;/span&gt;도 마찬가지로 &lt;span&gt;this&lt;/span&gt;는 &lt;u&gt;전역객체에 바인딩&lt;/u&gt; 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1666239785895&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var value = 1;
 
var obj = {
  value: 100,
  foo: function() {
    setTimeout(function() {
      console.log(&quot;callback's this: &quot;,  this);  // window
      console.log(&quot;callback's this.value: &quot;,  this.value); // 1
    }, 100);
  }
};
 
obj.foo();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 &lt;u&gt;내부함수는 일반함수&lt;span&gt;, &lt;/span&gt;메소드&lt;span&gt;, &lt;/span&gt;콜백함수, 어디에서 선언되었던 관계없이 &lt;span&gt;this&lt;/span&gt;는 전역객체를 바인딩&lt;/u&gt;합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조를 피하는 방법은 아래와 같이 &lt;b&gt;&lt;span&gt;var that = this; &lt;/span&gt;&lt;/b&gt;로 초기화해서 사용하면 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1666239824638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var value = 1;
 
var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj
 
    console.log(&quot;foo's this: &quot;,  this);  // obj
    console.log(&quot;foo's this.value: &quot;,  this.value); // 100
    function bar() {
      console.log(&quot;bar's this: &quot;,  this); // window
      console.log(&quot;bar's this.value: &quot;, this.value); // 1
 
      console.log(&quot;bar's that: &quot;,  that); // obj
      console.log(&quot;bar's that.value: &quot;, that.value); // 100
    }
    bar();
  }
};
 
obj.foo();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3) 생성자 함수 호출할 때의 &lt;span&gt;this &lt;/span&gt;바인딩&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;601&quot;&gt;&lt;span&gt;&lt;span&gt;var Person = function(name) {&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;this.name = name;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;var kim = new Person(&quot;kim&quot;);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;console.log(kim.name); //kim&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;var lee = new Person(&quot;lee&quot;);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;console.log(lee.name); //lee&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;New&lt;/span&gt;&lt;/b&gt;를 통해 생성자 함수를 생성한 객체에 대해 바인딩이 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>JavaScript</category>
      <category>this</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/266</guid>
      <comments>https://narup.tistory.com/266#entry266comment</comments>
      <pubDate>Thu, 20 Oct 2022 13:24:04 +0900</pubDate>
    </item>
    <item>
      <title>[React] SEO 처리 하기</title>
      <link>https://narup.tistory.com/265</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 기본적으로 CSR(Client Side Rendering), SPA(Single Page Application)으로 동작합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 React에서는 HTML 파일이 딱 1개뿐이고, 그 내용조차 서버에서 javascript를 받아서 렌더링하기 전까지는 빈 껍데기인 상태이기 때문에 검색 엔진에 노출되기가 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 SEO에 노출시키기 위해 html 내용을 미리 만들어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. pre-rendering&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pre-rendering이란, 빌드할때 미리 특정 페이지를 렌더링해서 html을 만들어 두는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드할 때 html 파일을 만들어두기 때문에 검색 엔진이 크롤링하러 사이트에 접근했을 때, 내용물을 보여줄 수가 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 빌드 시에는 정적 파일만 가져오기 때문에 서버측에서 가져오는 데이터는 포함되어 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. react-snap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) pre-rendering을 사용하기 위해 react-snap 라이브러리를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666166074430&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install --dev react-snap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) package.json에 &amp;nbsp; &quot;postbuild&quot;: &quot;react-snap&quot; 스크립트를 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666166106567&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;react-scripts test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;,
    &quot;postbuild&quot;: &quot;react-snap&quot;
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) pre-rendering할 페이지를 package.json에 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666167197466&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;reactSnap&quot;: {
    &quot;include&quot;: [
      &quot;/index&quot;,&quot;/&quot;
    ]
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) npm run build로 빌드해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 먼저 빌드가 되고 자동적으로 react-snap 라이브러리가 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rxJjJ/btrO5gFz2NJ/CqK4D9Y85QKfCHfngwfCHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rxJjJ/btrO5gFz2NJ/CqK4D9Y85QKfCHfngwfCHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rxJjJ/btrO5gFz2NJ/CqK4D9Y85QKfCHfngwfCHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrxJjJ%2FbtrO5gFz2NJ%2FCqK4D9Y85QKfCHfngwfCHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;365&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) serve로 서버 실행하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;serve 라이브러리를 전역에 설치하고,&lt;/p&gt;
&lt;pre id=&quot;code_1666167421340&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g serve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/build/server.json을 생성해 아래와 같이 작성합니다&lt;/p&gt;
&lt;pre id=&quot;code_1666167454393&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;rewrites&quot;: [
    { &quot;source&quot;: &quot;/&quot;, &quot;destination&quot;: &quot;/200.html&quot; },
    { &quot;source&quot;: &quot;/index&quot;, &quot;destination&quot;: &quot;/index.html&quot; }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;135&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Groy/btrODvrI9Aa/xon5LcF7cv8kdNWLpk1i4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Groy/btrODvrI9Aa/xon5LcF7cv8kdNWLpk1i4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Groy/btrODvrI9Aa/xon5LcF7cv8kdNWLpk1i4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Groy%2FbtrODvrI9Aa%2Fxon5LcF7cv8kdNWLpk1i4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;135&quot; height=&quot;312&quot; data-origin-width=&quot;135&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 아래 명령어로 서버를 실행시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666167492513&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;serve -c server.json build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buyDPP/btrOQFM5qAX/JdKtQxSVAtk8lj9iWbLst0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buyDPP/btrOQFM5qAX/JdKtQxSVAtk8lj9iWbLst0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buyDPP/btrOQFM5qAX/JdKtQxSVAtk8lj9iWbLst0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuyDPP%2FbtrOQFM5qAX%2FJdKtQxSVAtk8lj9iWbLst0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;159&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>SEO</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/265</guid>
      <comments>https://narup.tistory.com/265#entry265comment</comments>
      <pubDate>Wed, 19 Oct 2022 17:20:21 +0900</pubDate>
    </item>
    <item>
      <title>[React] SSR과 CSR(SPA) 차이</title>
      <link>https://narup.tistory.com/264</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 브라우저가 동작되어 우리에게 정보를 표현할 때, HTML의 정보를 읽어 이를 내부적으로 렌더링 한 후 js나 css를 반영해 최종적으로 정보를 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;렌더링&lt;/b&gt;&lt;/span&gt;을 하는 방식이 SSR과 CSR이 있는데, 이 두 가지 렌더링 방법에 대한 차이를 이해해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. SSR(Server Side Rendering)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://narup.tistory.com/235&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1666161016217&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Next.js] SSG, SSR 개념 정리&quot; data-og-description=&quot;1. 개요 전 글에서는 Hydrate에 대해서 알아봤는데, 이번에는 SSG(Server-Side-Generation, Static-Site-Generation)와 SSR(Sever-Side-Rendering)에 대해 알아보려고 합니다. React: CSR(Client-Side-Rendering)..&quot; data-og-host=&quot;narup.tistory.com&quot; data-og-source-url=&quot;https://narup.tistory.com/235&quot; data-og-url=&quot;https://narup.tistory.com/235&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ts0FN/hyQbyD4F8F/cEb6YaRoKPygrZhidfN8Ok/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860,https://scrap.kakaocdn.net/dn/ccsuxH/hyQbB1ROep/8YORXDV6BPCFJjvopKIMC1/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860,https://scrap.kakaocdn.net/dn/bKVCVy/hyP9yeY6Te/v7PYcpE1hyYIcYVGOKGL2k/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/235&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://narup.tistory.com/235&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ts0FN/hyQbyD4F8F/cEb6YaRoKPygrZhidfN8Ok/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860,https://scrap.kakaocdn.net/dn/ccsuxH/hyQbB1ROep/8YORXDV6BPCFJjvopKIMC1/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860,https://scrap.kakaocdn.net/dn/bKVCVy/hyP9yeY6Te/v7PYcpE1hyYIcYVGOKGL2k/img.png?width=754&amp;amp;height=860&amp;amp;face=0_0_754_860');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Next.js] SSG, SSR 개념 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 개요 전 글에서는 Hydrate에 대해서 알아봤는데, 이번에는 SSG(Server-Side-Generation, Static-Site-Generation)와 SSR(Sever-Side-Rendering)에 대해 알아보려고 합니다. React: CSR(Client-Side-Rendering)..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;narup.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR은 Next.js 프레임워크에서 동작하는 렌더링 방법인데 자세한 설명은 위에 나와있고, 간략하게 설명을 하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Eds4/btrO3VhXYQd/kPH6ivW4TQnS4SRukUIfX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Eds4/btrO3VhXYQd/kPH6ivW4TQnS4SRukUIfX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Eds4/btrO3VhXYQd/kPH6ivW4TQnS4SRukUIfX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Eds4%2FbtrO3VhXYQd%2FkPH6ivW4TQnS4SRukUIfX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;327&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 서버에게 웹 페이지를 요청할 때마다 새로고침이 일어나며, 서버에서 새로운 HTML을 그때그때마다 받아와 서버에서&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;그때그때마다 렌더링 하는 것&lt;/b&gt;&lt;/span&gt;을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. CSR(Client Side Rendering), SPA(Single Page Application)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js가 아닌 React.js는 기본적으로&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; CSR(Client Side Rendering), SPA(Single Page Application)&lt;/b&gt;&lt;/span&gt;방식으로 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 모바일 친화적으로 사용될 수 있는 서비스가 필요해서인데, 그때그때마다 렌더링을 할 경우 모바일에서 성능이 부담이 갔기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwMNXQ/btrOQmT82Rc/25GwzzTKYnpb3z4S3iUdt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwMNXQ/btrOQmT82Rc/25GwzzTKYnpb3z4S3iUdt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwMNXQ/btrOQmT82Rc/25GwzzTKYnpb3z4S3iUdt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwMNXQ%2FbtrOQmT82Rc%2F25GwzzTKYnpb3z4S3iUdt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;416&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 나온 개념이 SPA(Single Page Application)으로, &lt;u&gt;최초 한번 전체 페이지를 로딩한 이후 부터에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;브라우저 내부에서 데이터만 변경해서 사용&lt;/b&gt;&lt;/span&gt;하는 방식&lt;/u&gt;입니다. 이러한 SPA 방식은 트래픽을 감소시키고 사용자에게 최적화된 환경을 제공할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서버는 단순하게 Json 파일만 보내주고, 클라이언트 측에선 초기에 전송받은 HTML과 javascript를 통해 렌더링을 수행합니다. 이러한 과정을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CSR(클라이언트 사이드 렌더링)&lt;/b&gt;&lt;/span&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. SPA의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 사이드 렌더링을 수행하는 SPA는 서버에서 View를 렌더링하지 않고, HTML, JS, 각종 리소스를 서버에서 다운 받은 후 브라우저에서 렌더링을 수행하기 때문에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;초기 View의 로딩 속도가 오래 걸리는 단점&lt;/b&gt;&lt;/span&gt;이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 사용자의 액션에 따라 필요한 부분만 다시 읽어드리기 때문에 인터랙션이 빠르다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. SEO 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO란, Search Engine Optimization의 약자로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;검색 엔진 최적화&lt;/b&gt;&lt;/span&gt;라는 의미인데 네이버나 구글과 같은 검색 엔진이 SPA로 만든 웹 페이지가 노출 되기 위한 과정을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR같은 경우 서버측에서 렌더링한 페이지(완성된 페이지)를 클라이언트로 전달하기 때문에 문제가 없는데, SPA 같은 경우는 완성된 페이지가 아닌 html+js+css와 같은 리소스를 받아서 브라우저 측에서 조합하기 때문에 검색 엔진에 노출되기가 어렵다는 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*실제로 조회하면 html 뼈대만 있는 빈 페이지만 노출되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://goodgid.github.io/Server-Side-Rendering-and-Client-Side-Rendering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://goodgid.github.io/Server-Side-Rendering-and-Client-Side-Rendering/&lt;/a&gt;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>CSR</category>
      <category>react</category>
      <category>react.js</category>
      <category>SPA</category>
      <category>SSR</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/264</guid>
      <comments>https://narup.tistory.com/264#entry264comment</comments>
      <pubDate>Wed, 19 Oct 2022 16:21:58 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 브라우저 동작 원리, 렌더링 트리</title>
      <link>https://narup.tistory.com/263</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 브라우저 기본 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UYPLk/btrO1wDcV5G/1HCcMTxfAgYnv789oUCB31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UYPLk/btrO1wDcV5G/1HCcMTxfAgYnv789oUCB31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UYPLk/btrO1wDcV5G/1HCcMTxfAgYnv789oUCB31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUYPLk%2FbtrO1wDcV5G%2F1HCcMTxfAgYnv789oUCB31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;530&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자 인터페이스&lt;/b&gt; : 주소표시줄, 이전/다음 버튼, 웹페이지가 보여지는 창 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브라우저 엔진&lt;/b&gt; : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더링 엔진&lt;/b&gt; : 요청한 콘텐츠를 표시하기 위해 요청된 HTML과 CSS를 파싱해 화면에 표시합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통신&lt;/b&gt; : HTTP 요청과 같은 네트워크 호출에 사용됩니다. 독립적인 인터페이스로 각 플랫폼 하부에서 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자바스크립트 해석기&lt;/b&gt; : 자바스크립트 코드를 해석하고 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자료 저장소&lt;/b&gt; : 자료를 저장하는 계층으로 로컬 스토리지, 쿠키와 같이 모든 종류의 자원을 하드디스크에 저장합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬은 대부분의 브라우저와 달리 각 탭마다 별도의 렌더링 인스턴스를 유지해서 독립된 프로세스로 처리합니다&lt;span&gt;(&lt;/span&gt;이래서 메모리를 많이 먹는 걸수도&lt;span&gt;?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 렌더링 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청받은&lt;span&gt; HTML, XML &lt;/span&gt;문서&lt;span&gt;, &lt;/span&gt;이미지와 같은 내용을 브라우저 화면에 표시합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이어폭스는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;게코(Gecko) 엔진&lt;/b&gt;&lt;/span&gt;을 사용하고&lt;span&gt;, &lt;/span&gt;사파리와 크롬은&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; 웹킷(Webkit) 엔진&lt;/b&gt;&lt;/span&gt;을 사용합니다&lt;span&gt;.(&lt;/span&gt;많이 들어본&lt;span&gt;?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작과정은&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HpeC5/btrO36iYbD7/MAH3EK3bwqTu26r7G4klZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HpeC5/btrO36iYbD7/MAH3EK3bwqTu26r7G4klZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HpeC5/btrO36iYbD7/MAH3EK3bwqTu26r7G4klZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHpeC5%2FbtrO36iYbD7%2FMAH3EK3bwqTu26r7G4klZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;103&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이&lt;span&gt; HTMl &lt;/span&gt;문서를 파싱하고&lt;span&gt;, [&lt;/span&gt;콘텐츠 트리&lt;span&gt;] &lt;/span&gt;내부에서 태그를&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; DOM 노드&lt;/b&gt;&lt;/span&gt;로 변환합니다&lt;span&gt;. &lt;/span&gt;그 다음 외부&lt;span&gt; CSS &lt;/span&gt;파일과 함께 포함된 스타일 요소를 파싱합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 파싱한 정보를 바탕으로 스타일 정보와&lt;span&gt; HTML &lt;/span&gt;표시 규칙이 포함된 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;렌더 트리&lt;/b&gt;&lt;/span&gt;를 생성합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리는 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고 있는데&lt;span&gt;, &lt;/span&gt;정해진 순서대로 화면에 표시됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리 생성이 끝나면 배치가 시작되는데&lt;span&gt;, &lt;/span&gt;이것은 각 노드가 화면의 정확한 위치에 표시되는 것을 의미합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;*&lt;/span&gt;&lt;b&gt;파싱&lt;span&gt;, &lt;/span&gt;파서&lt;/b&gt;&lt;span&gt;: &lt;/span&gt;분석하는 것이라는 의미로&lt;span&gt;, &lt;/span&gt;브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미합니다&lt;span&gt;. &lt;/span&gt;파싱 결과는 문서 구조를 나타내는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DOM 노드 트리&lt;/b&gt;&lt;/span&gt;로 구현됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 웹킷의 동작 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BjMtJ/btrO2WuEv28/jzrUHavxxiBjp7EyV39imk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BjMtJ/btrO2WuEv28/jzrUHavxxiBjp7EyV39imk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BjMtJ/btrO2WuEv28/jzrUHavxxiBjp7EyV39imk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBjMtJ%2FbtrO2WuEv28%2FjzrUHavxxiBjp7EyV39imk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;435&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저가 문서&lt;span&gt;(HTML)&lt;/span&gt;을 해석하면서 하는 수행하는 과정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;불러오기(loading)&lt;/b&gt; : HTTP 모듈 또는 파일시스템으로 전달받은 리소스 스트림(resource stream)을 읽는 과정으로 로더(Loader)가 이 역할을 맡고 있습니다. 로더는 단순히 읽기만 하는 것이 아니라 데이터를 읽었는지 확인하고, 팝업창을 열지 말지, 파일을 다운로드 받을지를 결정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파싱:&lt;/b&gt; 파싱은 DOM(Document Object Model)트리를 만드는 과정으로 HTML, XML 파서 2개의 파서를 가지고 있습니다. XML 파서는 XML 형식을 따르는 SVG, MathML등을 처리하는데 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더링 트리&lt;/b&gt; : DOM 트리와 다르게 화면에 표시하기 위한 위치와 크기 정보, 그리는 순서 등을 저장하기 위한 별도의 트리 구조를 말합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 브라우저가 &lt;span&gt;HTML &lt;/span&gt;문서를 만나게 되면 &lt;span&gt;HTML&lt;/span&gt;을 파싱하고 외부 자원인 &lt;span&gt;css&lt;/span&gt;와 &lt;span&gt;js &lt;/span&gt;파일을 로드합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Javascript&lt;/span&gt;는 전달된 시점에서 실행되고&lt;span&gt;, DOM &lt;/span&gt;트리의 구축을 완료한 이후에 이미지 파일 및 플래시 등의 외부 리소스를 로드하면서 모든 작업이 완료됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 스크립트를 만나게 될 경우&lt;span&gt;, &amp;lt;script&amp;gt; &lt;/span&gt;태그를 만나면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;스크립트가 해석 및 실행되는 동안 문서의 파싱은 중단&lt;/b&gt;&lt;/span&gt;되게 됩니다&lt;span&gt;. &lt;/span&gt;또한 &lt;u&gt;스크립트가 외부&lt;/u&gt;에 있는 경우 네트워크로부터 자원을 가져와야 하는데 이 또한 실시간으로 처리되고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;자원을 받을때까지 파싱은 중단&lt;/b&gt;&lt;/span&gt;됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;*css&lt;/span&gt;는 이론적으로 &lt;span&gt;DOM &lt;/span&gt;트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단하지 않습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 자바스크립트 로드&lt;span&gt; async, defer &lt;/span&gt;속성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴보았던 브라우저가 &lt;span&gt;HTML&lt;/span&gt;을 해석하는 과정 대로 진행하게 되면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHAeq/btrO2odRCv6/Q9jh8b4bm49RZlOHlSZM21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHAeq/btrO2odRCv6/Q9jh8b4bm49RZlOHlSZM21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHAeq/btrO2odRCv6/Q9jh8b4bm49RZlOHlSZM21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHAeq%2FbtrO2odRCv6%2FQ9jh8b4bm49RZlOHlSZM21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;428&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1&lt;/span&gt;번 &lt;span&gt;-&amp;gt; 2&lt;/span&gt;번 &lt;span&gt;-&amp;gt; 3&lt;/span&gt;번 순서로 코드가 실행됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때&lt;span&gt;, script&lt;/span&gt;가 해석 및 실행되는 동안 문서의 파싱이 중단되기 때문에&lt;span&gt;, 2&lt;/span&gt;번에서 &lt;span&gt;2&lt;/span&gt;초&lt;span&gt;~3&lt;/span&gt;초가 걸릴 경우 홈페이지 전체적인 로딩이 늦어진다고 느껴지게 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에 &lt;span&gt;async, defer &lt;/span&gt;속성을 이용합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Async &lt;/b&gt;: html &lt;/span&gt;렌더링을 멈추지 않고 동시에 &lt;span&gt;js &lt;/span&gt;파일을 다운로드하고 다운로드가 끝난후에 자바스크립트를 실행합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Defer&lt;/b&gt; : html &lt;/span&gt;렌더링을 멈추지 않고 동시에 &lt;span&gt;js &lt;/span&gt;파일을 다운로드하고 &lt;span&gt;html &lt;/span&gt;렌더링이 끝난 후에 자바스크립트를 실행합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bftQGH/btrOQuqWnOH/Xon44TM7bOPYbjpaURyeIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bftQGH/btrOQuqWnOH/Xon44TM7bOPYbjpaURyeIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bftQGH/btrOQuqWnOH/Xon44TM7bOPYbjpaURyeIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbftQGH%2FbtrOQuqWnOH%2FXon44TM7bOPYbjpaURyeIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;625&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;625&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://webclub.tistory.com/m/630&quot;&gt;https://webclub.tistory.com/m/630&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://realmojo.tistory.com/m/96&quot;&gt;https://realmojo.tistory.com/m/96&lt;/a&gt; &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>async</category>
      <category>defer</category>
      <category>JavaScript</category>
      <category>렌더링트리</category>
      <category>브라우저동작원리</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/263</guid>
      <comments>https://narup.tistory.com/263#entry263comment</comments>
      <pubDate>Wed, 19 Oct 2022 14:31:57 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 이벤트 버블링</title>
      <link>https://narup.tistory.com/262</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이벤트 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링에 알기 전에 앞서 이벤트 등록에 대해 알아봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666074530385&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var button = document.querySelector('button');
button.addEventListener('click', addItem);

function addItem(event) {
	console.log(event);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발을 하셨던 분이라면 익숙한 코드지요? 버튼을 클릭했을 경우 addItem 함수를 실행시키는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 클릭하면 addItem함수가 실행되고, event 인자가 넘어오며, console.log를 통해 event에 관련된 정보를 알 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 원리는 이러한데, 브라우저는 어떻게 이벤트를 감지할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이벤트 버블링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때, 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가능 특성을 의미합니다.(하위 -&amp;gt; 상위)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIg9Y/btrOYfOw2C4/0qDKXez5LJgyZdHGs6ddK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIg9Y/btrOYfOw2C4/0qDKXez5LJgyZdHGs6ddK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIg9Y/btrOYfOw2C4/0qDKXez5LJgyZdHGs6ddK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIg9Y%2FbtrOYfOw2C4%2F0qDKXez5LJgyZdHGs6ddK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;914&quot; height=&quot;814&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 코드가 있다고 가정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666081020129&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
	&amp;lt;div class=&quot;one&quot;&amp;gt;
		&amp;lt;div class=&quot;two&quot;&amp;gt;
			&amp;lt;div class=&quot;three&quot;&amp;gt;
			&amp;lt;/div&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1666081029496&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', logEvent);
});

function logEvent(event) {
	console.log(event.currentTarget.className);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 자바스크립트는 3개의 div에 모두 클릭이벤트를 등록하고, 클릭했을때 해당 클래스 이름을 출력하는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 제일 하위에 있는 div를 클릭하게 되면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;212&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrMpV/btrOXkXCKaf/5geYMRmNm2e5LTpC61lVP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrMpV/btrOXkXCKaf/5geYMRmNm2e5LTpC61lVP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrMpV/btrOXkXCKaf/5geYMRmNm2e5LTpC61lVP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrMpV%2FbtrOXkXCKaf%2F5geYMRmNm2e5LTpC61lVP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;212&quot; height=&quot;159&quot; data-origin-width=&quot;212&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 하위부터 상위까지의 div 클래스 이름이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것과 같이 동작하는 것을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이벤트 버블링&lt;/b&gt;&lt;/span&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링이 발생하는 이유는 브라우저가 이벤트를 감지하는 방식 때문인데, 브라우저는 특정화면요소에서 이벤트가 발생했을 때, 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이벤트 캡처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 캡처는 이벤트 버블링과 반대로 상위에서 하위로 흘러가는 진행 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn06Ol/btrOYwv0uiD/eb1HLmRkMHADdgKbYmpsoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn06Ol/btrOYwv0uiD/eb1HLmRkMHADdgKbYmpsoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn06Ol/btrOYwv0uiD/eb1HLmRkMHADdgKbYmpsoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn06Ol%2FbtrOYwv0uiD%2Feb1HLmRkMHADdgKbYmpsoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;918&quot; height=&quot;840&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1666081509074&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', logEvent, {
		capture: true // default 값은 false입니다.
	});
});

function logEvent(event) {
	console.log(event.currentTarget.className);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링과 반대로 이벤트 캡처를 사용하려고 한다면 addEventListener의 옵션에 &lt;u&gt;capture:true&lt;/u&gt;를 적용해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위의 three가 있는 클래스를 클릭할 경우&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;182&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biRTEQ/btrOZa61RD4/KVbL5Uja0vprlTKkAQwGnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biRTEQ/btrOZa61RD4/KVbL5Uja0vprlTKkAQwGnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biRTEQ/btrOZa61RD4/KVbL5Uja0vprlTKkAQwGnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiRTEQ%2FbtrOZa61RD4%2FKVbL5Uja0vprlTKkAQwGnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;182&quot; height=&quot;179&quot; data-origin-width=&quot;182&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위에서 하위로 이벤트가 전파되어 출력이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. event.stopPropagation()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 복잡한 이벤트의 전달방식을 신경쓰고 싶지 않다면 이벤트 속성에서 이벤트가 전파되는 것을 막는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;stopPropagation()&lt;/b&gt;&lt;/span&gt; 함수를 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666081661377&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function logEvent(event) {
	event.stopPropagation();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/&lt;/a&gt;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>JavaScript</category>
      <category>이벤트버블링</category>
      <category>이벤트캡처</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/262</guid>
      <comments>https://narup.tistory.com/262#entry262comment</comments>
      <pubDate>Tue, 18 Oct 2022 17:28:41 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 이벤트 루프와 태스크 큐</title>
      <link>https://narup.tistory.com/261</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. Javascript &lt;/span&gt;이벤트 루프&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 기본적으로 싱글 스레드로 한번에 하나의 작업만 할 수 있는 특징이 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;One thread == one call stack == one thing at a time&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Call stack(호출 스택)&lt;/b&gt;&lt;/span&gt;은 우리가 어떤 순서로 작업을 수행하는지 기록하는 작업 스케쥴링과 관련된 도구입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 함수를 실행하면 그 함수를 호출 스택의 맨 위에 놓는데 이것을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;push 과정&lt;/b&gt;&lt;/span&gt;이라고 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 함수가 어떤 값을 리턴하거나 실행을 종료하면&lt;span&gt;, &lt;/span&gt;호출 스택 맨 위에서 그 함수를 꺼내는데 이 과정을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;pop&lt;/b&gt;&lt;/span&gt;이라고 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JP2Ka/btrOYfgDtKX/FNiJfkgx2vdkQmwIronqF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JP2Ka/btrOYfgDtKX/FNiJfkgx2vdkQmwIronqF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JP2Ka/btrOYfgDtKX/FNiJfkgx2vdkQmwIronqF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJP2Ka%2FbtrOYfgDtKX%2FFNiJfkgx2vdkQmwIronqF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;905&quot; height=&quot;594&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 코드가 있으면 스택에 순서대로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;main() -&amp;gt; printSquare() -&amp;gt; square() -&amp;gt; multiply()로 push가 되고,&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;pop&lt;/span&gt;은 함수의 실행이 종료되었을 때 그 함수를 꺼내는 것이니까&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;multiply() -&amp;gt; square() -&amp;gt; console.log() -&amp;gt; printSquare() -&amp;gt; main()과 같은 과정으로 pop이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;* console.log&lt;/span&gt;도 함수입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 콜 스택에 재귀적인 호출이 일어나 함수가 계속 쌓이게 되면 &lt;u&gt;&lt;span&gt;Maximum call stack size exceeded&lt;/span&gt;&lt;/u&gt;라는 에러메세지를 발생시킵니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. 비동기 콜백&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 간단한 프로그램이라면 상관 없지만&lt;span&gt;, &lt;/span&gt;복잡한 프로그램을 구동한다고 하고 시간이 매우 오래걸리는 작업이 스택에 쌓이고 실행될 경우&lt;span&gt;, &lt;/span&gt;그 다음 작업은 무한정 대기하게 되는데&lt;span&gt;, &lt;/span&gt;이 경우를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;블로킹(blocking)&lt;/b&gt;&lt;/span&gt;이라고 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 문제가 되는 이유는&lt;span&gt;, &lt;/span&gt;블록킹이 발생할 경우 브라우저가 다른 일을 할 수가 없기 때문에 조작이 불가능해지기 때문입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위한 해결 방안이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;비동기 콜백(Asynchronous Callbacks)&lt;/b&gt;&lt;/span&gt;입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트가 싱글 스레드임에도 불구하고 웹사이트에서 여러 작업을 할 수 있도록 지원해주는 것이 브라우저가&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;Web APIs&lt;/b&gt; &lt;/span&gt;같은 것들을 제공해 비동기 작업을 가능하게 해주기 때문입니다&lt;span&gt;!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCCQ5M/btrOUMfelvm/XpPvKIgMnVzSgmnIephtq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCCQ5M/btrOUMfelvm/XpPvKIgMnVzSgmnIephtq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCCQ5M/btrOUMfelvm/XpPvKIgMnVzSgmnIephtq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCCQ5M%2FbtrOUMfelvm%2FXpPvKIgMnVzSgmnIephtq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;679&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 내부에는 &lt;span&gt;callstack&lt;/span&gt;이 있는데&lt;span&gt;, &lt;u&gt;ajax&lt;/u&gt;&lt;/span&gt;나 &lt;span&gt;&lt;u&gt;setTimeout&lt;/u&gt;, &lt;u&gt;dom event &lt;/u&gt;&lt;/span&gt;&lt;u&gt;함수와 같은 비동기 함수&lt;/u&gt;를 &lt;span&gt;pop &lt;/span&gt;과정을 거쳐 실행하게 되면&lt;span&gt;, &lt;/span&gt;자바스크립트 엔진은 &lt;span&gt;call stack&lt;/span&gt;에서 &lt;span&gt;web APIs(&lt;/span&gt;브라우저&lt;span&gt;)&lt;/span&gt;로 함수를 보내고 정해진 시간 혹은 이벤트가 발생한 순간에 순차적으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Callback Queue&lt;/b&gt;&lt;/span&gt;라는 곳에 적재합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Callback queue&lt;/span&gt;에 적재된 함수들은 &lt;span&gt;js&lt;/span&gt;의 &lt;span&gt;call stack&lt;/span&gt;에 쌓여있던 것들이 모두 제거되었을 때 차례대로 스택에 쌓여서 실행되게 됩니다&lt;span&gt;. &lt;/span&gt;이것을 이벤트 루프라고 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;이후 &lt;b&gt;콜백 큐&lt;/b&gt;는 &lt;b&gt;태스크 큐&lt;/b&gt;라고 정정합니다&lt;span&gt;. HTML &lt;/span&gt;표준 스택에서 &lt;span&gt;task queue&lt;/span&gt;로 표기하기 때문입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bae6Dz/btrOEhG3Pab/26rF7n2yVzb978R0rKZti0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bae6Dz/btrOEhG3Pab/26rF7n2yVzb978R0rKZti0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bae6Dz/btrOEhG3Pab/26rF7n2yVzb978R0rKZti0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbae6Dz%2FbtrOEhG3Pab%2F26rF7n2yVzb978R0rKZti0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;102&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 태스크 큐와 마이크로태스크 큐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 동작과정은 위에서 설명한 것과 같은데&lt;span&gt;, &lt;/span&gt;세부적으로는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;태스크 큐&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;마이크로 태스크 큐&lt;/b&gt;&lt;/span&gt;&lt;span&gt;, 2&lt;/span&gt;개의 큐로 나누게 되고 이는&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; Promise 동작 방식&lt;/b&gt;&lt;/span&gt;과 연관이 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 자세한 내용으로 태스크큐와 마이크로태스크 큐 모두 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;제일 오래된 태스크&lt;/b&gt;&lt;/span&gt;에 우선순위를 주어 태스크를 꺼내는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;우선순위 큐(priority Queue)&lt;/b&gt;&lt;/span&gt;로 동작합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태스크큐에 들어가는 콜백함수들&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로태스크큐에 들어가는 콜백함수들&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Process.nextTick, promise, object.observe, MutationObserver&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 이와 같은 &lt;span&gt;2&lt;/span&gt;개의 큐에서 콜백함수를 꺼내야하는데&lt;span&gt;, &lt;/span&gt;순서는 마이크로태스크 큐가 태스크 큐보다 우선순위를 가지고 있습니다&lt;span&gt;!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1666074177211&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log('콜 스택!');
setTimeout(() =&amp;gt; console.log('태스크 큐!'), 0);
Promise.resolve().then(() =&amp;gt; console.log('마이크로태스크 큐!'));
 
-&amp;gt;
콜 스택!
마이크로태스크 큐!
태스크 큐!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 비동기 로직을 다룰 때 콜백 함수를 사용하게 될 경우&lt;span&gt;, &lt;/span&gt;이러한 우선순위를 예측하여 코드를 작성해야 합니다&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>JavaScript</category>
      <category>마이크로태스크큐</category>
      <category>이벤트루프</category>
      <category>태스크큐</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/261</guid>
      <comments>https://narup.tistory.com/261#entry261comment</comments>
      <pubDate>Tue, 18 Oct 2022 15:23:37 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 호이스팅이란?</title>
      <link>https://narup.tistory.com/260</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1.&amp;nbsp;&lt;/span&gt;호이스팅이란&lt;span&gt;?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;최상단에 선언하는 것&lt;/b&gt;&lt;/span&gt;을 말합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. 자바스크립트 함수 동작 원리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1) 자바스크립트 Parser가 함수 실행 전 해당 함수를 한번 훑는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2) 함수안에 존재하는 변수/함수선언에 대한 정보를 기억하고 있다가 실행한다. (함수 블록 {} 안에서만.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3) 함수를 실행할 때, 호출한 위치보다 해당 함수가 하단부에 있을 경우 자바스크립트 Parser가 내부적으로 함수 위치를 끌어올려서 처리한다. -&amp;gt; 호이스팅&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;* 실제 메모리에서는 변화가 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Why?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;자바스크립트에서 함수를 선언하는 방식이 함수선언식과 함수표현식이 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1665982572842&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수선언식
function test1() {
 return 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1665982582252&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수표현식
var test2 = function() {
 return 2;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 함수도 객체로 인식되기 때문에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;변수&lt;/b&gt;&lt;/span&gt;에 할당이 가능해서 함수표현식처럼 변수값에 함수 표현을 담아서 사용할 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수표현식의 장점 때문에 호이스팅이 사용되는데요&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수표현식을 사용하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;클로저&lt;/b&gt;&lt;/span&gt;로 사용할 수 있고&lt;span&gt;, &lt;/span&gt;다른 함수의 인자로 넘기는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;콜백&lt;/b&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;함수도 객체이기 때문에&lt;span&gt;)&lt;/span&gt;으로 사용할 수 있기 때문입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수표현식의 호이스팅 예제&lt;/h2&gt;
&lt;pre id=&quot;code_1665982626997&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 정상 */
 function printName(firstname) { // 함수선언문
     var inner = function() { // 함수표현식
         return &quot;inner value&quot;;
     }
       
     var result = inner(); // 함수 &quot;호출&quot;
     console.log(&quot;name is &quot; + result);
 }
 
 printName(); // &amp;gt; &quot;name is inner value&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1665982635864&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 오류 */
 function printName(firstname) { // 함수선언문
     console.log(inner); // &amp;gt; &quot;undefined&quot;: 선언은 되어 있지만 값이 할당되어있지 않은 경우
     var result = inner(); // ERROR!!
     console.log(&quot;name is &quot; + result);
 
     var inner = function() { // 함수표현식
         return &quot;inner value&quot;;
     }
 }
 printName(); // &amp;gt; TypeError: inner is not a function&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 표현식에 대한 할당이 호출하는 위치보다 밑에 있을 경우 에러를 발생합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 함수표현식을 사용할 때에는 코드의 최상단에 작성하도록 하거나&lt;span&gt;, &lt;/span&gt;혹은 &lt;span&gt;let, const&lt;/span&gt;를 사용하면 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html&quot;&gt;https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <category>JavaScript</category>
      <category>호이스팅</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/260</guid>
      <comments>https://narup.tistory.com/260#entry260comment</comments>
      <pubDate>Mon, 17 Oct 2022 14:00:57 +0900</pubDate>
    </item>
    <item>
      <title>[Jenkins] Linux EC2에서 Jenkins 설치하기(Java 11버전 설치)</title>
      <link>https://narup.tistory.com/259</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 ec2 인스턴스에 jenkins를 설치하려고 하니 java version 이슈가 있어서 설치가 곤란했었던 터라, EC2 인스턴스의 Linux 환경에서 Jenkins를 설치하는 과정을 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Jenkins 구동 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;jenkins.service&amp;nbsp;-&amp;nbsp;Jenkins&amp;nbsp;Continuous&amp;nbsp;Integration&amp;nbsp;Server&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Loaded: loaded (/usr/lib/systemd/system/jenkins.service; disabled; vendor preset: disabled)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Active: failed (Result: start-limit) since 목 2022-06-30 08:32:00 KST; 7s ago&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Process: 7079 ExecStart=/usr/bin/jenkins (code=exited, status=1/FAILURE)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main PID: 7079 (code=exited, status=1/FAILURE)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: Failed to start Jenkins Continuous Integration Server.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: Unit jenkins.service entered failed state.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: jenkins.service failed.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: jenkins.service holdoff time over, scheduling restart.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: Stopped Jenkins Continuous Integration Server.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: start request repeated too quickly for jenkins.service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: Failed to start Jenkins Continuous Integration Server.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: Unit jenkins.service entered failed state.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost.localdomain systemd[1]: jenkins.service failed.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins를 설치하고 systemctl start jenkins를 해서 구동하려는데, 구동이 실패했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인에 대한 로그가 정확하게 나와있지 않아서, 사실 추측할 수밖에 없는 상황이었는데 아마 이미 사용하고 있는 포트가 있어서 포트가 겹치거나, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;설치된 자바 버전이 맞지 않아서 구동이 실패&lt;/b&gt;&lt;/span&gt;하는 원인일 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 추후에 확인해보니 설치된 자바 버전이 맞지 않았습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 설치할 수 있는 자바 버전 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1663906436669&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum list | grep jdk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcGKF6/btrMOWRu4JY/X4FmQAThEIdUw9qJ9PKAN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcGKF6/btrMOWRu4JY/X4FmQAThEIdUw9qJ9PKAN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcGKF6/btrMOWRu4JY/X4FmQAThEIdUw9qJ9PKAN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcGKF6%2FbtrMOWRu4JY%2FX4FmQAThEIdUw9qJ9PKAN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;685&quot; height=&quot;411&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어로 확인해보면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;java 1.7~1.8 버전&lt;/b&gt;&lt;/span&gt;만 yum 패키지에서 설치할 수 있는 것으로 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 젠킨스 공식 홈페이지에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;java 11 버전 이상&lt;/b&gt;&lt;/span&gt;을 지원하는 것으로 확인됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuJxWN/btrMSMmqBkt/rTBuYNJMX0ubtgXGSxgE71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuJxWN/btrMSMmqBkt/rTBuYNJMX0ubtgXGSxgE71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuJxWN/btrMSMmqBkt/rTBuYNJMX0ubtgXGSxgE71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuJxWN%2FbtrMSMmqBkt%2FrTBuYNJMX0ubtgXGSxgE71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;826&quot; height=&quot;586&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 설치되어 있는 Java 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 다른 버전의 java가 설치가 되어 있다면,&lt;/p&gt;
&lt;pre id=&quot;code_1663906603972&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum list installed | grep &quot;java&quot;
# java-1.8.0-openjdk-headless.x86_64    1:1.8.0.222.b10-0.47.amzn1   @amzn-updates
# java-11-amazon-corretto-devel.x86_64  1:11.0.7.10-1                installed
yum remove java-1.8.0-openjdk-headless.x86_64&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 통해 자바를 제거해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. Java 11 버전 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1663906660452&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# aws coreetto 다운로드
sudo curl -L https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm -o jdk11.rpm

# jdk11 설치
sudo yum localinstall jdk11.rpm

# jdk version 선택
sudo /usr/sbin/alternatives --config java

# java 버전 확인
java --version

# 다운받은 설치키트 제거
rm -rf jdk11.rpm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 통해 java 11버전을 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-4. Jenkins 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1663906768028&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# yum에 젠킨스 repository 추가
sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo 

# 로컬 gpg 키링에 jenkins gpg key 추가
sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key 

# 젠킨스 설치
sudo yum install jenkins 

# 젠킨스 시작
sudo systemctl service jenkins start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마, 위 과정이면 젠킨스가 정상적으로 시작이 될 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Jenkins 포트 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;젠킨스가 설치되어 있는 환경에서&lt;/p&gt;
&lt;pre id=&quot;code_1663906829835&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /etc/sysconfig/jenkins&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 젠킨스 설정을 수정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWBUKn/btrMQDKANpb/9jcHu3sAtq3dnfzXiSJ3ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWBUKn/btrMQDKANpb/9jcHu3sAtq3dnfzXiSJ3ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWBUKn/btrMQDKANpb/9jcHu3sAtq3dnfzXiSJ3ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWBUKn%2FbtrMQDKANpb%2F9jcHu3sAtq3dnfzXiSJ3ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;298&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JENKINS_PORT=&quot; &quot;를 찾아 사용하고 싶은 포트로 변경해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 다른 방법으로 포트 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방법으로 포트 변경이 되지 않았다면 아래 방법을 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1663907443455&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /usr/lib/systemd/system/jenkins.service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdHNit/btrMPUZ5ugn/RG29ybSALfgR3EzVUAgi6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdHNit/btrMPUZ5ugn/RG29ybSALfgR3EzVUAgi6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdHNit/btrMPUZ5ugn/RG29ybSALfgR3EzVUAgi6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdHNit%2FbtrMPUZ5ugn%2FRG29ybSALfgR3EzVUAgi6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;195&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Jenkins</category>
      <category>ec2에서jenkins설치</category>
      <category>jenkins</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/259</guid>
      <comments>https://narup.tistory.com/259#entry259comment</comments>
      <pubDate>Fri, 23 Sep 2022 13:22:18 +0900</pubDate>
    </item>
    <item>
      <title>[React] 콜백에서의 useState, useEffect 사용 이슈</title>
      <link>https://narup.tistory.com/258</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 아래로 스크롤 할 때, 섹션별로 스크롤이 이동하는 기능을 구현하는 과정에서 &lt;b&gt;scroll event&lt;/b&gt;를 사용하게 되었는데, 이때 event가 발생할 때 콜백으로 scroll section index 값을 변경하려고 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl4hkZ/btrMIWLxrop/UjOiWciUXNCk4bxsf4Uzik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl4hkZ/btrMIWLxrop/UjOiWciUXNCk4bxsf4Uzik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl4hkZ/btrMIWLxrop/UjOiWciUXNCk4bxsf4Uzik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl4hkZ%2FbtrMIWLxrop%2FUjOiWciUXNCk4bxsf4Uzik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;659&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 스크롤 인덱스가 무조건 변경되는 것이 아니라, 인덱스가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;특정 범위를 넘지 않도록 필터링을 하는 조건&lt;/b&gt;&lt;/span&gt;을 추가하였는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 dependency로 등록한 scrollIndex의 console은 잘 찍히는 반면, 필터링하는 부분에서의 scrollIndex 값은 1로 고정이 되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용된 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1663823733600&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const outerDivRef = useRef();
  const windowSize = useRef(getWindowDimensions());
  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
  const [scrollIndex, setScrollIndex] = useState(1);
  const scrollIndexRef = useRef(1);

  useEffect(() =&amp;gt; {
    console.log(&quot;변경 후 index : &quot; + scrollIndex)
  }, [scrollIndex])

  const moveDown = () =&amp;gt; {
    console.log(&quot;move down&quot;)
    if (5 &amp;lt;= scrollIndex) return;
    setScrollIndex(scrollIndex + 1);
  }

  const moveUp = () =&amp;gt; {
    console.log(&quot;move Up&quot;)
    if (scrollIndex &amp;lt;= 1) return;
    setScrollIndex(scrollIndex - 1);
  };

  function getWindowDimensions() {
    const { innerWidth: width, innerHeight: height } = window;
    return {
      width,
      height
    };
  }

  useEffect(() =&amp;gt; {
    const wheelHandler = (e) =&amp;gt; {
      e.preventDefault();
      windowSize.current = getWindowDimensions();
      const { deltaY } = e;
      if (deltaY &amp;lt; 0) {
        moveUp()
      }
      else if (deltaY &amp;gt; 0) {
        moveDown()
      }

    }

    const outerDivRefCurrent = outerDivRef.current;
    outerDivRefCurrent.addEventListener(&quot;wheel&quot;, wheelHandler);
    return () =&amp;gt; {
      outerDivRefCurrent.removeEventListener(&quot;wheel&quot;, wheelHandler);
    }
  }, [])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 scrollIndex가 콜백 함수 호출시 값이 고정되어 있었냐면, 리스너를 통해 콜백 함수가 호출될 시, 호출될 때 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;state가 고정&lt;/b&gt;&lt;/span&gt;된다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 콘솔을 찍어서 테스트 해봅니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. setState 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJsnj/btrMKt3feOF/ZUwRDW6E0LJNuOm572duK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJsnj/btrMKt3feOF/ZUwRDW6E0LJNuOm572duK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJsnj/btrMKt3feOF/ZUwRDW6E0LJNuOm572duK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJsnj%2FbtrMKt3feOF%2FZUwRDW6E0LJNuOm572duK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;367&quot; height=&quot;164&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMYpnD/btrMNec9z3x/gEdwQq3uITELxndOKhozjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMYpnD/btrMNec9z3x/gEdwQq3uITELxndOKhozjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMYpnD/btrMNec9z3x/gEdwQq3uITELxndOKhozjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMYpnD%2FbtrMNec9z3x%2FgEdwQq3uITELxndOKhozjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;177&quot; height=&quot;64&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;moveDown은 callback 함수로 등록한 함수로, callback을 호출하면 스크롤 인덱스가 호출전과, 호출후에 변동이 있을 줄 알았는데, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;setState의 값은 변동이 없었습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. ref.current 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;899&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chvUIT/btrMNMU3OoL/ckwbbUJ3VohdKN0K6qttoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chvUIT/btrMNMU3OoL/ckwbbUJ3VohdKN0K6qttoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chvUIT/btrMNMU3OoL/ckwbbUJ3VohdKN0K6qttoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchvUIT%2FbtrMNMU3OoL%2FckwbbUJ3VohdKN0K6qttoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;840&quot; height=&quot;899&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;899&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref.current로 임시로 숫자를 등록해 증감하도록 useState와 같이 테스트를 해보았는데,&lt;br /&gt;useState는 변동되지 않지만, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ref.current값은 변동&lt;/b&gt;&lt;/span&gt;되는 것을 확인할 수 있었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. let 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBsh4/btrMNhnsP5z/wbuLItA6kN69HdmhWNqp90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBsh4/btrMNhnsP5z/wbuLItA6kN69HdmhWNqp90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBsh4/btrMNhnsP5z/wbuLItA6kN69HdmhWNqp90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBsh4%2FbtrMNhnsP5z%2FwbuLItA6kN69HdmhWNqp90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;836&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 useState, ref.current, let 3개를 모두 테스트 해보았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백함수가 동작했을 때, 동기적으로 값이 바뀌는건 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ref.current와 let&lt;/b&gt; &lt;/span&gt;2개 인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론:&lt;/b&gt; 콜백함수를 등록했을 때의 state 값은 고정이 되어 변동되지 않고, ref.current와 let이 변동됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 검색을 해보았는데, 이렇다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 불변성이라는 이름이 아직도 거부감이 들 수도 있는데, 위에서 확인했듯이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;한 번 랜더링된 컴포넌트가 가지고 있는 상태값은 그 중간에 변하지 않는다&lt;/b&gt;.&lt;/p&gt;
&lt;p id=&quot;b792&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;[count, setCount]&lt;span&gt;&amp;nbsp;&lt;/span&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;setCount&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 이용할 때, 이&lt;span&gt;&amp;nbsp;&lt;/span&gt;setCount&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 메모리 어딘가에 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;_val&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 변경한 것이지, 지금 옆에 가지고 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;count&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 변경된 것이 아니다.&lt;/p&gt;
&lt;p id=&quot;aa99&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;count&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 새로운 값이 되는건 리랜더링이 된 이후다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;count&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 바로 직전의&lt;span&gt;&amp;nbsp;&lt;/span&gt;count&lt;span&gt;&amp;nbsp;&lt;/span&gt;와는 전혀 관계가 없는 새로운 녀석이다.) 리랜더링할 때(= Functional Component가 다시 호출될 때) 다시&lt;span&gt;&amp;nbsp;&lt;/span&gt;useState&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 부르면 그때 변경된&lt;span&gt;&amp;nbsp;&lt;/span&gt;_val&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 가져온다고 생각할 수 있다.&lt;/p&gt;
&lt;p id=&quot;31a5&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;그래서 아래와 같은 경우에 당황한 적이 많았다. 여러분들도 분명히 있을거라 생각한다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const [state, setState] = useState(0);

useEffect(() =&amp;gt; {
  setState(state + 1); // 분명 state에 1을 더했는데?
  console.log(state); // 호출: 0
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;ad9f&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;setState&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 이용한 직후에&lt;span&gt;&amp;nbsp;&lt;/span&gt;state&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 불렀는데, 업데이트가 되지 않는 상황이었다.&lt;/p&gt;
&lt;p id=&quot;4bb0&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;처음에는 뭔가 비동기적으로 발생해서 중간에 shadow 구간이 생기는건가&amp;hellip; 라고 생각했는데 그게 아니었다.&lt;/p&gt;
&lt;p id=&quot;de81&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;state&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 새로운 값이 되려면 리랜더링이 되어야 하는데, JS는 싱글 스레드로 돌아가기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;useEffect&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 들어있는 콜백이 마무리된 이후에 리랜더링이 진행&lt;/b&gt;될 것이다. 그러면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;아직&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;console.log(state)&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 실행하는 시점에는 리랜더링이 되기 전이라는거니까,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;state&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은 0인 것이 당연&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://medium.com/hcleedev/web-usestate%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC%EC%99%80-%ED%95%A8%EC%A0%95-7b4825c16b9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/hcleedev/web-usestate%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC%EC%99%80-%ED%95%A8%EC%A0%95-7b4825c16b9&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useState()&lt;/b&gt;: 리렌더링이 발생한 이후 state값이 변경됨&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;(비동기적)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useEffect():&lt;/b&gt; dependency로 등록한 값이 변경된 이후 등록한 effect 함수가 작동함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 useState를 사용하면 값이 바뀌고, useEffect내부에서 setState를 사용할 경우, 그 다음에 바로 값이 바뀌는 것도 아니라는 것을 알았습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번의 이유에서 작성된 코드처럼 setState를 한다고 해서 값이 바뀌는 것을 가정하고 코드를 작성하면 원하는 결과가 나오지 않는다는 점이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. useState + useEffect 개선안&lt;/h3&gt;
&lt;pre id=&quot;code_1663835976950&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [state, setState] = useState(0);

setState(state + 1);
  
useEffect(() =&amp;gt; {
  console.log(state); // 호출: 1
}, [state]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 useEffect에 dependency를 등록해서 값이 변경될 경우에 호출하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. useState 내부에서의 콜백&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8Ip3g/btrMKVeAaOf/kydieLZO60z9MwR6fO20Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8Ip3g/btrMKVeAaOf/kydieLZO60z9MwR6fO20Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8Ip3g/btrMKVeAaOf/kydieLZO60z9MwR6fO20Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8Ip3g%2FbtrMKVeAaOf%2FkydieLZO60z9MwR6fO20Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;314&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 useState 내부에서 콜백을 사용하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. setState 전 값 비교&lt;/h3&gt;
&lt;pre id=&quot;code_1663836245370&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [scrollIndex, setScrollIndex] = useState(0);
  let scrollIndexLet = 0;
  
  // not working
  const moveUp1 = () =&amp;gt; {
    if (scrollIndex &amp;gt;= 5) return;
    setScrollIndex(scrollIndex + 1);
  };
  
  // working
  const moveUp2 = () =&amp;gt; {
    if (scrollIndexLet &amp;gt;= 5) return;
    scrollIndexLet += 1;
    setScrollIndex(scrollIndexLet);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState전에 state 값에 대한 필터링이 필요하다면 ref.current나 let을 사용해서 state와 관계가 없는 값을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 이슈</category>
      <category>react</category>
      <category>useEffect</category>
      <category>useState</category>
      <category>이슈</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/258</guid>
      <comments>https://narup.tistory.com/258#entry258comment</comments>
      <pubDate>Thu, 22 Sep 2022 17:46:16 +0900</pubDate>
    </item>
    <item>
      <title>[React] Redux Toolkit 에서의 비동기 처리(createAsyncThunk, extraReducers)</title>
      <link>https://narup.tistory.com/257</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서서 Redux Toolkit의 상태관리를 위한 기본적인 사용법에서 알아보았는데요, createSlice()를 통해 Slice라는 것을 생성하며, 여기에 초기 default 값과 dispatch를 통해 호출할 reducer 함수를 지정했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 개발을 하다보면, 전역에서 자주 사용되는 api를 호출하거나, api 호출한 결과를 여러 군데에서 사용해야 할 상황이 생기는데, 이와 같은 비동기 처리를 redux store에서는 자체적으로 하지 못하는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Redux를 사용할 때는 redux-thunk, redux-saga와 같은 미들웨어를 사용해서 비동기 처리를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위의 기능들 같은 경우 Redux와 같이 사용하기 위한 러닝커브가 조금 있기 때문에, Redux Toolkit의 createAsyncThunk를 사용해 비동기 처리를 진행하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. createAsyncThunk()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createAsyncThunk()의 속성은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1663576799780&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;createAsyncThunk(typePrefix: string, payloadCreator: AsyncThunkPayloadCreator,
options?: AsyncThunkOptions): AsyncThunk&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1663576652555&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const fetchUser = createAsyncThunk(
  'auth/profile',
  // 만약, api를 호출할때 object를 추가하고 싶으면 async (object, thunkAPI)로 호출합니다.
  async (_, thunkAPI) =&amp;gt; {
    try {
      const response = await axios.get&amp;lt;{
        name: string
        email: string
        type: string
      }&amp;gt;('api/users/me')
      return response.data
    } catch (error) {
      return thunkAPI.rejectWithValue(error)
    }
  }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;u&gt;첫번째 파라미터&lt;/u&gt;로 typePrefix로 액션 타입을 생성하고, &lt;u&gt;두번째 파라미터&lt;/u&gt;로 promise 기반으로 결과를 반환하는 action을 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억해야 할 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createAsyncThunk는 Promise의 3가지 상태와 같이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;pending, fulfilled, rejected의 상태&lt;/b&gt;&lt;/span&gt;를 갖습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epbIJM/btrMwYIrtuC/PLreYvJhqPryuOuf5Y70LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epbIJM/btrMwYIrtuC/PLreYvJhqPryuOuf5Y70LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epbIJM/btrMwYIrtuC/PLreYvJhqPryuOuf5Y70LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FepbIJM%2FbtrMwYIrtuC%2FPLreYvJhqPryuOuf5Y70LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;192&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 기본적으로 비동기를 처리하는 promise를 사용하는 것처럼 함수를 작성해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. extraReducer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식사이트&lt;/b&gt; :: &lt;a href=&quot;https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 비동기 처리를 할 액션을 정의했으니, 리듀서에 연결해야겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 앞에서 설명했다시피 Redux에서는 자체적으로 비동기 처리를 지원하지 않아서, extraReducers라는 것을 사용해 createAsyncThunk로 생성한 Thunk를 등록시켜주어야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1663577496996&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const fetchSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: (builder) =&amp;gt; {
    builder.addCase(fetchUser.pending, (state) =&amp;gt; {
      state.loading = 'pending';
    });
    builder.addCase(fetchUser.fulfilled, (state, action) =&amp;gt; {
      state.loading = 'succeeded';
      state.user = action.payload;
    });
    builder.addCase(fetchUser.rejected, (state) =&amp;gt; {
      state.loading = 'failed';
    });
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 extraReducer에 파라미터인 builder를 통해 addCase를 사용해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;case(pending 상태에 대한 처리, fulfilled에 대한 처리 등)&lt;/b&gt;&lt;/span&gt;를 등록해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;promise 결과가 pending이면 대기중인 상태인 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fulfilled면 성공, rejected면 거절이니까, 그 상황에 맞게끔 반환되는 값을 state에 지정해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useDispatch를 통해 createAsyncThunk를 통해 만든 Thunk 함수를 호출하고, useSelecter를 통해 state값을 동기화 시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 전역에서 사용할 수 있는 dispatch hooks을 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;store/index.ts&lt;/p&gt;
&lt;pre id=&quot;code_1663651307054&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const combinedReducers = combineReducers({
  authReducer: authSlice.reducer,
  testReducer: testSlice.reducer,
  searchItemReducer: searchItemSlice.reducer,
})
export type OurStore = ReturnType&amp;lt;typeof combinedReducers&amp;gt;

const rootReducer = (
  state: ReturnType&amp;lt;typeof combinedReducers&amp;gt;,
  action: AnyAction
) =&amp;gt; {
  if (action.type === HYDRATE) {
    const nextState = {
      ...state,
      ...action.payload,
    }
    return nextState
  }
  return combinedReducers(state, action)
}

export const store = configureStore&amp;lt;OurStore&amp;gt;({
  reducer: rootReducer,
})

// https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () =&amp;gt; AppDispatch = useDispatch

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;searchForm.ts&lt;/p&gt;
&lt;pre id=&quot;code_1663577902266&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
import { useAppDispatch } from '../store'
import { unwrapResult } from '@reduxjs/toolkit';

const LoginPetch = () =&amp;gt; {
  const dispatch = useAppDispatch();
  
  const { user, loading } = useSelector(state =&amp;gt; state.postsState);
  

  useEffect(() =&amp;gt; {
    const apiAction = dispatch(fetchUser());
    // unwrapResult 함수를 사용할 경우 바로 state의 값을 가져올 수 있다.
  	const apiResult = unwrapResult(apiAction);
  }, [dispatch]);
  
  return (
    &amp;lt;&amp;gt;
      &amp;lt;PostCardGrid user={user} /&amp;gt;
      {loading !== 'idle' &amp;amp;&amp;amp; &amp;lt;PostCardGridSkeleton /&amp;gt;}
    &amp;lt;/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>Redux</category>
      <category>reduxtoolkit</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/257</guid>
      <comments>https://narup.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 19 Sep 2022 18:01:33 +0900</pubDate>
    </item>
    <item>
      <title>[React] Redux Toolkit 사용법</title>
      <link>https://narup.tistory.com/256</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 상태관리 툴로 Redux를 사용하는데, Redux를 사용하기가 상당히 까다롭고 진입장벽이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액션 함수, 액션 생성 함수, 리듀서, 스토어... 또 타입스크립트를 쓰게 되면 해당 타입을 또 일일히 지정해주어야 하고 관리할 상태값이 1개가 생길 때마다 작성하는 코드도 많아집니다. 가독성도 안좋아지고요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제 때문에 상태관리를 도와주는 라이브러리가 등장하게 되었는데, 사실 Redux Toolkit보다 더 직관적이고 좋은 라이브러리는 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 상태관리라는 개념에 대해 정확하게 알고 사용하는 것이 좋을 것이라고 생각이 되어 Redux를 먼저 사용해보고 그 Redux를 편리하게 만든 Redux Toolkit에 알아보려고 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식사이트는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redux-toolkit.js.org/introduction/getting-started&quot;&gt;https://redux-toolkit.js.org/introduction/getting-started&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663564547968&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @reduxjs/toolkit react-redux&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 사이트에 나온 것처럼 프로젝트에 위 패키지를 설치해 줍니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 주요 함수 및 실제 사용 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷을 사용할 때 2개의 함수만 잘 사용하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 리듀서가 동작하는 createSlice(), store의 구성설정인 configureStore().&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이러니하게도 기능 압축이 잘 된 라이브러리인데, 공식 사이트의 설명이 조금 난해하게 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요점만 잘 이해하기 위해서 함수별로 정리해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1.createSlice()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/store/counterSlice.ts&lt;/p&gt;
&lt;pre id=&quot;code_1663564528648&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

interface CounterState {
  value: number
}

const initialState = { value: 0 } as CounterState

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      state.value++
    },
    decrement(state) {
      state.value--
    },
    incrementByAmount(state, action: PayloadAction&amp;lt;number&amp;gt;) {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 타입스크립트로 작성되었습니다. 자바스크립트로 된 버전은 공식사이트를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 카운트를 증감하는 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createSlice함수는 파라미터에 name, initialState, reducers 이 3개를 작성해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) initialState:&lt;/b&gt; default값이면서 동시에 상태 관리에 사용되는 type입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface로 type을 지정하고,&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; initalState의 값을 초기화&lt;/b&gt;&lt;/span&gt;를 시켜주는데, 카운터 예제이기 때문에 초기값을 0으로 설정한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) name&lt;/b&gt;: createSlice()를 통해 slice를 생성하게 되는데, 내부적으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;중복&lt;/b&gt;&lt;/span&gt;을 피하기 위해서 사용되는 고유한 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) reducers&lt;/b&gt;: &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;상태 변화를 처리하는 함수를 정의&lt;/b&gt;&lt;/span&gt;합니다. 함수의 이름은 dispatch로 부르는 액션 함수의 이름이며, 함수 내부는 위와 같이 state의 상태 값을 변경하는 처리를 해줍니다. dispatch에 포함해서 전달한 값은 PayloadAction&amp;lt;&amp;gt;의 타입의 action.payload 값으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 Redux에서 액션 타입을 지정하고, 타입에 따른 액션 생성함수, action.type에 따른 상태 변화 처리 및 불변성 처리를 3단계에 나눠서 했었다면, Redux Toolkit에서는 이 하나의 함수를 정의하는 것으로 끝납니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 후처리&lt;/b&gt;: createSlice로 생성된 counterSlice의 actions를 export시켜 외부에서 action을 호출할 수 있게 합니다. reducer 또한 export시켜 추후에 store에 리듀서를 등록할 수 있도록 노출시켜 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. configureStore()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/store/store.ts&lt;/p&gt;
&lt;pre id=&quot;code_1663568165653&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../store/counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType&amp;lt;typeof store.getState&amp;gt;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1에서 내보낸 reducer를 configureStore에 등록시켜 줍니다. 추후에 App에서 state.counter.value를 사용해 store에 저장된 리듀서의 값을 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. App 전역 Provider에 등록&lt;/h3&gt;
&lt;pre id=&quot;code_1663568316454&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.tsx
import {Provider} from &quot;react-redux&quot;
import {store} from './store/store'

ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;App /&amp;gt;
    &amp;lt;/Provider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById('root')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 store를 전역에서 사용할 수 있도록 React의 App 시작점에 Provider로 등록을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-4. Store 호출&lt;/h3&gt;
&lt;pre id=&quot;code_1663568464665&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react'
import type { RootState } from '../../app/store'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './store/counterSlice'

export function Counter() {
  const count = useSelector((state: RootState) =&amp;gt; state.counter.value)
  const dispatch = useDispatch()

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;button
          aria-label=&quot;Increment value&quot;
          onClick={() =&amp;gt; dispatch(increment())}
        &amp;gt;
          Increment
        &amp;lt;/button&amp;gt;
        &amp;lt;span&amp;gt;{count}&amp;lt;/span&amp;gt;
        &amp;lt;button
          aria-label=&quot;Decrement value&quot;
          onClick={() =&amp;gt; dispatch(decrement())}
        &amp;gt;
          Decrement
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelector Hooks를 이용해 store에 저장된 state를 가져오고, useDispatch를 사용해 변경할 값을 reducer에 전달해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Redux보다 Redux Toolkit으로 사용한 코드가 훨씬 더 직관적입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 온라인에서 실제로 해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/?from-embed&quot;&gt;https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/?from-embed&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1663564989510&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;redux-essentials-counter-example - CodeSandbox&quot; data-og-description=&quot;redux-essentials-counter-example using @reduxjs/toolkit, @testing-library/jest-dom, @testing-library/react, @testing-library/user-event, react, react-dom, react-redux, react-scripts&quot; data-og-host=&quot;codesandbox.io&quot; data-og-source-url=&quot;https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/?from-embed&quot; data-og-url=&quot;https://codesandbox.io/s/un8my&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cGRhgy/hyPQhRcAUE/5vMfjviKut46w34RhvDs5K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/?from-embed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codesandbox.io/s/github/reduxjs/redux-essentials-counter-example/tree/master/?from-embed&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cGRhgy/hyPQhRcAUE/5vMfjviKut46w34RhvDs5K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;redux-essentials-counter-example - CodeSandbox&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;redux-essentials-counter-example using @reduxjs/toolkit, @testing-library/jest-dom, @testing-library/react, @testing-library/user-event, react, react-dom, react-redux, react-scripts&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codesandbox.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>Redux</category>
      <category>reduxtoolkit</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/256</guid>
      <comments>https://narup.tistory.com/256#entry256comment</comments>
      <pubDate>Mon, 19 Sep 2022 15:23:16 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 자주 쓰는 CSS 정리</title>
      <link>https://narup.tistory.com/255</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. img, video 등 오브젝트 요소에는 object-fit: cover 속성을 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용의 크기가 요소의 크기와는 상관없이 기본 알고리즘에 의해 조정된다. 이 알고리즘은 원본의 크기에 가운데 정렬된 형태를 띈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 반응형 css 작업 우선순위&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃을 작성할 때 반응형으로 작업하고 있고, 반응형을 나누는 기준이 min-width라면 모바일 크기부터 데스크탑으로, 작은 화면에서 큰 화면으로 우선순위를 맞춰서 작업한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. display: flex 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되도록이면 flex를 사용하되 flex를 아무 이유 없이 남발하지 말것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flex 밑의 자식은 flex-item이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSGU6M/btrMdHBb5Qw/jyeUzTil25KrT2eyg4OdcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSGU6M/btrMdHBb5Qw/jyeUzTil25KrT2eyg4OdcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSGU6M/btrMdHBb5Qw/jyeUzTil25KrT2eyg4OdcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSGU6M%2FbtrMdHBb5Qw%2FjyeUzTil25KrT2eyg4OdcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;818&quot; height=&quot;342&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flex-item이 1일 경우 flex: 1 1 0이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;grow: 늘어남 / shrink 줄어듬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flex-grow가 0이면 크기 고정, 1이면 컨테이너를 채우도록 늘어남&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flex-shrink가 0이면 크기 고정, 1이면 컨테이너에 맞게 줄어듬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. overflow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용이 요소의 크기를 벗어 났을때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visible&amp;nbsp;:&amp;nbsp;박스를&amp;nbsp;넘어가도&amp;nbsp;보여줍니다. &lt;br /&gt;hidden&amp;nbsp;:&amp;nbsp;박스를&amp;nbsp;넘어간&amp;nbsp;부분은&amp;nbsp;보이지&amp;nbsp;않습니다. &lt;br /&gt;scroll&amp;nbsp;:&amp;nbsp;박스를&amp;nbsp;넘어가든&amp;nbsp;넘어가지&amp;nbsp;않든&amp;nbsp;스크롤바가&amp;nbsp;나옵니다. &lt;br /&gt;auto&amp;nbsp;:&amp;nbsp;박스를&amp;nbsp;넘어가지&amp;nbsp;않으면&amp;nbsp;스크롤바가&amp;nbsp;나오지&amp;nbsp;않고,&amp;nbsp;박스를&amp;nbsp;넘어갈&amp;nbsp;때에는&amp;nbsp;스크롤바가&amp;nbsp;나옵니다. &lt;br /&gt;initial&amp;nbsp;:&amp;nbsp;기본값으로&amp;nbsp;설정합니다. &lt;br /&gt;inherit&amp;nbsp;:&amp;nbsp;부모&amp;nbsp;요소의&amp;nbsp;속성값을&amp;nbsp;상속받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. css 상속&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd6mQE/btrMdER7GHb/ZshYnY5ajVKEEFvPM1gcU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd6mQE/btrMdER7GHb/ZshYnY5ajVKEEFvPM1gcU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd6mQE/btrMdER7GHb/ZshYnY5ajVKEEFvPM1gcU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd6mQE%2FbtrMdER7GHb%2FZshYnY5ajVKEEFvPM1gcU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;823&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 font와 color는 상속이되고, display, width와 같은 속성은 상속이 되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. grid 사용&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs05rF/btrMi60iiwL/KASb0JzadYCjts8o53Qng0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs05rF/btrMi60iiwL/KASb0JzadYCjts8o53Qng0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs05rF/btrMi60iiwL/KASb0JzadYCjts8o53Qng0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs05rF%2FbtrMi60iiwL%2FKASb0JzadYCjts8o53Qng0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1592&quot; height=&quot;519&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 디자인은 3개의 칸으로 나눠서 디자인 하고, 컬럼을 모바일 1개, 태블릿 2개, 데스크탑 3개로 나눠서 반응형으로 디자인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663306108788&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 justify-evenly gap-10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css를 위와 같이 작성하도록 한다.(with. tailwindcss)&lt;/p&gt;</description>
      <category>FrontEnd/HTML&amp;amp;CSS</category>
      <category>CSS</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/255</guid>
      <comments>https://narup.tistory.com/255#entry255comment</comments>
      <pubDate>Fri, 16 Sep 2022 13:53:39 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 자주 쓰는 문법 정리</title>
      <link>https://narup.tistory.com/254</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 배열&lt;/h3&gt;
&lt;pre id=&quot;code_1658281238865&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// default
let list: number[] = [1,2,3];
// or
let list: Array&amp;lt;number&amp;gt; = [1,2,3];



// interface array
interface EnumServiceItem {
    id: number; label: string; key: any
}
interface EnumServiceItemList extends Array&amp;lt;EnumServiceItem&amp;gt;{}
// using
var result: EnumServiceItem[] = [
    { id: 0, label: 'CId', key: 'contentId' },
    { id: 1, label: 'Modified By', key: 'modifiedBy' },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 인터페이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1658282081214&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface PeopleInterface {
  name: string
  age: number
}

// interface 확장시 extends 키워드 사용
interface StudentInterface extends PeopleInterface {
  school: string
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface 확장시 extends 키워드 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. type&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스와 같이 객체의 타입을 정의하는 키워드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type과 interface 같은 기능을 갖지만, 확장성이 좋은 interface를 사용하는 것이 더 좋다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1662598768322&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type UserType = {
  name: string;
  age: number;
};

// type 뒤의 &amp;amp;은 interface의 extends 키워드와 같은 역할(확장)
type NewUserType = UserType &amp;amp; { address: string };

let obj: NewUserType = { name: &quot;NAME&quot;, age: 29, address: 'Busan' };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ? 물음표(선택적 프로퍼티)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4-1. 선택적 프로퍼티&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 프로퍼티 이름 끝에 ?를 붙여 표시.&lt;/p&gt;
&lt;pre id=&quot;code_1662526574730&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ICompanyInfo {
  name: string;
  chairman?: string;
}

let obj: ICompanyInfo = {
  name: 'FaceBook'
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택적 프로퍼티를 사용하지 않을 경우, ICompanyInfo의 인터페이스를 모두 기술하지 않아서 에러가 발생하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 프로퍼티 이름 끝에 ?를 붙일 경우 에러가 발생하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658281387718&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface SquareConfig {
    color?: string;
    width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
    let newSquare = {color: &quot;white&quot;, area: 100};
    if (config.clor) {
        // Error: Property 'clor' does not exist on type 'SquareConfig'
        newSquare.color = config.clor;
    }
    return newSquare;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 위와 같이 인터페이스에 속하지 않은 프로퍼티의 사용을 에러로 처리해줌.(color인데 clor로 기술해서 오류 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4-2. 옵셔널 체이닝&lt;/h4&gt;
&lt;pre id=&quot;code_1662526693966&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ICompanyInfo {
  name: string | null | undefined;
}

let obj: ICompanyInfo = {
  name: &quot;FaceBook&quot;
};

console.log(obj.name?.length);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티의 타입을 여러 개로 옵션을 주었을 때, 단순하게 obj.name을 사용할 경우 null 또는 undefined가 가능하다고 문제가 발생하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obj.name?.length 로 사용할 경우 undefined를 리턴해줌 -&amp;gt; 에러처리에 용이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 제네릭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 자주볼 수 있는 기능&lt;/p&gt;
&lt;pre id=&quot;code_1662598908807&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// number 타입의 매개변수를 return하는 함수
function NumberReturnFunc(arg: number): number {
  return arg;
}

// string 타입의 매개변수를 return하는 함수
function StringReturnFunc(arg: string): string {
  return arg;
}

// boolean 타입의 매개변수를 return하는 함수
function BooleanReturnFunc(arg: boolean): boolean {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3개의 함수를&lt;/p&gt;
&lt;pre id=&quot;code_1662598917654&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function GenericReturnFunc&amp;lt;T&amp;gt;(arg: T): T {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 개의 함수로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 함수 구현 방법은 함수명 뒤에 &quot;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&amp;lt;T&amp;gt;&lt;/b&gt;&lt;/span&gt;&quot;를 추가하고, T는 매개변수의 타입 또는 반환 타입으로 설정해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 T는 변수와 같은 느낌으로, 다른 문자열을 사용해도 된다. &amp;lt;O&amp;gt;, &amp;lt;P&amp;gt;, &amp;lt;Unit&amp;gt; 등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662599031695&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let numVar = GenericReturnFunc&amp;lt;number&amp;gt;(123);
let strVar = GenericReturnFunc&amp;lt;string&amp;gt;('ABC');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는 위와 같이 함수를 호출하고 &amp;lt; &amp;gt; 내부에 적용한 타입을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 함수 호출하는 건 간단하기 때문에 함수를 잘 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662599320958&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function loggingIdentity&amp;lt;T&amp;gt;(arg: T[]): T[] {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 T라는 타입의 배열로도 사용할 수 있다. 때문에 arg의 .length는 문제가 없이 작동한다.(any와 unknown 타입인 경우 에는 에러가 발생한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-1. 제네릭 화살표 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1662599388107&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function identity&amp;lt;T&amp;gt;(arg: T): T {
  return arg;
}

let myIdentity: &amp;lt;T&amp;gt;(arg: T) =&amp;gt; T = identity;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 펑션을 아래의 화살표 처럼 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-2. 객체 리터럴 타입의 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1662599486263&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function identity&amp;lt;T&amp;gt;(arg: T): T {
  return arg;
}

let myIdentity: { &amp;lt;T&amp;gt;(arg: T): T } = identity;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-3. .tsx에서의 제네릭 화살표 함수 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1662599157591&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let GenericReturnFunc = &amp;lt;Type&amp;gt;(arg: Type): Type =&amp;gt; {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.tsx 확장자 파일에서 위와 같은 코드를 사용할 경우 &amp;lt;Type&amp;gt;에서 태그 &amp;lt;&amp;gt; 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 제네릭 화살표 함수를 구현해야 하는 경우, 제네릭 매개 변수에 extends를 사용해서 컴파일러에게 제네릭 화살표 함수라고 알려 주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1662599212167&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let GenericReturnFunc = &amp;lt;Type extends {}&amp;gt;(arg: Type): Type =&amp;gt; {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>FrontEnd/ES&amp;amp;JS 문법</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/254</guid>
      <comments>https://narup.tistory.com/254#entry254comment</comments>
      <pubDate>Thu, 8 Sep 2022 10:13:41 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] Logstash No configuration found in the configured sources. [에러해결]</title>
      <link>https://narup.tistory.com/253</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElasticSearch에 Logstash의 배치 스케쥴러를 사용하려고 했는데 configuration을 못찾는다는 오류가 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4bgA/btrIADoABs8/ymfsacELIK5iHbe8IMTfI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4bgA/btrIADoABs8/ymfsacELIK5iHbe8IMTfI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4bgA/btrIADoABs8/ymfsacELIK5iHbe8IMTfI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4bgA%2FbtrIADoABs8%2FymfsacELIK5iHbe8IMTfI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;252&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config폴더에 pipelines.yml과 logstash.conf 파일은 바꾼적이 없는데 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 에러해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링을 해본 결과...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxiNlw/btrINtLChlz/rk3WrUPy71XlWhwIzpPSd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxiNlw/btrINtLChlz/rk3WrUPy71XlWhwIzpPSd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxiNlw/btrINtLChlz/rk3WrUPy71XlWhwIzpPSd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxiNlw%2FbtrINtLChlz%2Frk3WrUPy71XlWhwIzpPSd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;885&quot; height=&quot;174&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conf 파일의 권한이 없거나, 존재하지 않거나, 이름을 틀렸거나 등등 인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 관리자 권한으로 실행을 해보았는데도 에러는 해결되지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 구글링을 해보니, logstash.bat을 실행할때 -f 뒤의 parameter로 .conf를 지정해주었던 것이 기억이 났습니다!&lt;/p&gt;
&lt;pre id=&quot;code_1659421130165&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logstash.bat -f config/logstash.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 실행시키면 잘 동작합니다.&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/ElasticSearch</category>
      <category>elasticsearch</category>
      <category>logstash</category>
      <category>에러해결</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/253</guid>
      <comments>https://narup.tistory.com/253#entry253comment</comments>
      <pubDate>Tue, 2 Aug 2022 15:19:21 +0900</pubDate>
    </item>
    <item>
      <title>[Git] 변경된 내용 삭제(git reset)</title>
      <link>https://narup.tistory.com/252</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 서비스 중인 프로그램에 대해 업데이트를 진행할 때, 우연찮게 서버 내부에서 수정사항이 발생하고, 이 수정사항에 대해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;git에 반영하지 않고 변경된 내용을 삭제&lt;/b&gt;&lt;/span&gt;하고 단순하게 pull만 받고 싶을 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1658815371625&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git reset
git checkout .
git clean -fdx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1번째 줄&lt;/b&gt; : 모든 staged된 파일이 unstage됩니다(실수로 커밋했을 경우, 로컬에서 unstage)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2번째 줄&lt;/b&gt; : 모든 변경 사항을 취소합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3번째 줄&lt;/b&gt; : 추적할 수 없는 모든 파일 제거(추적된 파일만 남습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 git pull로 변경된 내용을 받고 서비스를 진행하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://extbrain.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://extbrain.tistory.com/83&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1658815441950&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[git] 커밋되지 않았거나 저장되지 않은 모든 변경 사항 취소&quot; data-og-description=&quot;▶git 커밋되지 않았거나 저장되지 않은 모든 변경 사항 취소 ▶설명 git을 사용할 때 커밋되지 않았거나 저장되지 않은 변경 사항이 나타나는 경우가 많습니다. 이 때 현재 로컬에서 작업한 사항&quot; data-og-host=&quot;extbrain.tistory.com&quot; data-og-source-url=&quot;https://extbrain.tistory.com/83&quot; data-og-url=&quot;https://extbrain.tistory.com/83&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dak3yE/hyPdhkeNpI/dRSJLqlArW5JGqnzGRRsck/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c0CyRs/hyPdkBefdm/Rkg0aTe3MK1MpAikBb1KQ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://extbrain.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://extbrain.tistory.com/83&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dak3yE/hyPdhkeNpI/dRSJLqlArW5JGqnzGRRsck/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c0CyRs/hyPdkBefdm/Rkg0aTe3MK1MpAikBb1KQ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[git] 커밋되지 않았거나 저장되지 않은 모든 변경 사항 취소&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;▶git 커밋되지 않았거나 저장되지 않은 모든 변경 사항 취소 ▶설명 git을 사용할 때 커밋되지 않았거나 저장되지 않은 변경 사항이 나타나는 경우가 많습니다. 이 때 현재 로컬에서 작업한 사항&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;extbrain.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/기타</category>
      <category>git</category>
      <category>git명령어</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/252</guid>
      <comments>https://narup.tistory.com/252#entry252comment</comments>
      <pubDate>Tue, 26 Jul 2022 15:04:31 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] logstash + mysql window 환경 연동</title>
      <link>https://narup.tistory.com/251</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElasticSearch에 RDB로 이용하고 있는 mysql 데이터를 적재하고 싶어서 찾아보다가, elk의 스택인 logstash를 이용해서 batch job 형태로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;주기적으로 적재&lt;/b&gt;&lt;/span&gt;할 수 있는 방법을 찾게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 테스트 용도이기 때문에, 윈도우 환경에서 작업을 했고, 리눅스 환경에서의 예제는 많지만 윈도우 환경에서의 예제는 없는 것 같아 정리해보려고 합니다.(elk가 생각보다 상당히 리소스를 많이 차지합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 준비물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) ElasticSearch&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/kr/downloads/elasticsearch&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1658215271924&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download Elasticsearch&quot; data-og-description=&quot;Download Elasticsearch or the complete Elastic Stack (formerly ELK stack) for free and start searching and analyzing in minutes with Elastic.&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot; data-og-url=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bS7RZw/hyO8Q0OeOy/KDePZGz0q9Z70ILKhFp9z1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/bKd8de/hyO8YxLE5s/lURUmD1xNlJNvNdZvY2KH0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/c4tJzr/hyO8SEjmfC/uLkmvmCmM20IyVvSKOKRsk/img.png?width=1441&amp;amp;height=841&amp;amp;face=0_0_1441_841&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/kr/downloads/elasticsearch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bS7RZw/hyO8Q0OeOy/KDePZGz0q9Z70ILKhFp9z1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/bKd8de/hyO8YxLE5s/lURUmD1xNlJNvNdZvY2KH0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/c4tJzr/hyO8SEjmfC/uLkmvmCmM20IyVvSKOKRsk/img.png?width=1441&amp;amp;height=841&amp;amp;face=0_0_1441_841');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download Elasticsearch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Download Elasticsearch or the complete Elastic Stack (formerly ELK stack) for free and start searching and analyzing in minutes with Elastic.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Logstash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/logstash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/kr/downloads/logstash&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1658215240858&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download Logstash Free | Get Started Now&quot; data-og-description=&quot;Download Logstash or the complete Elastic Stack (formerly ELK stack) for free and start collecting, searching, and analyzing your data with Elastic in minutes.&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/kr/downloads/logstash&quot; data-og-url=&quot;https://www.elastic.co/kr/downloads/logstash&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fF5Uv/hyO8Rej6x5/JzMB9a0kKd20IpY22kAPK1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/bHJs5B/hyO8TiVFe3/coz0AV6QFFQRAvOLq0SFw0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/7Pknw/hyO8OhBBQW/MSpCms0yr5Xq99H4SAhNqK/img.png?width=1438&amp;amp;height=839&amp;amp;face=0_0_1438_839&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/logstash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/kr/downloads/logstash&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fF5Uv/hyO8Rej6x5/JzMB9a0kKd20IpY22kAPK1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/bHJs5B/hyO8TiVFe3/coz0AV6QFFQRAvOLq0SFw0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/7Pknw/hyO8OhBBQW/MSpCms0yr5Xq99H4SAhNqK/img.png?width=1438&amp;amp;height=839&amp;amp;face=0_0_1438_839');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download Logstash Free | Get Started Now&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Download Logstash or the complete Elastic Stack (formerly ELK stack) for free and start collecting, searching, and analyzing your data with Elastic in minutes.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) Mysql, Mysql connector&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB로 사용하는 Mysql은 알아서 설치가 되었다고 생각하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&quot;&gt;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1658215312593&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Maven Repository: mysql &amp;raquo; mysql-connector-java &amp;raquo; 8.0.18&quot; data-og-description=&quot;JDBC Type 4 driver for MySQL Note: There is a new version for this artifact mysql mysql-connector-java 8.0.18 // https://mvnrepository.com/artifact/mysql/mysql-connector-java implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.18' //&quot; data-og-host=&quot;mvnrepository.com&quot; data-og-source-url=&quot;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&quot; data-og-url=&quot;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.18?__cf_chl_rt_tk=zaRajG9xCh3rQuIRHVjRuu4RLOo4WgNidVrt8TTON2k-1658197549-0-gaNycGzNCL0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Maven Repository: mysql &amp;raquo; mysql-connector-java &amp;raquo; 8.0.18&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JDBC Type 4 driver for MySQL Note: There is a new version for this artifact mysql mysql-connector-java 8.0.18 // https://mvnrepository.com/artifact/mysql/mysql-connector-java implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.18' //&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mvnrepository.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-connctor-java.jar를 받아줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 포트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElasticSearch = http://localhost:9200/&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Logstash 설정 파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 설정 파일의 폴더 구성은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enC64n/btrHJbkNqhT/oAw032ccsAMbh7hFYU4okK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enC64n/btrHJbkNqhT/oAw032ccsAMbh7hFYU4okK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enC64n/btrHJbkNqhT/oAw032ccsAMbh7hFYU4okK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenC64n%2FbtrHJbkNqhT%2FoAw032ccsAMbh7hFYU4okK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;233&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;config/pipelines.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658215496109&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...

# List of pipelines to be loaded by Logstash
#
# This document must be a list of dictionaries/hashes, where the keys/values are pipeline settings.
# Default values for omitted settings are read from the `logstash.yml` file.
# When declaring multiple pipelines, each MUST have its own `pipeline.id`.
#
# Example of two pipelines:
#
- pipeline.id: &quot;mysql&quot;
  pipeline.workers: 1
  pipeline.batch.size: 1
  path.config: &quot;C:/elk/logstash-8.3.2/config/*.config&quot;
#   config.string: &quot;input { generator {} } filter { sleep { time =&amp;gt; 1 } } output { stdout { codec =&amp;gt; dots } }&quot;
# - pipeline.id: another_test
#   queue.type: persisted

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 4개의 주석을 제외해주고 작성해주는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path.config: 뒤의 경로는 실제로 logstash가 설치된 config 폴더의 경로를 입력해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;config/logstash.conf&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658215557424&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Sample Logstash configuration for creating a simple
# Beats -&amp;gt; Logstash -&amp;gt; Elasticsearch pipeline.

input {
  jdbc {
	jdbc_driver_library =&amp;gt; &quot;C:/elk/logstash-8.3.2/lib/mysql-connector-java-8.0.18.jar&quot;
	jdbc_driver_class =&amp;gt; &quot;com.mysql.jdbc.Driver&quot;
	jdbc_connection_string =&amp;gt; &quot;jdbc:mysql://localhost:3306/maindatabase?serverTimezone=UTC&quot;
	jdbc_user =&amp;gt; &quot;root&quot;
	jdbc_password =&amp;gt; &quot;1234&quot;
	jdbc_paging_enabled =&amp;gt; true
	tracking_column =&amp;gt; &quot;unix_ts_in_secs&quot;
	use_column_value =&amp;gt; true
	tracking_column_type =&amp;gt; &quot;numeric&quot;
	schedule =&amp;gt; &quot;*/60 * * * * *&quot;
	statement =&amp;gt; &quot;SELECT *, UNIX_TIMESTAMP(updateAt) AS unix_ts_in_secs
		FROM maindatabase.shop WHERE (UNIX_TIMESTAMP(updateAt) &amp;gt; :sql_last_value
		AND updateAt &amp;lt; NOW()) ORDER BY updateAt ASC&quot;
  }
}

filter {
  mutate {
    copy =&amp;gt; { &quot;id&quot; =&amp;gt; &quot;[@metadata][_id]&quot;}
    remove_field =&amp;gt; [&quot;id&quot;, &quot;@version&quot;, &quot;unix_ts_in_secs&quot;]
  }
}

output{
   elasticsearch{
		hosts =&amp;gt; [&quot;127.0.0.1:9200&quot;]
    	index =&amp;gt; &quot;shop&quot; 
    	document_id =&amp;gt; &quot;%{[@metadata][_id]}&quot;
    	document_type =&amp;gt; &quot;_doc&quot;
    #user =&amp;gt; &quot;elastic&quot;
    #password =&amp;gt; &quot;changeme&quot;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logstash를 받고 나서 logstash-sample.conf가 있을 텐데 그 파일명을 위처럼 수정해주시면 되고, 위의 내용을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일의 설정값으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;RDB에서 특정 데이터베이스의 테이블의 ID를 읽어서 ElasticSearch의 특정 주기마다 적재&lt;/b&gt;&lt;/span&gt;하기 때문에 이 설정값에 대한 이해가 중요합니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;b&gt;jdbc_driver_library&lt;/b&gt; : 아까 받은 mysql connector java의 경로를 작성합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;b&gt;jdbc_connection_string&lt;/b&gt; : database 접속 주소를 설정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;b&gt;schedule&lt;/b&gt; : 스케쥴 주기(크론식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;statement(중요함!!)&lt;/b&gt;&lt;/span&gt; : 데이터베이스에서 테이블을 조회하는 쿼리를 작성합니다. FROM 뒤에는 테이블을 작성하시고, :sql_last_value는 현재 스케쥴러가 동작한 시간이 들어가기 때문에, 이에 맞춰서 sql을 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) &lt;b&gt;hosts&lt;/b&gt; : ElasticSearch의 주소를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) &lt;b&gt;index&lt;/b&gt; : _doc/index에 적재될 이름을 지정합니다. (document_id는 보통 테이블의 Unique한 id번호로 지정하게 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cmd창을 열고, C:\elk\logstash-8.3.2\bin과 같은 logstash가 설치된 bin 폴더에 들어가서 아래의 명령어를 입력해 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-f 인자로 &quot;config/logstash.conf&quot;, 4번에서 설정한 logstash의 설정 파일 인자를 넘겨주는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1658216047808&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logstash.bat -f config/logstash.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;log4j에 의해서 실행 중인 로그를 확인할 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 상태가 ERROR인 상태의 내용을 확인하면서 조정하시면 정상적으로 RDB의 테이블에서 데이터를 읽어서 ElasticSearch에 적재를 할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 shop라는 인덱스에 적재가 된 내용을 확인해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MAnS6/btrHIGMq5PV/KAQY0e5LtSs9ghimn9Kzt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MAnS6/btrHIGMq5PV/KAQY0e5LtSs9ghimn9Kzt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MAnS6/btrHIGMq5PV/KAQY0e5LtSs9ghimn9Kzt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMAnS6%2FbtrHIGMq5PV%2FKAQY0e5LtSs9ghimn9Kzt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;436&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/ElasticSearch</category>
      <category>elasticsearch</category>
      <category>logstash</category>
      <category>MySQL</category>
      <category>window</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/251</guid>
      <comments>https://narup.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 19 Jul 2022 16:36:53 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] Insert(생성), Search(조회) 기본적인 CRUD</title>
      <link>https://narup.tistory.com/250</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB를 하다가 ElasticSearch를 찾아보는데, 개념 자체가 RDB랑은 많이 달라서 최대한 알기 쉽게끔 필요한 기능만 정리를 해보려고 이 글을 작성해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터, 샤드랑 노드, 인덱스, 다큐먼트 등 개념적인 부분은 이 글에서는 다루지 않겠고, 아래 가이드북 링크를 참고해주시길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://esbook.kimjmin.net/03-cluster/3.2-index-and-shards&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://esbook.kimjmin.net/03-cluster/3.2-index-and-shards&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElasticSearch 버전은 7.0버전 이상의 버전으로 진행합니다. 7.0버전부터 type이 사라졌기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클러스터 관련 Rest API&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 클러스터 상태 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1658112095852&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XGET http://localhost:9200/_cluster/health?pretty=true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I01ct/btrHsDKgE7r/yDfg9HFtU1qgFrs7v1NE2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I01ct/btrHsDKgE7r/yDfg9HFtU1qgFrs7v1NE2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I01ct/btrHsDKgE7r/yDfg9HFtU1qgFrs7v1NE2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI01ct%2FbtrHsDKgE7r%2FyDfg9HFtU1qgFrs7v1NE2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;412&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;b&gt; status: yellow&lt;/b&gt; = 모든 데이터가 읽기/쓰기 가능한 상태입니다. 다만, replica shard(복제 샤드)가 배정되지 않은 상태이므로 검색 성능에 영향이 있을 수 있습니다를 알려주는 상태 표시입니다. 복제 샤드가 있으면 초록색으로 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 클러스터 인덱스 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1658112374324&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XGET http://localhost:9200/_cat/indices?v
# curl -XGET http://localhost:9200/_cat/shards?v&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l905I/btrHCoj37pG/nwxjKCk4ycwVhCfZtcAXF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l905I/btrHCoj37pG/nwxjKCk4ycwVhCfZtcAXF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l905I/btrHCoj37pG/nwxjKCk4ycwVhCfZtcAXF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl905I%2FbtrHCoj37pG%2FnwxjKCk4ycwVhCfZtcAXF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;901&quot; height=&quot;181&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제들 가운데서 _cat이라는 url 속성이 사용자가 생성한 인덱스인줄 알았는데, elastic search에서 제공하는 고정된 기본 기능입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;span data-offset-key=&quot;ab3895445fff4a30908bdfdf178d7847:4&quot;&gt;인덱스라는 단어가 여러 뜻으로 사용되기 때문에 데이터 저장 단위인 인덱스는 &lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;인디시즈(indices)&lt;/b&gt;&lt;/span&gt;&lt;span data-offset-key=&quot;ab3895445fff4a30908bdfdf178d7847:6&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMiVFQyU5RCVCOCVFQiU4RCVCMSVFQyU4QSVBNCVFQiU5RCVCQyVFQiU4QSU5NCUyMCVFQiU4QiVBOCVFQyU5NiVCNCVFQSVCMCU4MCUyMCVFQyU5NyVBQyVFQiU5RiVBQyUyMCVFQiU5QyVCQiVFQyU5QyVCQyVFQiVBMSU5QyUyMCVFQyU4MiVBQyVFQyU5QSVBOSVFQiU5MCU5OCVFQSVCOCVCMCUyMCVFQiU5NSU4QyVFQiVBQyVCOCVFQyU5NyU5MCUyMCVFQiU4RCVCMCVFQyU5RCVCNCVFRCU4NCVCMCUyMCVFQyVBMCU4MCVFQyU5RSVBNSUyMCVFQiU4QiVBOCVFQyU5QyU4NCVFQyU5RCVCOCUyMCVFQyU5RCVCOCVFQiU4RCVCMSVFQyU4QSVBNCVFQiU4QSU5NCUyMCUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCUyQyU3QiUyMm9iamVjdCUyMiUzQSUyMmxlYWYlMjIlMkMlMjJ0ZXh0JTIyJTNBJTIyJUVDJTlEJUI4JUVCJTk0JTk0JUVDJThCJTlDJUVDJUE2JTg4KGluZGljZXMpJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJtYXJrJTIyJTJDJTIydHlwZSUyMiUzQSUyMmJvbGQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTdEJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCUyQyU3QiUyMm9iamVjdCUyMiUzQSUyMmxlYWYlMjIlMkMlMjJ0ZXh0JTIyJTNBJTIyJTIwJUVCJTlEJUJDJUVBJUIzJUEwJTIwJUVEJTkxJTlDJUVEJTk4JTg0JUVEJTk1JTk4JUVBJUI4JUIwJUVCJThGJTg0JTIwJUVEJTk1JUE5JUVCJThCJTg4JUVCJThCJUE0LiUyMCUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjI0MzJhZmIxMWQyOTQ2NzI5MGNkOTBmNzdmOWZkNDNjJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyMzA2MWFjZWIxNDA0NGMwNzg5NzE4NjMyM2M1MGE0NWYlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIyNmY0ZDlmMjc4ZTk0NjdiYTYwZWVmNjA3NjIzY2Y4ZiUyMiU3RA==&quot;&gt;라고 표현하기도 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) index 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1658117880169&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XPUT 'localhost:9200/test?pretty'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test라는 인덱스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB와 다르게 create, insert 이런 문법은 사용하지 않고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PUT REST API&lt;/b&gt;&lt;/span&gt;를 통해 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;pretty&lt;/b&gt;는 결과(response)를 잘 정리해서 보여주는 옵션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;acknowledged : true&lt;/b&gt;면 결과가 성공되었다는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUoGN6/btrHzoZroF8/gPhh2973Sx2CeTHrkXd8M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUoGN6/btrHzoZroF8/gPhh2973Sx2CeTHrkXd8M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUoGN6/btrHzoZroF8/gPhh2973Sx2CeTHrkXd8M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUoGN6%2FbtrHzoZroF8%2FgPhh2973Sx2CeTHrkXd8M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;179&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1658118006847&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XGET http://localhost:9200/_cat/indices?v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 생성한 후, 2번의 인덱스를 다시 조회해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) document 생성(PUT)&lt;/h3&gt;
&lt;pre id=&quot;code_1658118127177&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XPUT 'localhost:9200/test2/_doc/1?pretty' -H 'Content-Type: application/json' -d '{
&quot;name&quot;: &quot;james&quot;
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PUT REST API&lt;/b&gt;&lt;/span&gt;를 이용해서 document를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nreDi/btrHBAldSLV/EuBQy6Nl04nL0CANKLl9Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nreDi/btrHBAldSLV/EuBQy6Nl04nL0CANKLl9Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nreDi/btrHBAldSLV/EuBQy6Nl04nL0CANKLl9Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnreDi%2FbtrHBAldSLV%2FEuBQy6Nl04nL0CANKLl9Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;686&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test2라는 index에 id는 1인 곳에 name이 james라는 데이터를 생성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* PostMan이라는 프로그램을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 동일한 URL에 다른 내용의 document를 입력하게 되면 위와 같이 &quot;result&quot; : &quot;update&quot;라는 결과를 받게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) Document 조회 (GET)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 생성이 되었다면, 해당 인덱스의 document를 조회해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1658122709748&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# curl -XGET 'http://localhost:9200/test2/_doc/1?pretty'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1uQIt/btrHCXf0nXD/t7jf9iy9UmfiQUDA7yFYh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1uQIt/btrHCXf0nXD/t7jf9iy9UmfiQUDA7yFYh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1uQIt/btrHCXf0nXD/t7jf9iy9UmfiQUDA7yFYh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1uQIt%2FbtrHCXf0nXD%2Ft7jf9iy9UmfiQUDA7yFYh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;589&quot; height=&quot;526&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* url에 있는 _search는 query DSL으로 사용되는 API이며, 뒤에 query를 명시함에 따라 다른 결과를 받아볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch에서는&amp;nbsp;단일&amp;nbsp;도큐먼트별로&amp;nbsp;고유한&amp;nbsp;URL을&amp;nbsp;갖습니다.&amp;nbsp;도큐먼트에&amp;nbsp;접근하는&amp;nbsp;URL은&amp;nbsp; &lt;br /&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;http://&amp;lt;호스트&amp;gt;:&amp;lt;포트&amp;gt;/&amp;lt;인덱스&amp;gt;/_doc/&amp;lt;도큐먼트&amp;nbsp;id&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;구조로&amp;nbsp;되어&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.x 이전 까지는,&amp;nbsp; &lt;u&gt;http://&amp;lt;호스트&amp;gt;:&amp;lt;포트&amp;gt;/&amp;lt;인덱스&amp;gt;/&amp;lt;도큐먼트 타입&amp;gt;/&amp;lt;도큐먼트 id&amp;gt;&lt;/u&gt; 구조였으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch 7.0 부터는 도큐먼트 타입 개념이 사라지고 대신 고정자&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&amp;nbsp;_doc&lt;/b&gt;&lt;/span&gt; 으로&amp;nbsp;접근해야&amp;nbsp;합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://esbook.kimjmin.net/04-data/4.2-crud&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://esbook.kimjmin.net/04-data/4.2-crud&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/ElasticSearch</category>
      <category>elasticsearch</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/250</guid>
      <comments>https://narup.tistory.com/250#entry250comment</comments>
      <pubDate>Mon, 18 Jul 2022 15:10:27 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] The bulk request must be terminated by a newline [\\n] 에러</title>
      <link>https://narup.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 개요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elastic search의 자동완성 기능을 구현해보려고 샘플 데이터를 insert하는 도중,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;The bulk request must be terminated by a newline&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;[\\n]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;와 같은 에러가 발생했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;2. 해결 방법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658107821928&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;1&quot;}}
{&quot;word&quot;:&quot;스팀게임&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;2&quot;}}
{&quot;word&quot;:&quot;스팀게임 추천&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;3&quot;}}
{&quot;word&quot;:&quot;스팀게임 추천 2019&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;4&quot;}}
{&quot;word&quot;:&quot;스팀게임 환불&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;5&quot;}}
{&quot;word&quot;:&quot;스팀게임 싸게&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;6&quot;}}
{&quot;word&quot;:&quot;스팀게임 순위&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;7&quot;}}
{&quot;word&quot;:&quot;스팀게임 추천 2020&quot;}
{&quot;index&quot;:{&quot;_index&quot;:&quot;autocomplete_test_1&quot;,&quot;_id&quot;:&quot;8&quot;}}
{&quot;word&quot;:&quot;스팀게임 환불하는법&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 데이터가 위와 같은 데이터라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;맨 마지막 줄에 Enter로 한 줄을 추가&lt;/b&gt;&lt;/span&gt;해주시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgDgx1/btrHAH5c4hZ/yTntr3hkgUq3gHNVk2ffM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgDgx1/btrHAH5c4hZ/yTntr3hkgUq3gHNVk2ffM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgDgx1/btrHAH5c4hZ/yTntr3hkgUq3gHNVk2ffM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgDgx1%2FbtrHAH5c4hZ%2FyTntr3hkgUq3gHNVk2ffM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;446&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 insert가 잘 됩니다.&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/ElasticSearch</category>
      <category>elasticsearch</category>
      <category>오류해결</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/249</guid>
      <comments>https://narup.tistory.com/249#entry249comment</comments>
      <pubDate>Mon, 18 Jul 2022 10:31:39 +0900</pubDate>
    </item>
    <item>
      <title>[Mysql] 위도 경도 반경 구하기</title>
      <link>https://narup.tistory.com/248</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도에 관한 로직을 짤때 latitude, longitude 위도 경도 값을 구해서 radius, 위도 경도 값 기준으로 주변 5km, 10km의 위도 경도 리스트 또는 거리를 알고 싶을 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 로직을 mysql에서 구현할 수 있을까 해서 찾아봤는데 stack over flow에서 친절하게도 작성된 sql이 있더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657850724725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Radius of 40 kilometers using latitude and longitude&quot; data-og-description=&quot;Possible Duplicate: Radius of multiple latitude/longitude points distance calculations in mysql queries select within 20 kilometers based on latitude/longitude I have a MyISAM table, which...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&quot; data-og-url=&quot;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d8ZnDR/hyO5SK9ezM/zv7ja254UyffQNc1hvKbH0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/8850336/radius-of-40-kilometers-using-latitude-and-longitude&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d8ZnDR/hyO5SK9ezM/zv7ja254UyffQNc1hvKbH0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Radius of 40 kilometers using latitude and longitude&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Possible Duplicate: Radius of multiple latitude/longitude points distance calculations in mysql queries select within 20 kilometers based on latitude/longitude I have a MyISAM table, which...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. SQL&lt;/h2&gt;
&lt;pre id=&quot;code_1657850713252&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
    *,
    ( 6371 * acos( cos( radians({$lat}) ) * cos( radians( `lat` ) ) * cos( radians( `lng` ) - radians({$lng}) ) + sin( radians({$lat}) ) * sin( radians( `lat` ) ) ) ) AS distance
FROM `positions`
HAVING distance &amp;lt;= {$radius}
ORDER BY distance ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 공식에 대한 이해는 부족하지만, 위도와 경도를 기준으로 해당 테이블 안에 위도와 경도 중에서 거리가 {$radius}보다 작은 위도 경도 리스트를 출력하는 쿼리라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PHU7v/btrHjceQJ63/KUTYityfjkfbmrgmjh3d6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PHU7v/btrHjceQJ63/KUTYityfjkfbmrgmjh3d6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PHU7v/btrHjceQJ63/KUTYityfjkfbmrgmjh3d6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPHU7v%2FbtrHjceQJ63%2FKUTYityfjkfbmrgmjh3d6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;407&quot; height=&quot;225&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 radius를 2로 주었고, 결과는 위와 같이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/RDBMS</category>
      <category>MySQL</category>
      <category>위도경도반경구하기</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/248</guid>
      <comments>https://narup.tistory.com/248#entry248comment</comments>
      <pubDate>Fri, 15 Jul 2022 11:07:18 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] router query(props) 값이 비어있는 오류</title>
      <link>https://narup.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 개요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbjYgs/btrHiYgKY9t/zYyadsL9U75PQfsGUnmswK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbjYgs/btrHiYgKY9t/zYyadsL9U75PQfsGUnmswK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbjYgs/btrHiYgKY9t/zYyadsL9U75PQfsGUnmswK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbjYgs%2FbtrHiYgKY9t%2FzYyadsL9U75PQfsGUnmswK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;218&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 router를 사용해서 페이지 이동을 할때, query에 keyword를 담아서 페이지 이동을 시키려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;search라는 페이지에 해당하는 tsx에서 props로 저 쿼리가 읽혀야 하는데 props.data에 값이 비어있었습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해결 방법&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXBVG/btrHksAIiWc/TrQsHP6gaHRmDZSFrPglN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXBVG/btrHksAIiWc/TrQsHP6gaHRmDZSFrPglN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXBVG/btrHksAIiWc/TrQsHP6gaHRmDZSFrPglN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXBVG%2FbtrHksAIiWc%2FTrQsHP6gaHRmDZSFrPglN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;415&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657849464523&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const router = useRouter();
  const queries = router.query; // 전달받은 쿼리 내용

  useEffect(() =&amp;gt; {
    if (!router.isReady) return;
    console.log(queries)
    
    }, () =&amp;gt; { })
  }, [router.isReady])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 전달 받은 tsx에서 useRouter() 객체를 가지고 초기화를 해서 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;정적파일 최적화(Automatic Static Optimaizaion)&lt;/b&gt;&lt;/span&gt;에 의해서 최적화된 페이지의 루트 매개 변수가 제공되지 않아서 query가 빈 객체가 된다고 합니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 router의 isReady를 useEffect에서 감시하도록 두고 그 내부에서 쿼리로 받은 데이터를 사용하면 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://im-designloper.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://im-designloper.tistory.com/98&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/Next.js</category>
      <category>Next.js</category>
      <category>정적파일최적화</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/247</guid>
      <comments>https://narup.tistory.com/247#entry247comment</comments>
      <pubDate>Fri, 15 Jul 2022 10:47:00 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] received plaintext http traffic on an https channel, closing connection 에러</title>
      <link>https://narup.tistory.com/246</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 환경에서 ElasticSearch를 설치하고 localhost:9200으로 열어보았을때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;received&amp;nbsp;plaintext&amp;nbsp;http&amp;nbsp;traffic&amp;nbsp;on&amp;nbsp;an&amp;nbsp;https&amp;nbsp;channel,&amp;nbsp;closing&amp;nbsp;connection&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 오류가 발생하고 연결이 되지 않는 이슈가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[elasticsearch설치폴더]/config/elasticsearch.yml&lt;/p&gt;
&lt;pre id=&quot;code_1657758569420&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...


# Enable security features
xpack.security.enabled: false

xpack.security.enrollment.enabled: false

# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
  enabled: false
  keystore.path: certs/http.p12

# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
  enabled: false
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
# Create a new cluster with the current node only
# Additional nodes can still join the cluster later
cluster.initial_master_nodes: [&quot;DESKTOP-4GLM324&quot;]

# Allow HTTP API connections from anywhere
# Connections are encrypted and require user authentication
http.host: 0.0.0.0

# Allow other nodes to join the cluster from anywhere
# Connections are encrypted and mutually authenticated
#transport.host: 0.0.0.0



...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- xpack.security.enabled: false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- xpack.security.enrollment.enabled: false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- xpack.security.http.ssl: &lt;br /&gt;&amp;nbsp;&amp;nbsp;enabled: false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- xpack.security.transport.ssl: &lt;br /&gt;&amp;nbsp;&amp;nbsp;enabled: false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 설정 파일에 들어가서 총 4개의 옵션을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;true-&amp;gt; false&lt;/b&gt;&lt;/span&gt;로 변경해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elasticsearch를 사용할 때 https를 통해서&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; ssl 인증서&lt;/b&gt;&lt;/span&gt;가 필요하게 되는데, 이 인증서에 대한 작업이 선행되지 않아 보안적인 이슈가 발생하는 것으로 확인됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local에서 테스트 할 때는 저 옵션만 false로 바꿔서 테스트를 진행하고, 실제 운영할 때는 ssl을 적용하는 방향으로 해결을 하는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후에 http://localhost:9200 로 접속하면 아래와 같이 연결 된 것을 확인할 수 있습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDx5Pw/btrHfQnSuHo/BKZSjiJr51T8APsB0UI4nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDx5Pw/btrHfQnSuHo/BKZSjiJr51T8APsB0UI4nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDx5Pw/btrHfQnSuHo/BKZSjiJr51T8APsB0UI4nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDx5Pw%2FbtrHfQnSuHo%2FBKZSjiJr51T8APsB0UI4nK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;264&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/ElasticSearch</category>
      <category>elasticsearch</category>
      <category>에러해결</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/246</guid>
      <comments>https://narup.tistory.com/246#entry246comment</comments>
      <pubDate>Thu, 14 Jul 2022 09:35:17 +0900</pubDate>
    </item>
    <item>
      <title>[Node.js] Selenium 크롤링 사용</title>
      <link>https://narup.tistory.com/245</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하다보니 실제 프로젝트에 사용할 데이터가 필요할 때가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터를 얻기 위해 자연스럽게 인터넷의 정보를 쉽게 얻어 올 수 있기 위해 크롤링을 사용해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 패키지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 폴더를 하나 생성하고&lt;/p&gt;
&lt;pre id=&quot;code_1657613099463&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 node.js 기반 패키지를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 라이브러리는 selenium으로&lt;/p&gt;
&lt;pre id=&quot;code_1657613124928&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install selenium-webdriver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 패키지를 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;selenium 크롤링을 사용하기 위해서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;브라우저의 드라이버&lt;/b&gt;&lt;/span&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://chromedriver.storage.googleapis.com/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://chromedriver.storage.googleapis.com/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 크롬 드라이버를 사용했기 때문에, 위의 링크에서 최신 크롬 드라이버를 받아주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Yx6d/btrG6V41aZ7/fOnC8Rzq0GRIfXtZuvDX4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Yx6d/btrG6V41aZ7/fOnC8Rzq0GRIfXtZuvDX4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Yx6d/btrG6V41aZ7/fOnC8Rzq0GRIfXtZuvDX4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Yx6d%2FbtrG6V41aZ7%2FfOnC8Rzq0GRIfXtZuvDX4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;430&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운 받은 드라이버는 프로젝트 폴더에 넣어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHZOtc/btrG8mBbe3Q/RgzEIzkKMb3r0vHe3czf31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHZOtc/btrG8mBbe3Q/RgzEIzkKMb3r0vHe3czf31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHZOtc/btrG8mBbe3Q/RgzEIzkKMb3r0vHe3czf31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHZOtc%2FbtrG8mBbe3Q%2FRgzEIzkKMb3r0vHe3czf31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;223&quot; height=&quot;132&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.js&lt;/p&gt;
&lt;pre id=&quot;code_1657612948729&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {Builder, By, Key, until} = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');    

const run = async () =&amp;gt; {
	// headless로 크롬 드라이버 실행
    let driver = await new Builder()
    .forBrowser('chrome')    
    .setChromeOptions(new chrome.Options().headless().addArguments(&quot;--disable-gpu&quot;, &quot;window-size=1920x1080&quot;,
    &quot;lang=ko_KR&quot;))
    .build();

    try {
        // 특정 URL 생성
        await driver.get('https://map.kakao.com/');
        let userAgent = await driver.executeScript(&quot;return navigator.userAgent;&quot;)
        console.log('[UserAgent]', userAgent);

        // By.id로 #query Element를 얻어온다.
        let searchInput = await driver.findElement(By.id('search.keyword.query'));
        // keyword를 선택하고 검색 버튼 사용
        let keyword = &quot;닭발&quot;;
        searchInput.sendKeys(keyword, Key.ENTER);

        // css selector로 가져온 element가 위치할때까지 최대 10초간 기다린다.
        await driver.wait(until.elementLocated(By.id('info.search.place.list')), 10000);
        let resultElements  = await driver.findElements(By.className(&quot;placetit&quot;));

        // 검색한 elements 하위의 value를 출력함
        console.log('[resultElements.length]', resultElements.length)
        for (var i = 0; i &amp;lt; resultElements.length; i++) {
            console.log('- ' + await resultElements[i].getCssValue())
        }
    }
    catch(e){
        console.log(e);
    }
    finally {
        driver.quit();
    }
}
run();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;headless, 크롬 창이 팝업되지 않은 백그라운드에서 크롤링이 되는 옵션을 사용했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작하는 과정을 확인하고 싶다면 .setChromeOptions(...)을 지우시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1657613504164&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node index.js를 입력해 실행하면 elementes의 결과물이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 문법은 아래 링크를 참고하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657613559889&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;selenium-webdriver&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.selenium.dev&quot; data-og-source-url=&quot;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&quot; data-og-url=&quot;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;selenium-webdriver&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.selenium.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Node.js</category>
      <category>node.js</category>
      <category>Selenium</category>
      <category>크롤링</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/245</guid>
      <comments>https://narup.tistory.com/245#entry245comment</comments>
      <pubDate>Tue, 12 Jul 2022 17:12:47 +0900</pubDate>
    </item>
    <item>
      <title>[React] Redux 사용하기(2) - 카운터 구현</title>
      <link>https://narup.tistory.com/244</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 글에서 흐름을 설명했던 카운터를 실제로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 아래 블로그를 참고했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.vlpt.us/redux/04-make-modules.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://react.vlpt.us/redux/04-make-modules.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655787007836&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;4. 리덕스 모듈 만들기 &amp;middot; GitBook&quot; data-og-description=&quot;4. 리덕스 모듈 만들기 이번에는, 리액트 프로젝트에 리덕스를 적용하기 위해서 리덕스 모듈을 만들어보도록 하겠습니다. 리덕스 모듈이란 다음 항목들이 모두 들어있는 자바스크립트 파일을 &quot; data-og-host=&quot;react.vlpt.us&quot; data-og-source-url=&quot;https://react.vlpt.us/redux/04-make-modules.html&quot; data-og-url=&quot;https://react.vlpt.us/redux/04-make-modules.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://react.vlpt.us/redux/04-make-modules.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.vlpt.us/redux/04-make-modules.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;4. 리덕스 모듈 만들기 &amp;middot; GitBook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;4. 리덕스 모듈 만들기 이번에는, 리액트 프로젝트에 리덕스를 적용하기 위해서 리덕스 모듈을 만들어보도록 하겠습니다. 리덕스 모듈이란 다음 항목들이 모두 들어있는 자바스크립트 파일을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.vlpt.us&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 코드 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0lIYT/btrFoSniWfp/wBSnjkLzWLtg7lNM6f73Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0lIYT/btrFoSniWfp/wBSnjkLzWLtg7lNM6f73Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0lIYT/btrFoSniWfp/wBSnjkLzWLtg7lNM6f73Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0lIYT%2FbtrFoSniWfp%2FwBSnjkLzWLtg7lNM6f73Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;255&quot; height=&quot;121&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은 위와 같습니다. 인풋 값으로 숫자를 입력받아 그 값만큼 현재값에서 +하거나 -하는 카운터 기능입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G9JHt/btrFoBF6VnT/JwkzQDfJKcX4m6a9ihVHf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G9JHt/btrFoBF6VnT/JwkzQDfJKcX4m6a9ihVHf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G9JHt/btrFoBF6VnT/JwkzQDfJKcX4m6a9ihVHf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG9JHt%2FbtrFoBF6VnT%2FJwkzQDfJKcX4m6a9ihVHf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;423&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;423&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 폴더 구조는 위와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;module/counter.js&lt;/h3&gt;
&lt;pre id=&quot;code_1655787051036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 액션 타입 만들기 */
// Ducks 패턴을 따를땐 액션의 이름에 접두사를 넣어주세요.
// 이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지 할 수 있습니다.
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

/* 액션 생성함수 만들기 */
// 액션 생성함수를 만들고 export 키워드를 사용해서 내보내주세요.
export const setDiff = (diff) =&amp;gt; ({ type: SET_DIFF, diff });
export const increase = () =&amp;gt; ({ type: INCREASE });
export const decrease = () =&amp;gt; ({ type: DECREASE });

/* 초기 상태 선언 */
const initialState = {
  number: 0,
  diff: 1
};

/* 리듀서 선언 */
// 리듀서는 export default 로 내보내주세요.
export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case SET_DIFF:
      return {
        ...state,
        diff: action.diff
      };
    case INCREASE:
      return {
        ...state,
        number: state.number + state.diff
      };
    case DECREASE:
      return {
        ...state,
        number: state.number - state.diff
      };
    default:
      return state;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;modules/reducer.js&lt;/h3&gt;
&lt;pre id=&quot;code_1655790684255&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { combineReducers } from &quot;redux&quot;;
import counterReducer from &quot;./counter&quot;;

const rootReducer = combineReducers({
    counterReducer
})

export default rootReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;index.js&lt;/h3&gt;
&lt;pre id=&quot;code_1655790714118&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules/reducer';

const store = createStore(rootReducer);
console.log(store.getState());

ReactDOM.render(
  &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/Provider&amp;gt;,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;App.js&lt;/h3&gt;
&lt;pre id=&quot;code_1655790739255&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './App.css';
import CounterContainer from './container/CounterContainer';

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;CounterContainer/&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;component/Counter.jsx&lt;/h3&gt;
&lt;pre id=&quot;code_1655790754843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';

function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
  const onChange = e =&amp;gt; {
    // e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다.
    onSetDiff(parseInt(e.target.value, 10));
  };
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{number}&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;input type=&quot;number&quot; value={diff} min=&quot;1&quot; onChange={onChange} /&amp;gt;
        &amp;lt;button onClick={onIncrease}&amp;gt;+&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={onDecrease}&amp;gt;-&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Counter;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;container/CounterContainer.jsx&lt;/h3&gt;
&lt;pre id=&quot;code_1655790780288&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../component/Counter';
import { increase, decrease, setDiff } from '../modules/counter';

function CounterContainer() {
  // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다.
  // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다.
  const { number, diff } = useSelector(state =&amp;gt; ({
    number: state.counterReducer.number,
    diff: state.counterReducer.diff
  }));

  // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () =&amp;gt; dispatch(increase());
  const onDecrease = () =&amp;gt; dispatch(decrease());
  const onSetDiff = (diff) =&amp;gt; dispatch(setDiff(diff));

  return (
    &amp;lt;Counter
      // 상태와
      number={number}
      diff={diff}
      // 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onSetDiff={onSetDiff}
    /&amp;gt;
  );
}

export default CounterContainer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 코드 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modules/counter.js를 먼저 보겠습니다. 여기에서는 액션과 액션 생성함수, 리듀서를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운터에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;입력받는 숫자가 diff&lt;/b&gt;&lt;/span&gt;이고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;number는 현재 state&lt;/b&gt;&lt;/span&gt;이며 상태가 변할 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 number가 현재 state라는 말에 대해 이해가 가지 않는다면 전 글을 읽고 오시길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/243?category=1044606&quot;&gt;https://narup.tistory.com/243?category=1044606&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1655792178828&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[React] Redux 사용하기(1) - 개념과 흐름&quot; data-og-description=&quot;1. 개요 React 기반으로 웹 프로젝트를 개발할때, 컴포넌트의 상태 관리를 해야할 경우가 생기는데, 이때 사용하는 것이 Redux입니다. 리액트의 꽃이라고 할 수 있고, 처음 접하는 사람에게 굉장히 &quot; data-og-host=&quot;narup.tistory.com&quot; data-og-source-url=&quot;https://narup.tistory.com/243?category=1044606&quot; data-og-url=&quot;https://narup.tistory.com/243&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ogFkN/hyOQoJZ7GL/IKIjpHKD0KOCm2exU1Oe90/img.gif?width=260&amp;amp;height=266&amp;amp;face=0_0_260_266,https://scrap.kakaocdn.net/dn/dPEjaS/hyOQnYDF3W/jHFqF9QBPKaa5CzV8U7uNK/img.gif?width=260&amp;amp;height=266&amp;amp;face=0_0_260_266,https://scrap.kakaocdn.net/dn/cI3mV6/hyOOuSDhEM/l7Wui6qKQ86KbQAjU8AF6k/img.png?width=1200&amp;amp;height=564&amp;amp;face=0_0_1200_564&quot;&gt;&lt;a href=&quot;https://narup.tistory.com/243?category=1044606&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://narup.tistory.com/243?category=1044606&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ogFkN/hyOQoJZ7GL/IKIjpHKD0KOCm2exU1Oe90/img.gif?width=260&amp;amp;height=266&amp;amp;face=0_0_260_266,https://scrap.kakaocdn.net/dn/dPEjaS/hyOQnYDF3W/jHFqF9QBPKaa5CzV8U7uNK/img.gif?width=260&amp;amp;height=266&amp;amp;face=0_0_260_266,https://scrap.kakaocdn.net/dn/cI3mV6/hyOOuSDhEM/l7Wui6qKQ86KbQAjU8AF6k/img.png?width=1200&amp;amp;height=564&amp;amp;face=0_0_1200_564');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[React] Redux 사용하기(1) - 개념과 흐름&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 개요 React 기반으로 웹 프로젝트를 개발할때, 컴포넌트의 상태 관리를 해야할 경우가 생기는데, 이때 사용하는 것이 Redux입니다. 리액트의 꽃이라고 할 수 있고, 처음 접하는 사람에게 굉장히&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;narup.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 이 counter라는 상태 관리에서는 총 2가지의 state를 관리합니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 값: number&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 값: diff&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 state는 리듀서를 통해 store에 저장되고, 저희 개발자는 store에서 이 state를 가져다가 쓸 수 있는데, 자세한 것은 아래에서 후술하겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) increase(), 카운터 증가를 호출할 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 액션이 발생되었을 경우를 예로 들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+버튼을 눌렀을 경우, 컴포넌트 측(CounterContainer.jsx)의 dispatch(increase())가 호출되면서 위 코드의 액션 생성 함수가 호출될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 액션 생성함수는 타입이 INCREASE라는 액션을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655791047631&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const increase = () =&amp;gt; ({ type: INCREASE });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 액션 생성함수가 값을 반환하면, 리듀서로 지정한 counterReducer(state, action)가 호출되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 switch-case에 따라&lt;/p&gt;
&lt;pre id=&quot;code_1655789876581&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return {
  ...state,
  number: state.number + state.diff
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...state와 number가 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...state는 뭐지 싶습니다. ...이 들어간 이 연산자는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;스프레드 연산자&lt;/b&gt;&lt;/span&gt;로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;현재 state의 값&lt;/b&gt;&lt;/span&gt;을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;새롭게 복사&lt;/b&gt;&lt;/span&gt;해서 반환합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZIYtE/btrFkZn4SNE/YDM9nUKAVFKKjXsUghzLc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZIYtE/btrFkZn4SNE/YDM9nUKAVFKKjXsUghzLc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZIYtE/btrFkZn4SNE/YDM9nUKAVFKKjXsUghzLc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZIYtE%2FbtrFkZn4SNE%2FYDM9nUKAVFKKjXsUghzLc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;268&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;불변성을 유지&lt;/b&gt;&lt;/span&gt;해야 하기 때문에 무조건! 위와 같이 스프레드 연산자를 사용하도록 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 카운터 리덕스에서는 총 2가지 어떤 상태를 관리한다고 했죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number와 diff라는 2가지의 상태를 관리한다고 1번에서 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...state는 number와 diff를 그대로 복사해서 return을 해버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 추가적으로 number가 현재 state의 number와 state의 diff값을 서로 더한 값을 반환한다고 되어있으니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 현재 number가 6이고, diff가 3인 초기 상태였을 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리듀서를 통과하면 현재 number는 9가되고, diff는 3인 되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counterReducer가 return하면 이 리턴된 값을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;스토어(Store)&lt;/b&gt;&lt;/span&gt;에 저장해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modules/reducer.js에서 combineReducers()함수는 여러 가지 리듀서를 한 가지로 합쳐주는 함수인데,&lt;/p&gt;
&lt;pre id=&quot;code_1655791566687&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const rootReducer = combineReducers({
    counterReducer, //todoReducer, toggleReducer 등등
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 프로젝트에서 한 가지 리듀서만 사용하지는 않기 때문에 이처럼 rootReducer라고 리듀서를 따로 지정해서 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 스토어에 리듀서를 저장하기 위해&lt;/p&gt;
&lt;pre id=&quot;code_1655791624318&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const store = createStore(rootReducer);

ReactDOM.render(
  &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/Provider&amp;gt;,
  document.getElementById('root')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 createStore()함수에 rootReducer를 담아 store 객체를 만들고, Provider로 App 컴포넌트를 감싸서 Store를 등록해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 App 컴포넌트 하위에 있는 컴포넌트 들은 프로바이더에 등록된 스토어에서 저장된 값들을 자유롭게 사용할 수 있게 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655791726131&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { number, diff } = useSelector(state =&amp;gt; ({
    number: state.counterReducer.number,
    diff: state.counterReducer.diff
  }));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;container/CounterContainer.jsx에서처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;값을 읽을때는 useSelector()를 통해 읽으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655791764655&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () =&amp;gt; dispatch(increase());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container/CounterContainer.jsx에서처럼 useDispatch()를 통해 액션 함수를 호출해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) setDiff(), 입력 값을 변경할 경우&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQxZN/btrFooAl26r/IcokYxac9ycyRdblPxm6LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQxZN/btrFooAl26r/IcokYxac9ycyRdblPxm6LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQxZN/btrFooAl26r/IcokYxac9ycyRdblPxm6LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQxZN%2FbtrFooAl26r%2FIcokYxac9ycyRdblPxm6LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;255&quot; height=&quot;112&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 더 예를 들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 number는 19이고, diff를 5로 변경하려고 입력했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 Input 컴포넌트에서 숫자를 변경할 경우 디스패치를 통해 setDiff()가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번과 같이 액션 생성함수를 통해 액션 값이 반환되는데,&lt;/p&gt;
&lt;pre id=&quot;code_1655790547599&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const setDiff = (diff) =&amp;gt; ({ type: SET_DIFF, diff });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이한 점은 인자로 diff값을 받아서 액션에 diff값을 담아줍니다. 어떤 값으로 변경할지 지정해주는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 리듀서를 통해&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655790594980&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;case SET_DIFF:
    return {
      ...state,
      diff: action.diff
    };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...state, 즉 스프레드 연산자를 통해 현재의 상태(number와 diff)를 반환하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;action 함수에서 담았던 diff를 diff: action.diff로 초기화해서 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 number가 19이고 diff가 1인 상태였다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number가 19이고 diff가 5인 상태로 값을 변경해서 반환해주는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번과 같이 리듀서를 통해서 반환된 값은 스토어(Store)에 저장되며, 각 컴포넌트에서 useSelector()함수를 통해 값을 가져올 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 소스 코드입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kimdongjang/learn-redux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kimdongjang/learn-redux&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>reducer</category>
      <category>카운터</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/244</guid>
      <comments>https://narup.tistory.com/244#entry244comment</comments>
      <pubDate>Tue, 21 Jun 2022 15:13:27 +0900</pubDate>
    </item>
    <item>
      <title>[React] Redux 사용하기(1) - 개념과 흐름</title>
      <link>https://narup.tistory.com/243</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 기반으로 웹 프로젝트를 개발할때, 컴포넌트의 상태 관리를 해야할 경우가 생기는데, 이때 사용하는 것이 Redux입니다. 리액트의 꽃이라고 할 수 있고, 처음 접하는 사람에게 굉장히 러닝 커브가 높은 부분입니다.(저도 그랬습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;u&gt;(상태 관리 하는 이유 : 리렌더링에 의한 자원 소모를 줄이기 위해서)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Redux가 어렵기도 해서 사용하기 쉽게 라이브러리들이 개발이 되어서 &lt;span style=&quot;background-color: #ffffff; color: #202124;&quot;&gt;MobX, xstate, recoil, zustand 등 여러가지 라이브러리가 등장하고, Redux를 굳이 사용하지 않아도 위 라이브러리로 간편하게 상태 관리를 할 수 있게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 리액트 프로그래밍을 시작하는 사람으로서 어떤 맥락으로 상태관리가 이루어지는지 파악하는 것은 중요하다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 중요 키워드 및 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;액션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사용자에 의해서 상태의 변화(ex: 버튼을 눌렀을 경우)가 생길때 이것을 액션이 발생되었다고 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;타입&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;value&lt;/b&gt;&lt;/span&gt;로 이루어져있습니다. type은 무조건 들어가야하며 value는 자유롭게 작성해도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655775199026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  type: &quot;PUSH_BUTTON&quot;
  button: &quot;pushed&quot;
}

// 혹은

{
  type: &quot;PUSH_BUTTON&quot;
  isToggle: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;액션 생성함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;액션을 만드는 함수입니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;파라미터(data)&lt;/b&gt;&lt;/span&gt;를 받아와서 액션 객체 형태로 리턴해서, 사용자가 사전에 지정한 액션을 호출할 수 있게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655775381403&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function pushButton(data) {
  return {
    type: &quot;PUSH_BUTTON&quot;,
    data
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리듀서&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;변화를 일으키는 함수입니다. useReducer()와 똑같은 형태를 가지고 있으며, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;현재의 상태(state)&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;전달받은 액션(action)&lt;/b&gt;&lt;/span&gt;을 파라미터로 받고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;새로운 상태의 값(return +-1, 증분된 값)&lt;/b&gt;&lt;/span&gt;을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655775510673&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 카운터 기능을 예로 들었을 경우입니다.
function counter(state, action) {
  switch (action.type) {
    case 'INCREASE':
      return state + 1;
    case 'DECREASE':
      return state - 1;
    default:
      return state;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액션에서 지정한 type을 switch-case로 확인하고, 전달받은 state 파라미터에 +1또는 -1를 추가해서 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;u&gt;현재의 state&lt;/u&gt;와 &lt;u&gt;반환되는 state&lt;/u&gt;가 가장 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래 스토어(store)에 대해 확인하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스토어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리덕스에서는 단 하나의 스토어를 만들게 되는데, 이 스토어 내부에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;현재의 앱 상태(state)&lt;/b&gt;&lt;/span&gt;와 리듀서(reducer)가 들어가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPxy7K/btrFjz9S5hX/diDjpm6akQzntSdKH5gPr1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPxy7K/btrFjz9S5hX/diDjpm6akQzntSdKH5gPr1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPxy7K/btrFjz9S5hX/diDjpm6akQzntSdKH5gPr1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bPxy7K/btrFjz9S5hX/diDjpm6akQzntSdKH5gPr1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;260&quot; height=&quot;266&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 카운터를 만들었다고 가정하고, 리듀서 쪽에서 정의한 state를 다시 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운터로 인해 숫자가 늘어나고 줄어드는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;텍스트 박스(컴포넌트)&lt;/b&gt;&lt;/span&gt;가 있으며, 이 상태를 저희는 redux를 이용해 상태 관리를 하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 컴포넌트를 redux로 사용할 경우, 아래와 같이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;useSelector()&lt;/b&gt;&lt;/span&gt;라는 함수를 이용해서 스토어(store)에 저장된 현재의 상태(state)를 가져와서 초기화를 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655776341700&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const count = useSelector((state: RootState) =&amp;gt; state.counter.count);
  
  return (
  	&amp;lt;div&amp;gt;{count}&amp;lt;/div&amp;gt;
  )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 +1버튼을 누르거나 -1버튼을 누를때, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;디스패치(dispatch)&lt;/b&gt;&lt;/span&gt;라는 것을 통해 액션 생성함수를 호출하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;디스패치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;디스패치는 스토어(store)에서 제공하는 함수중의 하나로 액션을 발생시켜주는 도구입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655776558001&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; const dispatch = useDispatch(); // 디스패치 함수를 가져옵니다

  // 각 액션들을 디스패치하는 함수들을 만들어줍니다
  const onIncrease = () =&amp;gt; {
    dispatch(increase());
  };

  const onDecrease = () =&amp;gt; {
    dispatch(decrease());
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 디스패치 함수 안에 &lt;u&gt;액션 생성함수를 지정&lt;/u&gt;하고, onIncrease()(&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;액션 생성함수)를 호출&lt;/b&gt;&lt;/span&gt;하면, 액션 생성함수로 인해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;액션이 생성&lt;/b&gt;&lt;/span&gt;되고, 그 다음 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;리듀서가 호출&lt;/b&gt;&lt;/span&gt;되며 현재의 상태(텍스트 박스의 숫자가 3이면 3, 혹은 19면 19)에 리듀서에서 지정한 return state +1이 반환되며, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이 값이 스토어(store)에 저장&lt;/b&gt;&lt;/span&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelector()는 스토어에 있는 값을 가져다가 텍스트 박스를 갱신시켜주게 되고, 결과적으로 +1이 된 값이 사용자에게 보여 지게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDwzXJ/btrFnBFQZBn/oKg3nm46c8fe7PHpMIcy60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDwzXJ/btrFnBFQZBn/oKg3nm46c8fe7PHpMIcy60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDwzXJ/btrFnBFQZBn/oKg3nm46c8fe7PHpMIcy60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDwzXJ%2FbtrFnBFQZBn%2FoKg3nm46c8fe7PHpMIcy60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;564&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;흐름!&lt;/b&gt;&lt;/span&gt;이 가장 중요하고 이 흐름을 이해하셨으면 리덕스를 다 이해하신 거라고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래라면 전체 코드를 딱 적고, 그 코드에 대한 설명을 하는게 편하긴 하지만, 리덕스 같은 경우는 전반적인 이해와 전체적인 흐름에 대한 이해가 필수적으로 들어가야 했기 때문에 이 글에 대해서는 흐름만 이해할 수 있도록 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*위 그림에서 Middleware는 크게 중요하지 않기 때문에 그냥 넘어가셔도 무방합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 쪽에서 작성한 전체코드는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1655776644335&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const CounterContainer = (props: CounterContainerProps) =&amp;gt; {
  // 상태를 조회합니다. 상태를 조회 할 때에는 state 의 타입을 RootState 로 지정해야합니다.
  const count = useSelector((state: RootState) =&amp;gt; state.counter.count);
  const dispatch = useDispatch(); // 디스패치 함수를 가져옵니다

  // 각 액션들을 디스패치하는 함수들을 만들어줍니다
  const onIncrease = () =&amp;gt; {
    dispatch(increase());
  };

  const onDecrease = () =&amp;gt; {
    dispatch(decrease());
  };

  const onIncreaseBy = (diff: number) =&amp;gt; {
    dispatch(increaseBy(diff));
  };

  return (
    &amp;lt;Counter
      count={count}
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onIncreaseBy={onIncreaseBy}
    /&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Redux를 사용해서 상태관리를 하는 부분에 대한 자세한 코드는 다음 글에서 작성하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/React 심화</category>
      <category>react</category>
      <category>Redux</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/243</guid>
      <comments>https://narup.tistory.com/243#entry243comment</comments>
      <pubDate>Tue, 21 Jun 2022 11:06:39 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Proxy Axios 설정</title>
      <link>https://narup.tistory.com/242</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios를 사용해 Next.js서버가 아닌 다른 도메인으로 url을 요청할 때가 있는데, 이때 프록시 설정을 하지 않으면 CORS 오류로 인해 호출이 되지 않는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;React에서 package.json에 프록시를 명시한 것과 다르게 Next.js에서 Proxy는 조금 다른 방법으로 설정을 해야만 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;2. 코드&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios 호출&lt;/p&gt;
&lt;pre id=&quot;code_1655689613411&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    async function get() {
      try {
        const result: AxiosResponse&amp;lt;any, any&amp;gt; = await axios.post(&quot;/redis/visit&quot;);
        console.log(result.data)
        if (result) setVisitor(result.data)
      }
      catch (e) {

      }
    }
    get();
  }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 페이지가 로딩될 때 api가 호출되게끔 하기 위해 useEffect에 동기적으로 api를 호출하는 코드를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.config.js&lt;/p&gt;
&lt;pre id=&quot;code_1655689731594&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = (phase, { defaultConfig }) =&amp;gt; {
  const rewrites = () =&amp;gt; {
    return [
      {
        source: '/redis/:path*',
        destination: &quot;http://172.12.0.1:4949/redis/:path*&quot;,
      },
    ];
  }
  
  return { rewrites }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module.exports에 const rewrites 함수를 정의하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴하는 배열의 source에는 axios에서 호출하는 path 부분만 작성하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;destination에는 실제로 접근해야할 URL과 포트를 포함해서 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.js에서 초기에 설정해야할 값들이 많은데, 어떤 곳에서는 redirect를 써야한다, 함수를 호출하거나 변수로 먼저 선언한다던가, 다 시도를 해보아도 되지가 않았지만, 위 방법을 사용하니 잘 작동했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.edmondchuc.com/blog/next-js-development-proxy-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.edmondchuc.com/blog/next-js-development-proxy-server&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/Next.js</category>
      <category>axios</category>
      <category>Next.js</category>
      <category>Proxy</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/242</guid>
      <comments>https://narup.tistory.com/242#entry242comment</comments>
      <pubDate>Mon, 20 Jun 2022 10:52:29 +0900</pubDate>
    </item>
    <item>
      <title>[Nginx] Nginx Proxy Manager 설치</title>
      <link>https://narup.tistory.com/241</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx Proxy Manager를 재설치해야 하는 일이 빈번히 있어서 설치과정을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx Proxy Manager는 리버스 프록시, 리다이렉션, 보안 인증, SSL 인증서 관리를 GUI로 할 수 있게 도와주는 툴입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가이드 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nginxproxymanager.com/guide/#features&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nginxproxymanager.com/guide/#features&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655370074829&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Nginx Proxy Manager&quot; data-og-description=&quot;Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt&quot; data-og-host=&quot;nginxproxymanager.com&quot; data-og-source-url=&quot;https://nginxproxymanager.com/guide/#features&quot; data-og-url=&quot;https://nginxproxymanager.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvjgjr/hyOMSk37hD/aKJGf7FexOIakBar0L4Sok/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/BxWjz/hyOMKm3Llb/RNOYKjJagAbzUhTGiWNBR0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192&quot;&gt;&lt;a href=&quot;https://nginxproxymanager.com/guide/#features&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nginxproxymanager.com/guide/#features&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvjgjr/hyOMSk37hD/aKJGf7FexOIakBar0L4Sok/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/BxWjz/hyOMKm3Llb/RNOYKjJagAbzUhTGiWNBR0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Nginx Proxy Manager&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nginxproxymanager.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. docker-compose.yml&lt;/h2&gt;
&lt;pre id=&quot;code_1655369990650&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실행&lt;/h2&gt;
&lt;pre id=&quot;code_1655370002617&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIVHbd/btrEYImvRSH/bBuKAmam4QcgBm3sPGasb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIVHbd/btrEYImvRSH/bBuKAmam4QcgBm3sPGasb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIVHbd/btrEYImvRSH/bBuKAmam4QcgBm3sPGasb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIVHbd%2FbtrEYImvRSH%2FbBuKAmam4QcgBm3sPGasb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;566&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈로 실행하면 위와 같이 관련된 이미지를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZPWsi/btrEUPfCyUh/AwglRf6IzdQd2dOkaNQ7J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZPWsi/btrEUPfCyUh/AwglRf6IzdQd2dOkaNQ7J1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZPWsi/btrEUPfCyUh/AwglRf6IzdQd2dOkaNQ7J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZPWsi%2FbtrEUPfCyUh%2FAwglRf6IzdQd2dOkaNQ7J1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;327&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 매니저가 설치가 되면 81포트로 접속해줍니다(localhost:81)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 로그인 UI가 나오는데, 초기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;email: admin@example.com&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;password: changeme&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 설정이 되어 있어서 위 계정으로 로그인 하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 하시면 계정 변경하라는 UI가 나오는데, 취향에 맞게 변경해주시면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SgBrU/btrEZNtDHgO/JotwGQqhpyWgPs84O5sfl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SgBrU/btrEZNtDHgO/JotwGQqhpyWgPs84O5sfl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SgBrU/btrEZNtDHgO/JotwGQqhpyWgPs84O5sfl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSgBrU%2FbtrEZNtDHgO%2FJotwGQqhpyWgPs84O5sfl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1207&quot; height=&quot;464&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 프록시를 관리할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Nginx</category>
      <category>nginx</category>
      <category>NginxProxyManager</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/241</guid>
      <comments>https://narup.tistory.com/241#entry241comment</comments>
      <pubDate>Fri, 17 Jun 2022 10:26:17 +0900</pubDate>
    </item>
    <item>
      <title>[Nginx] SSL 설정(HTTPS 적용)</title>
      <link>https://narup.tistory.com/240</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQRjc/btrEJw8gVqk/Qsr6rIsnKhjYY7mzK6Keuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQRjc/btrEJw8gVqk/Qsr6rIsnKhjYY7mzK6Keuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQRjc/btrEJw8gVqk/Qsr6rIsnKhjYY7mzK6Keuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQRjc%2FbtrEJw8gVqk%2FQsr6rIsnKhjYY7mzK6Keuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;178&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 웹 사이트를 HTTP로 운영하고 있다가, 사용자의 정보같은 민감한 정보를 사용하게 될 경우에는 SSL 인증서를 사용한 보안처리를 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹서버에 SSL 인증서를 사용해 웹사이트를 HTTPS로 열 수 있게끔 Nginx 프록시 서버에 SSL 인증서를 적용하는 방법을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 준비물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;도메인, SSL 인증서(chain key, private key)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인은 유료로도 발급할 수 있지만, 저는 개발용이기 때문에 무료로 도메인을 발급해주는 no-ip라는 사이트를 통해서 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.noip.com/login?ref_url=console&quot;&gt;https://www.noip.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655196569127&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Sign In - No-IP&quot; data-og-description=&quot;&amp;times; Please select your Language To view website content in your Language, please select an option below.&quot; data-og-host=&quot;www.noip.com&quot; data-og-source-url=&quot;https://www.noip.com/login?ref_url=console&quot; data-og-url=&quot;https://www.noip.com/login?ref_url=console&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.noip.com/login?ref_url=console&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.noip.com/login?ref_url=console&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Sign In - No-IP&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;times; Please select your Language To view website content in your Language, please select an option below.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.noip.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 SSL 인증서 발급도 유료가 아닌 무료로 할 수 있는데, Lets Encrypt라는 인증기관을 통해서 certbot 도구로 SSL인증서를 발급받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://letsencrypt.org/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://letsencrypt.org/ko/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655196820096&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Let's Encrypt - 무료 SSL/TLS 인증서&quot; data-og-description=&quot;&quot; data-og-host=&quot;letsencrypt.org&quot; data-og-source-url=&quot;https://letsencrypt.org/ko/&quot; data-og-url=&quot;https://letsencrypt.org/ko/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://letsencrypt.org/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://letsencrypt.org/ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Let's Encrypt - 무료 SSL/TLS 인증서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;letsencrypt.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://certbot.eff.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://certbot.eff.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655196838180&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Certbot&quot; data-og-description=&quot;Tagline&quot; data-og-host=&quot;certbot.eff.org&quot; data-og-source-url=&quot;https://certbot.eff.org/&quot; data-og-url=&quot;https://certbot-prod.eff.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://certbot.eff.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://certbot.eff.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Certbot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Tagline&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;certbot.eff.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. nginx.conf&lt;/h2&gt;
&lt;pre id=&quot;code_1655196320961&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;

    # 443 포트로 접근시 ssl을 적용한 뒤 3000포트로 요청을 전달해주도록 하는 설정.
    server {
            server_name idu-market.shop;

            location / {
                    proxy_pass http://127.0.0.1:3000;
            }

            listen 443 ssl; # managed by Certbot
            ssl_certificate /etc/letsencrypt/live/idu-market.shop/fullchain.pem; # managed by Cert&amp;gt;
            ssl_certificate_key /etc/letsencrypt/live/idu-market.shop/privkey.pem; # managed by Ce&amp;gt;

            include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
            ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    }

    # 80 포트로 접근시 443 포트로 리다이렉트 시켜주는 설정
    server {
             return 301 https://$host$request_uri;
             # managed by Certbot


            listen 80;
            server_name idu-market.shop;
            return 404; # managed by Certbot
    }

    log_format  main  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
                      '$status $body_bytes_sent &quot;$http_referer&quot; '
                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';
    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) server 80 블록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx를 사용할 때 기존에 80포트(http)로 접근되는 요청을 proxy_pass에 특정 웹서버(:3000)로 연결시켰다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL을 사용할 때는 443포트(https)로 연결시키도록&lt;/p&gt;
&lt;pre id=&quot;code_1655253920429&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; return 301 https://$host$request_uri&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 작성해주는 것이 포인트입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) server 443 블록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;443포트는 https 요청에 연결되는 포트로, proxy_pass에 특정 웹서버(:3000)로 리다이렉트 해주는 부분을 명시해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 포인트는 ssl certificate 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 준비한 도메인과 연결된 SSL 인증서의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;chain key, private key를 nginx가 설치된 서버의 특정 폴더에 저장&lt;/b&gt;&lt;/span&gt;하고,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655254140449&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        ssl_certificate /경로/fullchain.pem;
        ssl_certificate_key /경로/privkey.pem;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 키가 위치한 폴더의 경로를 작성해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;443으로 들어오는 요청을 SSL 인증서로 암호화 하겠다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1658800213611&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Nginx를 이용하여 https 적용하는 법&quot; data-og-description=&quot;Nginx를 이용하여 https 적용하는 법. GitHub Gist: instantly share code, notes, and snippets.&quot; data-og-host=&quot;gist.github.com&quot; data-og-source-url=&quot;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&quot; data-og-url=&quot;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jR0tA/hyPc6WZVJ9/7itVaYFIRaPciXq6xI7p71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gist.github.com/woorim960/dda0bc85599f61a025bb8ac471dfaf7a&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jR0tA/hyPc6WZVJ9/7itVaYFIRaPciXq6xI7p71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Nginx를 이용하여 https 적용하는 법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nginx를 이용하여 https 적용하는 법. GitHub Gist: instantly share code, notes, and snippets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gist.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Nginx</category>
      <category>https</category>
      <category>nginx</category>
      <category>SSL</category>
      <category>ssl인증서</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/240</guid>
      <comments>https://narup.tistory.com/240#entry240comment</comments>
      <pubDate>Wed, 15 Jun 2022 08:58:32 +0900</pubDate>
    </item>
    <item>
      <title>[OpenSSL] SSL 인증서 발급 방법 요약</title>
      <link>https://narup.tistory.com/239</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버에 https 적용을 위한 SSL 인증서를 발급 받는 방법을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. openssl for windows 다운&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code.google.com/archive/p/openssl-for-windows/downloads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://code.google.com/archive/p/openssl-for-windows/downloads&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654757152328&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Google Code Archive - Long-term storage for Google Code Project Hosting.&quot; data-og-description=&quot;&quot; data-og-host=&quot;code.google.com&quot; data-og-source-url=&quot;https://code.google.com/archive/p/openssl-for-windows/downloads&quot; data-og-url=&quot;https://code.google.com/archive/p/openssl-for-windows/downloads&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://code.google.com/archive/p/openssl-for-windows/downloads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://code.google.com/archive/p/openssl-for-windows/downloads&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Google Code Archive - Long-term storage for Google Code Project Hosting.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;code.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUZi2Y/btrEjNvISgn/8gJVEFVEu7Ik24w2tw0tz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUZi2Y/btrEjNvISgn/8gJVEFVEu7Ik24w2tw0tz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUZi2Y/btrEjNvISgn/8gJVEFVEu7Ik24w2tw0tz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUZi2Y%2FbtrEjNvISgn%2F8gJVEFVEu7Ik24w2tw0tz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;314&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우에서 SSL 인증서 생성을 위해 위의 링크에 접속해서 프로그램을 다운받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k, e, d와 같은 라벨이 있는데 저는 e로 다운받고 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 환경변수 설정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZ7uFY/btrEmPzlcTc/VeCF1sxqb7ruVnILd27kEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZ7uFY/btrEmPzlcTc/VeCF1sxqb7ruVnILd27kEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZ7uFY/btrEmPzlcTc/VeCF1sxqb7ruVnILd27kEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZ7uFY%2FbtrEmPzlcTc%2FVeCF1sxqb7ruVnILd27kEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1116&quot; height=&quot;682&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번에서 압축 파일을 받고 푼 다음, /bin 경로에 있는 openssl.exe를 시스템 환경변수에 등록해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. private.key 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH7qcS/btrEnRQGcxw/5bpjojmpnMBIxLSXlXAeNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH7qcS/btrEnRQGcxw/5bpjojmpnMBIxLSXlXAeNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH7qcS/btrEnRQGcxw/5bpjojmpnMBIxLSXlXAeNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH7qcS%2FbtrEnRQGcxw%2F5bpjojmpnMBIxLSXlXAeNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;154&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rem AES256으로 암호화된 2048bit 길이의 private key를 생성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1654757343751&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl genrsa -aes256 -out private.key 2048&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용할 패스워드를 입력해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Enter pass phrase for private.key: [패스워드 입력]&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Verifying - Enter pass phrase for private.key: [패스워드 다시 입력]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. public.key 생성&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1654757513799&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl rsa -in private.key -pubout -out public.key&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4r8R9/btrEnRQGjwf/EGeQuf2qDoPD8ctSKRkmE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4r8R9/btrEnRQGjwf/EGeQuf2qDoPD8ctSKRkmE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4r8R9/btrEnRQGjwf/EGeQuf2qDoPD8ctSKRkmE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4r8R9%2FbtrEnRQGjwf%2FEGeQuf2qDoPD8ctSKRkmE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;58&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;rem private.key를 통해 public.key를 생성해줍니다. 서로 매칭되는 키이기 때문에 4번에서 사용한 패스워드를 똑같이 입력해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Enter pass phrase for private.key:[패스워드 입력]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 인증 서명 요청서(CSR) 생성&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTb1Bx/btrEkqNMIvB/KPhxKIoSRqzFna1iGTDySK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTb1Bx/btrEkqNMIvB/KPhxKIoSRqzFna1iGTDySK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTb1Bx/btrEkqNMIvB/KPhxKIoSRqzFna1iGTDySK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTb1Bx%2FbtrEkqNMIvB%2FKPhxKIoSRqzFna1iGTDySK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;344&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1654757549767&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl req -config ./openssl.cnf -new -key private.key -out private.csr&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;인증 서명 요청서(CSR)란, 인증기관(CA)을 통해 인증서를 발급받기 위한 정보를 담고 있는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;신청서&lt;/b&gt;&lt;/span&gt;입니다. 인증기관에게 인증서를 받기 위해 서류를 작성하는 신청서라고 생각하시면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;국가, 도시, 조직 등의 정보인데 개발/배포 용도에 따라 작성해주시면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Country&amp;nbsp;Name&amp;nbsp;(2&amp;nbsp;letter&amp;nbsp;code)&amp;nbsp;[AU]:KR &lt;br /&gt;State or Province Name (full name) [Some-State]:&lt;br /&gt;Locality Name (eg, city) []:&lt;br /&gt;Organization Name (eg, company) [Internet Widgits Pty Ltd]:&lt;br /&gt;Organizational Unit Name (eg, section) []:&lt;br /&gt;Common Name (eg, YOUR name) []:&lt;br /&gt;Email Address []:&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;rem에 대한 추가정보인데 작성할 필요는 없습니다.&lt;br /&gt;Please&amp;nbsp;enter&amp;nbsp;the&amp;nbsp;following&amp;nbsp;'extra'&amp;nbsp;attributes &lt;br /&gt;to&amp;nbsp;be&amp;nbsp;sent&amp;nbsp;with&amp;nbsp;your&amp;nbsp;certificate&amp;nbsp;request &lt;br /&gt;A&amp;nbsp;challenge&amp;nbsp;password&amp;nbsp;[]: &lt;br /&gt;An&amp;nbsp;optional&amp;nbsp;company&amp;nbsp;name&amp;nbsp;[]:&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;7. 자체 인증 기관(CA) 키 생성&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqPNG2/btrEkHuEYir/Zjucgw8bkg9dFR8fvJ5Qt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqPNG2/btrEkHuEYir/Zjucgw8bkg9dFR8fvJ5Qt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqPNG2/btrEkHuEYir/Zjucgw8bkg9dFR8fvJ5Qt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqPNG2%2FbtrEkHuEYir%2FZjucgw8bkg9dFR8fvJ5Qt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;152&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1654757733503&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl genrsa -aes256 -out root.ca.key 2048&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enter pass phrase for root.ca.key: [패스워드 입력]&lt;br /&gt;Verifying - Enter pass phrase for root.ca.key: [패스워드 다시 입력]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 자체 인증기관의 CSR 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvFNVy/btrEnSISf7l/Z39N1ISpvbmBA5gvsBQB7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvFNVy/btrEnSISf7l/Z39N1ISpvbmBA5gvsBQB7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvFNVy/btrEnSISf7l/Z39N1ISpvbmBA5gvsBQB7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvFNVy%2FbtrEnSISf7l%2FZ39N1ISpvbmBA5gvsBQB7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;278&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1654757884043&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl req -config ./openssl.cnf -x509 -new -nodes -key root.ca.key -days 3650 -out root.ca.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6번과 마찬가지로 개발/배포 용도에 따라 적당히 작성해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Country&amp;nbsp;Name&amp;nbsp;(2&amp;nbsp;letter&amp;nbsp;code)&amp;nbsp;[AU]:KR &lt;br /&gt;State&amp;nbsp;or&amp;nbsp;Province&amp;nbsp;Name&amp;nbsp;(full&amp;nbsp;name)&amp;nbsp;[Some-State]: &lt;br /&gt;Locality&amp;nbsp;Name&amp;nbsp;(eg,&amp;nbsp;city)&amp;nbsp;[]: &lt;br /&gt;Organization&amp;nbsp;Name&amp;nbsp;(eg,&amp;nbsp;company)&amp;nbsp;[Internet&amp;nbsp;Widgits&amp;nbsp;Pty&amp;nbsp;Ltd]: &lt;br /&gt;Organizational&amp;nbsp;Unit&amp;nbsp;Name&amp;nbsp;(eg,&amp;nbsp;section)&amp;nbsp;[]: &lt;br /&gt;Common&amp;nbsp;Name&amp;nbsp;(eg,&amp;nbsp;YOUR&amp;nbsp;name)&amp;nbsp;[]: &lt;br /&gt;Email&amp;nbsp;Address&amp;nbsp;[]:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 자체 인증기관을 통해 인증서(CRT) 발급&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Crfmx/btrElcPo8LL/JNKMs7zzoUsPef4MfjsL8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Crfmx/btrElcPo8LL/JNKMs7zzoUsPef4MfjsL8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Crfmx/btrElcPo8LL/JNKMs7zzoUsPef4MfjsL8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCrfmx%2FbtrElcPo8LL%2FJNKMs7zzoUsPef4MfjsL8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;140&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1654758783531&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openssl x509 -req -in private.csr -CA root.ca.pem -CAkey root.ca.key -CAcreateserial -out private.crt -days 3650&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7번에서 사용한 rem CA키의 패스워드를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enter pass phrase for root.ca.key: [패스워드 입력]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 최종 산출물&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uwqjS/btrEnSIVOdy/xCyvG0Rl79WYqK5Bdiy8C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uwqjS/btrEnSIVOdy/xCyvG0Rl79WYqK5Bdiy8C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uwqjS/btrEnSIVOdy/xCyvG0Rl79WYqK5Bdiy8C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuwqjS%2FbtrEnSIVOdy%2FxCyvG0Rl79WYqK5Bdiy8C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;160&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Nginx</category>
      <category>OpenSSL</category>
      <category>SSL</category>
      <category>ssl인증서</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/239</guid>
      <comments>https://narup.tistory.com/239#entry239comment</comments>
      <pubDate>Thu, 9 Jun 2022 16:15:23 +0900</pubDate>
    </item>
    <item>
      <title>[Nginx] 리버스 프록시(Reverse Proxy) 개념 및 사용법</title>
      <link>https://narup.tistory.com/238</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리버스 프록시&lt;/b&gt;란? 클라이언트 요청을 대신 받아 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;내부 서버로 전달&lt;/b&gt;&lt;/span&gt;해주는 것을 리버스 프록시(Reverse&amp;nbsp;Proxy)&amp;nbsp;라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 사실 프록시라는 개념이 낯설었는데요, 일단 프록시라는 개념부터 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프록시&lt;/b&gt;란 대리라는 의미로, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;정보를 대신 전달해주는 주체&lt;/b&gt;&lt;/span&gt;라고 생각하면 되는데, 만약 이 프록시 없이 웹 서버를 운영한다고 가정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost:3000&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하는 웹서버를 열어서 운영했을 때, 사용자가 갑자기 많아지거나, 웹서버가 그대로 노출되어 있기 때문에 보안적으로 위험성이 있겠죠? nginx를 사용하면 로드 밸런싱으로 부하를 줄여줄 수 있고, 분산 처리 또한 가능하며 웹서버의 SSL 인증도 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;사용자 -&amp;gt; nginx -&amp;gt; 웹서버&lt;/b&gt;&lt;/span&gt;로 구성해서 사용자의 요청을 nginx가 대신 웹서버로 전달해주도록 구성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8AN2H/btrDUuCAbFQ/Al3oK6Y7I7Yx8iPqgmKmM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8AN2H/btrDUuCAbFQ/Al3oK6Y7I7Yx8iPqgmKmM0/img.png&quot; data-alt=&quot;출처: https://velog.io/@chickenfondue/nginx-docker-compose%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EA%B5%AC%EC%84%B1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8AN2H/btrDUuCAbFQ/Al3oK6Y7I7Yx8iPqgmKmM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8AN2H%2FbtrDUuCAbFQ%2FAl3oK6Y7I7Yx8iPqgmKmM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;787&quot; height=&quot;693&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://velog.io/@chickenfondue/nginx-docker-compose%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EA%B5%AC%EC%84%B1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리버스 프록시의 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로드 밸런싱&lt;/b&gt; : Nginx는 클라이언트의 요청을 프록시 서버에 분산하기 위해 로드 밸런싱을 수행하여 성능, 확장성 및 신뢰성을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐싱&lt;/b&gt; : Nginx를 역방향 프록시로 사용하면 미리 렌더링된 버전의 페이지를 캐시하여 페이지 로드 시간을 단축할 수 있습니다. 이 기능은 프록시 서버의 응답에서 수신한 콘텐츠를 캐싱하고 이 콘텐츠를 사용하여 매번 동일한 콘텐츠를 프록시 서버에 연결할 필요 없이 클라이언트에 응답하는 방식으로 작동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SSL 터미네이션&lt;/b&gt; : Nginx는 클라이언트와의 연결에 대한 SSL 끝점 역할을 할 수 있습니다. 수신 SSL 연결을 처리 및 해독하고 프록시 서버의 응답을 암호화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;압축 &lt;/b&gt;:&amp;nbsp;프록시 서버가 압축된 응답을 보내지 않는 경우 클라이언트로 보내기 전에 응답을 압축하도록 Nginx를 구성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DDoS 공격 완화 :&lt;/b&gt;&amp;nbsp;수신 요청과 단일 IP 주소당 연결 수를 일반 사용자에게 일반적인 값으로 제한할 수 있습니다. 또한 Nginx를 사용하면 클라이언트 위치와 &quot;User-에이전트&quot; 및 &quot;Referer&quot;와 같은 요청 헤더 값을 기준으로 액세스를 차단하거나 제한할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx.conf&lt;/p&gt;
&lt;pre id=&quot;code_1654240802690&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

	# 백엔드 upstream 설정
    # upstream myweb-api {
    #     server api:8080;
    # }

	# 프론트엔드 upstream 설정
    upstream next-server {
        server 172.17.0.1:3000; # docker를 사용하지 않는다면 localhost:3000(웹서버주소)
    }

    server {
        listen 80;

		# /api 경로로 오는 요청을 백엔드 upstream 의 /api 경로로 포워딩
        # location /api {
        #     proxy_pass         http://myweb-api/api;
        # }

		# / 경로로 오는 요청을 프론트엔드 upstream 의 / 경로로 포워딩
        location / {
            proxy_pass         http://next-server/;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }
    log_format  main  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
                      '$status $body_bytes_sent &quot;$http_referer&quot; '
                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';
    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    # include /etc/nginx/conf.d/*.conf;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의깊게 봐야할 부분은 http 블럭 안의 upstream 블럭과 server 블럭입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 nginx 서버 주소로 요청합니다(localhost:80). 기본적으로 http는 80포트를 사용하기 때문에 nginx에서는 listen 80포트로 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. nginx서버는 location 블럭에서 proxy_pass로 지정된 주소로 요청을 전달해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 만약 운영중인 서버가 localhost:8080, 혹은 localhost:3000이면 upstream 블럭에 localhost:8080, localhost:3000으로 적어줍니다.(필자는 docker-compose로 구성했기 때문에 172.17.0.1로 작성했습니다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 웹서버(port 3000)를 시작하고, nginx도 시작해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. nginx 서버 주소로 요청하면(localhost:80), proxy_pass로 지정한 웹 서버로 요청이 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. docker-compose 사용시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;docker-compose.yml&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654241328494&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;

networks:
  corp:
    driver: bridge

services:
  nginx_proxy:
    image: nginx:1.21.5-alpine
    container_name: nginx_proxy    
    ports:
      - 80:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf

  next-client:
    container_name: next-client
    build:
      context: ./
      dockerfile: ./Dockerfile
    ports:
      - 3000:3000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;246&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgSWdC/btrDVLb1Nj7/5Br277jNpnYw82B9YN8AK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgSWdC/btrDVLb1Nj7/5Br277jNpnYw82B9YN8AK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgSWdC/btrDVLb1Nj7/5Br277jNpnYw82B9YN8AK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgSWdC%2FbtrDVLb1Nj7%2F5Br277jNpnYw82B9YN8AK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;246&quot; height=&quot;463&quot; data-origin-width=&quot;246&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;docker-compose.yml 폴더 위치에 nginx 폴더를 생성하고 nginx.conf 파일을 생성해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;nginx_proxy 블럭의 내용은 고정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑의 next-client는 docker로 띄울 웹 서버의 정보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Next.js로 웹 서버를 만들었고, Dockerfile로 빌드하도록 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jjeongil.tistory.com/1490&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jjeongil.tistory.com/1490&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@chickenfondue/nginx-docker-compose%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EA%B5%AC%EC%84%B1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@chickenfondue/nginx-docker-compose%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EA%B5%AC%EC%84%B1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Nginx</category>
      <category>docker-compose</category>
      <category>dockerfile</category>
      <category>nginx</category>
      <category>리버스프록시</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/238</guid>
      <comments>https://narup.tistory.com/238#entry238comment</comments>
      <pubDate>Fri, 3 Jun 2022 16:32:54 +0900</pubDate>
    </item>
    <item>
      <title>[Jenkins] ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job. 오류 해결</title>
      <link>https://narup.tistory.com/237</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins를 통해 Github와 웹훅으로 연결해서 자동배포를 하는데, 새 프로젝트를 연결하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ERROR:&amp;nbsp;Couldn't&amp;nbsp;find&amp;nbsp;any&amp;nbsp;revision&amp;nbsp;to&amp;nbsp;build.&amp;nbsp;Verify&amp;nbsp;the&amp;nbsp;repository&amp;nbsp;and&amp;nbsp;branch&amp;nbsp;configuration&amp;nbsp;for&amp;nbsp;this&amp;nbsp;job.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 오류가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 오류 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포지토리 또는 브런치 구성이 유효한지 한번 확인해라?? 같은 느낌이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 레포지토리 설정하는 부분에서 브런치가 자동적으로 */master로 설정되는 부분이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxxwgs/btrDVm4oQGA/jVQOxXjAwPSjgDkjBKQ7PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxxwgs/btrDVm4oQGA/jVQOxXjAwPSjgDkjBKQ7PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxxwgs/btrDVm4oQGA/jVQOxXjAwPSjgDkjBKQ7PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxxwgs%2FbtrDVm4oQGA%2FjVQOxXjAwPSjgDkjBKQ7PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1049&quot; height=&quot;83&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*/master -&amp;gt; */main&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 git을 사용하면 root 브런치가 main으로 잡히기 때문에 위로 변경해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend&amp;amp;Devops/Jenkins</category>
      <category>jenkins</category>
      <category>오류해결</category>
      <author>기은P</author>
      <guid isPermaLink="true">https://narup.tistory.com/237</guid>
      <comments>https://narup.tistory.com/237#entry237comment</comments>
      <pubDate>Fri, 3 Jun 2022 15:26:20 +0900</pubDate>
    </item>
  </channel>
</rss>