<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ SeunghyunKim - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/korean/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ SeunghyunKim - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/korean/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 16 Jun 2026 21:15:06 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/korean/news/author/seunghyunkim/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ React와 React 훅으로 검색 필터 만드는 법 ]]>
                </title>
                <description>
                    <![CDATA[ API에 GET 요청을 보내면 서버로부터 응답 데이터를 받습니다. 하지만 가끔은 이 데이터를 관리하는 게 문제가 되기도 합니다. 이 글에서는 React에서 검색 필터를 생성하는 법을 알려 드리려고 합니다. 함수형 컴포넌트와 React 훅을 이용하여 데이터에 있는 특정 단어를 검색할 것입니다. API에 GET 요청을 보내는 법 우선 서버에서 데이터를 불러오는 API에 GET 요청을 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/build-a-search-filter-using-react-and-react-hooks/</link>
                <guid isPermaLink="false">65338f09722f6d03ea1726bb</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ SeunghyunKim ]]>
                </dc:creator>
                <pubDate>Sat, 21 Oct 2023 10:32:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/10/main-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/build-a-search-filter-using-react-and-react-hooks/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build a Search Filter using React and React Hooks</a>
      </p><!--kg-card-begin: markdown--><p>API에 GET 요청을 보내면 서버로부터 응답 데이터를 받습니다. 하지만 가끔은 이 데이터를 관리하는 게 문제가 되기도 합니다.</p>
<p>이 글에서는 React에서 검색 필터를 생성하는 법을 알려 드리려고 합니다. 함수형 컴포넌트와 React 훅을 이용하여 데이터에 있는 특정 단어를 검색할 것입니다.</p>
<h2 id="apiget">API에 GET 요청을 보내는 법</h2>
<p>우선 서버에서 데이터를 불러오는 API에 GET 요청을 만들어 보겠습니다. 이 데이터를 불러오기 위해 아무 서버를 사용해도 되지만 이 글에서 필자는 사용자 목록을 불러오기 위해 {JSON} placeholder를 사용할 것입니다.</p>
<p>이 예시에서 사용자들의 이름과 이메일을 보여주는 카드들이 있습니다. 또한 특정 사용자들 검색하기 위해 사용할 검색 입력 창이 있습니다.</p>
<pre><code class="language-js,caption=JSON">import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    useEffect(() =&gt; {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) =&gt; {
                setAPIData(response.data);
            })
    }, [])
    return (
        &lt;div style={{ padding: 20 }}&gt;
            &lt;Input icon='search'
                placeholder='Search...'
            /&gt;
            &lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
                {APIData.map((item) =&gt; {
                    return (
                        &lt;Card&gt;
                            &lt;Card.Content&gt;
                                &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                &lt;Card.Description&gt;
                                    {item.email}
                                &lt;/Card.Description&gt;
                            &lt;/Card.Content&gt;
                        &lt;/Card&gt;
                    )
                })}
            &lt;/Card.Group&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-202008.png" alt="화면 상위의 검색 입력과 사용자 목록 얻기" width="600" height="400" loading="lazy"></p>
<p>그리고 만약 React에서 GET API 호출을 다루는 법을 모르신다면 API 호출에 대해 설명하는 <a href="https://www.freecodecamp.org/news/how-to-perform-crud-operations-using-react/">React CRUD 작업</a>에 관한 제 블로그 혹은 비디오를 보시길 바랍니다.</p>
<h2 id="">검색 입력 박스로부터 검색 입력을 얻는 법</h2>
<p>이제 검색 입력 박스로 부터 검색 쿼리(query)를 가져와 보겠습니다.</p>
<p>검색 입력을 위한 state를 생성합니다.</p>
<pre><code class="language-js,caption=검색">const [searchInput, setSearchInput] = useState('');
</code></pre>
<p>여기 <code>searchInput</code>은 문자열이고 검색 입력 값 설정을 위해 <code>setSearchInput</code>을 사용할 것입니다.</p>
<p>이제 검색 기능을 처리할 함수를 만들어 보겠습니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = () =&gt; {
        
}
</code></pre>
<p>그리고 이 함수를 <code>onChange</code> 이벤트로 검색 입력에 바인드(bind)합니다.</p>
<pre><code class="language-js,">&lt;Input icon='search'
    placeholder='Search...'
    onChange={() =&gt; searchItems()}
/&gt;
</code></pre>
<p>이리하여 입력 필드에 어떤 값이든 입력할 때마다 <code>searchItems</code>가 실행이 될 것입니다.</p>
<p>이제 <code>searchItems</code>에 입력 값을 전달해야 합니다.</p>
<pre><code class="language-js,caption=searchItems">&lt;Input icon='search'
    placeholder='Search...'
    onChange={(e) =&gt; searchItems(e.target.value)}
/&gt;
</code></pre>
<p>다음 검색 쿼리를 <code>searchItems</code> 함수에서 전달 받고 이전에 생성한 <code>setSearchInput</code>을 사용하여 <code>searchInput</code> state를 이 전달 받은 값으로 설정하겠습니다.</p>
<pre><code class="language-js,caption=searchInput">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
}
</code></pre>
<p>콘솔로 <code>searchValue</code> 값을 확인함으로써 위 로직이 잘 실행되는지 확인할 수 있습니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/size/w1000/2021/08/Screenshot-2021-08-14-203750.png" alt="Screenshot-2021-08-14-203750" width="600" height="400" loading="lazy"></p>
<p>보시다시피 필자의 이름을 입력했고 콘솔에서 이름이 나타납니다.</p>
<h2 id="">검색 결과에 따른 아이템 필터링</h2>
<p>이제 filter 메소드를 사용하여 APIData를 필터링(filtering)할 것입니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    APIData.filter((item) =&gt; {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
}
</code></pre>
<p><code>searchItems</code> 함수에서 서버로부터 얻는 데이터를 포함한 APIData state를 필터링하는 <code>filter</code> 메소드를 사용하고 있다는 것을 알 수 있습니다.</p>
<p>또한 객체 아이템으로부터 값들을 얻기 위해 <code>Object.values</code>를 사용하고 있습니다.</p>
<p>그런 다음 <code>join(' ')</code> 메소드를 사용하여 이 값들을 문자열로 바꾸어 줍니다.</p>
<p>다음 <code>toLowerCase</code> 메소드를 사용하여 이 문자열들을 소문자로 바꾸어 줍니다.</p>
<p>마지막으로 이 문자열들이 검색란에 입력한 값을 포함하고 있는지 확인합니다. 검색 입력 또한 소문자로 바꾸어 줍니다. 이는 단어를 대문자로 입력했을 시 검색을 더 효율적으로 만들어 주기 위해 그 문자열을 소문자로 바꾸게 하는 것입니다.</p>
<p>그런 다음 전체 쿼리를 반환합니다.</p>
<p>이제 이 필터링한 배열을 변수에 저장해야 합니다.</p>
<pre><code class="language-js,caption=필터링한">const filteredData = APIData.filter((item) =&gt; {
    return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
})
</code></pre>
<p>콘솔을 통해 위에서 만든 기능을 확인해 보겠습니다. 사용자 이름을 검색해보면 해당 사용자 이름에 맞는 데이터를 보게 될 것입니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/size/w1000/2021/08/Screenshot-2021-08-14-211709.png" alt="검색 아이템 콘솔에서 확인" width="600" height="400" loading="lazy"></p>
<p>이제 필터 데이터를 저장해야 하므로 state를 생성하겠습니다.</p>
<pre><code class="language-js,caption=필터">const [filteredResults, setFilteredResults] = useState([]);
</code></pre>
<p>필터링한 데이터를 담은 state를 생성합니다.</p>
<p>그런 다음 <code>setFilteredResults</code>를 사용하여 <code>searchItems</code> 함수에서 이 state를 <code>filteredData</code>로 설정합니다.</p>
<pre><code class="language-js,caption=filtedData를">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    const filteredData = APIData.filter((item) =&gt; {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
    setFilteredResults(filteredData)
}
</code></pre>
<h2 id="ui">필터링 결과를 UI에 보여주는 법</h2>
<p>이제 이 필터링 결과들을 주 UI에 보여주도록 하겠습니다.</p>
<p>우선 검색 입력란이 비어 있는지 확인하고 비어 있다면 모든 데이터를 보여주는 코드를 작성해야 합니다. 비어 있지 않다면 입력 값에 따라 결과를 필터링할 것입니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    if (searchInput !== '') {
        const filteredData = APIData.filter((item) =&gt; {
            return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
        })
        setFilteredResults(filteredData)
    }
    else{
        setFilteredResults(APIData)
    }
}
</code></pre>
<p>또한 애플리케이션의 반환 부분에서도 똑같은 확인이 필요합니다.</p>
<p>그래서 검색 단어의 길이가 1보다 크면 필터가 된 데이터를 보여줄 것입니다. 1보다 작다면 APIData state에 담긴 모든 데이터를 보여줄 것입니다.</p>
<pre><code class="language-js,caption=길이가">&lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
    {searchInput.length &gt; 1 ? (
        filteredResults.map((item) =&gt; {
            return (
                &lt;Card&gt;
                    &lt;Card.Content&gt;
                        &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                        &lt;Card.Description&gt;
                            {item.email}
                        &lt;/Card.Description&gt;
                    &lt;/Card.Content&gt;
                &lt;/Card&gt;
            )
        })
    ) : (
        APIData.map((item) =&gt; {
            return (
                &lt;Card&gt;
                    &lt;Card.Content&gt;
                        &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                        &lt;Card.Description&gt;
                            {item.email}
                        &lt;/Card.Description&gt;
                    &lt;/Card.Content&gt;
                &lt;/Card&gt;
            )
        })
    )}
&lt;/Card.Group&gt;
</code></pre>
<p>이제 검색 필드에서 사용자 이름을 검색한다면 해당 사용자의 데이터를 얻게 됩니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-212917.png" alt="UI에 검색 입력에 대한 결과" width="600" height="400" loading="lazy"></p>
<p>검색 창에서 이름을 지우면 모든 데이터를 얻게 됩니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-213037.png" alt="검색 입력란이 비어 있을 경우 모든 데이터 출력" width="600" height="400" loading="lazy"></p>
<p>여기 참고할 분들을 위해 전체 코드를 적어 두겠습니다.</p>
<pre><code class="language-js,caption=검색">import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    const [filteredResults, setFilteredResults] = useState([]);
    const [searchInput, setSearchInput] = useState('');
    useEffect(() =&gt; {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) =&gt; {
                setAPIData(response.data);
            })
    }, [])
    const searchItems = (searchValue) =&gt; {
        setSearchInput(searchValue)
        if (searchInput !== '') {
            const filteredData = APIData.filter((item) =&gt; {
                return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
            })
            setFilteredResults(filteredData)
        }
        else{
            setFilteredResults(APIData)
        }
    }
    return (
        &lt;div style={{ padding: 20 }}&gt;
            &lt;Input icon='search'
                placeholder='Search...'
                onChange={(e) =&gt; searchItems(e.target.value)}
            /&gt;
            &lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
                {searchInput.length &gt; 1 ? (
                    filteredResults.map((item) =&gt; {
                        return (
                            &lt;Card&gt;
                                &lt;Card.Content&gt;
                                    &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                    &lt;Card.Description&gt;
                                        {item.email}
                                    &lt;/Card.Description&gt;
                                &lt;/Card.Content&gt;
                            &lt;/Card&gt;
                        )
                    })
                ) : (
                    APIData.map((item) =&gt; {
                        return (
                            &lt;Card&gt;
                                &lt;Card.Content&gt;
                                    &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                    &lt;Card.Description&gt;
                                        {item.email}
                                    &lt;/Card.Description&gt;
                                &lt;/Card.Content&gt;
                            &lt;/Card&gt;
                        )
                    })
                )}
            &lt;/Card.Group&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p>이제 React 훅을 이용하여 React에서 완전 함수형의 검색 필터를 가지게 되었습니다.</p>
<p>보통 이 기능은 API 엔드포인트에서 검색 쿼리 인자를 전달하여 백엔드에서 처리하지만 프론트엔드에서 그 기능을 처리하는 것을 아는 것도 중요합니다.</p>
<p>이 글에 대한 보충 설명이 필요하다면 <a href="https://www.youtube.com/watch?v=8YsQmvJ9UZE">React와 React 훅을 이용하여 검색 필터 만들기</a>에 관한 제 유투브 비디오를 확인하시면 되겠습니다.</p>
<blockquote>
<p>이상 마무리 짓겠습니다. 감사합니다.</p>
</blockquote>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Node.js 자식 프로세스(Child Process): 알아야 할 모든 것 ]]>
                </title>
                <description>
                    <![CDATA[  spawn(), exec(), execFile(), fork() 사용법 > 업데이트: 이 글은 현재 필자의 책 "Node.js Beyond The Basics"의 일부입니다. > jscomplete.com/node-beyond-basics [https://github.com/samerbuna/efficient-node] 에서 Node에 대한 더 많은 정보와 이 글에 대한 갱신된 내용을 읽을 수 있습니다. Node.js에서 단일 스레드(single thread), 논-블라킹(non-blocking - 타 작업 수행 허용) 성능은 단일 프로세스에서 확실히 뛰어납니다. ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/</link>
                <guid isPermaLink="false">64cba01f36770806964a6e37</guid>
                
                    <category>
                        <![CDATA[ NodeJS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ SeunghyunKim ]]>
                </dc:creator>
                <pubDate>Mon, 14 Aug 2023 21:38:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/08/nodejs-child-proecss-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Node.js Child Processes: Everything you need to know</a>
      </p><h4></h4><h2 id="spawn-exec-execfile-fork-">spawn(), exec(), execFile(), fork() 사용법</h2><blockquote>업데이트: 이 글은 현재 필자의 책 "Node.js Beyond The Basics"의 일부입니다.</blockquote><blockquote><a href="https://github.com/samerbuna/efficient-node">jscomplete.com/node-beyond-basics</a>에서 Node에 대한 더 많은 정보와 이 글에 대한 갱신된 내용을 읽을 수 있습니다.</blockquote><p>Node.js에서 단일 스레드(single thread), 논-블라킹(non-blocking - 타 작업 수행 허용) 성능은 단일 프로세스에서 확실히 뛰어납니다. 하지만 결국, 단일 CPU에 단일 프로세스는 어플리케이션의 늘어나는 작업량을 감당하기에는 충분하지 않습니다.</p><p>서버의 성능이 얼마나 좋은지에 상관없이 단일 스레드는 한정된 처리량만을 지원할 수 밖에 없습니다.</p><p>Node.js가 단일 스레드에서 동작하는 것이 다중 프로세스나 다중 기기의 이점을 얻을 수 없다는 것을 의미하지는 않습니다.</p><p>다중 프로세스를 사용하는 것이 Node 어플리케이션의 크기 조정을 하는데 가장 좋은 방법입니다. Node.js는 많은 Node를 가진 분산(distributed) 어플리케이션을 만들기 위해 설계되었습니다. 이름이 Node인 이유는 여기서 찾을 수 있습니다. 확장성(Scalability)은 Node.js에 이미 탑재가 되었으며 어플리케이션의 생애주기 동안은 생각할 필요가 없는 요소입니다.</p><blockquote>이 글은 <a href="https://www.pluralsight.com/courses/nodejs-advanced" rel="nofollow">개인 Pluralsight Node.js 강의</a>의 일부입니다. 이 웹페이지에 비슷한 내용을 비디오로 담았습니다.</blockquote><p>이 글을 읽기 전에 Node.js 이벤트(event)와 스트림(stream)에 대한 이해가 필요합니다. 아직 두 요소에 대한 이해가 부족하시다면 이 글을 읽기 전에 다음의 두 글을 먼저 읽기를 권합니다.</p><p><a href="https://www.freecodecamp.org/news/understanding-node-js-event-driven-architecture-223292fcbc2d/" rel="nofollow">Node.js 이벤트 구동 구조에 대한 이해대부분의 Node 객체 - HTTP 요청, 응답과 스트림(stream) 등 - 는 이벤트 에미터(EventEmitter)를 실행하는데...</a></p><p><a href="https://medium.freecodecamp.com/node-js-streams-everything-you-need-to-know-c9141306be93" rel="nofollow">스트림(Stream): 알아야 할 모든 것</a>Node.js 스트림은 같이 사용하기 어려운 것으로 유명하지만 이해하는 게 더 어려운 걸로도 유명합니다. 하지만 좋은 소식이 있습니다...</p><h2 id="-child-process-">자식 프로세스(Child Process) 모듈</h2><p>자식 프로세스는 Node.js <code>child_process</code> 모듈을 통해 쉽게 만들어질 수 있으며 그 자식 프로세스들은 메시징 시스템(messaging system)을 통해 서로 쉽게 소통할 수 있습니다.</p><p><code>child_process</code> 모듈은 자식 프로세스 안에서 모든 시스템 명령어를 실행함으로써 운영 체제 기능들을 접근하게 해줍니다.</p><p>자식 프로세스의 입력 스트림을 제어하고 이 입력에 대한 출력 스트림을 리슨(Listen)합니다(리슨은 어떠한 대상을 받아 처리할 준비를 하는 것으로 이해하시면 됩니다. - 역주). 또한, 기저 OS 명령어에 전달되는 인자(argument)들을 제어할 수 있고 그 명령어들의 출력을 이용해 우리가 원하는 무엇이든 할 수 있습니다. 예를 들면, 하나의 명령어에 대한 출력을 다른 명령어의 입력으로 연결시킬 수 있습니다(Linux에서 하는 것과 같습니다). 이 명령어들의 입/출력이 Node.js 스트림을 통해서 표현될 수 있기 때문입니다.</p><p><em>이 글에 사용되는 예시들은 모두 Linux가 바탕이라는 것을 알아두시길 바랍니다. 윈도우에서는 필자가 사용하는 명령어의 대체 명령어로 바꿔 주어야 합니다.</em></p><p>Node에서 자식 프로세스를 생성하는 방법은 다음과 같이 4개가 있습니다: <code>spawn()</code>, <code>fork()</code>, <code>exec()</code>, <code>execFile()</code>.</p><p>이 4개의 함수들 간의 차이와 언제 사용하는지에 대하여 알아보겠습니다.</p><h2 id="spawn-">Spawn으로 생성된 자식 프로세스</h2><p><code>spawn</code> 함수는 새 프로세스에서 한 명령어를 시작시키며 그 명령어에 어떤 인자든지 전달하기 위해 이 함수를 사용할 수 있습니다. 예를 들면, 여기 <code>pwd</code> 명령어를 실행할 새 프로세스를 스폰(spawn)하는 코드입니다.</p><pre><code>const { spawn } = require('child_process');

const child = spawn('pwd');</code></pre><p><code>spawn</code> 함수는 간단히 <code>child_process</code> 모듈로부터 구조 분해(destructuring)하여 얻을 수 있으며 첫 번째 인자로 OS 명령어를 넣어 이 함수를 실행시킬 수 있습니다.</p><p>위 <code>spawn</code> 함수를 실행시켜 얻은 결과(위 <code>child</code> 객체)는 <code>ChildProcess</code> 인스턴스이며 <a href="https://medium.freecodecamp.com/understanding-node-js-event-driven-architecture-223292fcbc2d" rel="nofollow">이벤트 에미터(EventEmitter) API</a>를 실행합니다. 이는 그 <code>child</code> 객체에 이벤트를 처리하는 핸들러(handler)를 등록시킬 수 있다는 것을 뜻합니다. 예를 들면, <code>exit</code> 이벤트 핸들러 등록하면 자식 프로세스가 종료될 때 뭔가를 할 수 있습니다.</p><pre><code>child.on('exit', function (code, signal) {
  console.log('child process exited with ' +
              `code ${code} and signal ${signal}`);
});</code></pre><p>위의 핸들러는 자식 프로세스의 종료 <code>code</code>와 종료시킬 때 사용되는 <code>signal</code>를 줍니다. 이 <code>signal</code> 변수는 보통 자식 프로세스가 종료될 때 null입니다.</p><p><code>ChildProcess</code> 인스턴스에 핸들러를 등록할 수 있는 다른 이벤트로는 <code>disconnect</code>, <code>error</code>, <code>close</code> 그리고 <code>message</code>가 있습니다.</p><ul><li><code>disconnect</code> 이벤트는 부모 프로세스가 <code>child.disconnect</code> 함수를 직접 호출할 때 발생합니다(emitted).</li><li><code>error</code> 이벤트는 프로세스가 생성될 수 없거나 어떠한 이유로 멈출 때(killed) 발생합니다.</li><li><code>close</code> 이벤트는 자식 프로세스의 <code>stdio</code> 스트림이 종료될 때 발생합니다.</li><li><code>message</code> 이벤트는 가장 중요한 이벤트입니다. 이 이벤트는 메시지를 보내기 위해 자식 프로세스가 <code>process.send()</code> 함수를 사용할 때 발생합니다. 이 방법으로 부모/자식 프로세스 간에 소통을 할 수 있게 됩니다. 아래에서 이에 대한 예를 살펴보겠습니다.</li></ul><p>모든 자식 프로세스는 세 개의 표준 <code>stdio</code> 스트림도 갖고 있는데 이 스트림들은 <code>child.stdin</code>, <code>child.stdout</code>, <code>child.stderr</code>를 사용하여 접근할 수 있습니다.</p><p>그 스트림들이 종료될 때, 그것들을 사용하던 자식 프로세스가 <code>close</code> 이벤트를 발생시킵니다. 이 <code>close</code> 이벤트는 <code>exit</code> 이벤트와는 다른데 여러 자식 프로세스들이 같은 <code>stdio</code> 스트림을 공유할 지도 모르며 그 중 한 자식 프로세스가 종료(exit)된다는 것이 그 스트림들이 모두 종료(close)되는 것을 의미하지 않기 때문입니다.</p><p>모든 스트림이 이벤트 에미터(Event Emitter)이기 때문에 모든 자식 프로세스에 연결된 이 <code>stdio</code> 스트림들에서 다른 이벤트들을 리슨할 수 있습니다. 그렇지만 보통의 프로세스와 달리 자식 프로세스에서는 <code>stdin</code> 스트림이 쓰기가 가능한 스트림인 반면 <code>stdout</code>/<code>stderr</code> 스트림들은 읽기가 가능한 스트림입니다. 이는 기본적으로 주 프로세스(main process)에서의 스트림들의 유형과 정반대입니다. 이 스트림들에 사용하는 이벤트들이 표준 이벤트들입니다. 가장 중요한 것은 읽기가 가능한 스트림에서는 명령어에 대한 출력과 명령어 실행 시 발생하는 에러가 담긴 <code>data</code> 이벤트를 리슨할 수 있다는 것입니다.</p><pre><code>child.stdout.on('data', (data) =&gt; {
  console.log(`child stdout:\n${data}`);
});

child.stderr.on('data', (data) =&gt; {
  console.error(`child stderr:\n${data}`);
});</code></pre><p>위의 두 핸들러는 두 로그(log)가 각각 주 프로세스 <code>stdout</code>과 <code>stderr</code>에 출력될 것입니다. 위에서 언급했던 <code>spawn</code> 함수를 실행할 때, <code>pwd</code> 명령어에 대한 결과가 출력되고 자식 프로세스는 에러가 일어나지 않았다는 의미인 code <code>0</code>과 함께 종료됩니다.</p><p><code>spawn</code> 함수의 두번째 인자를 사용하여 <code>spawn</code> 함수에 의해 실행되는 명령어에 인자(리눅스 명령어에 넣을 인자 - 역주)를 넣을 수 있는데 이 때, 이 두번째 인자는 명령어에 전달되는 모든 인자의 배열입니다. 예를 들면, <code>find</code> 명령어를 현재 디렉토리(directory)에서 <code>-type f</code> 인자(파일 유형만 찾기)와 함께 실행시키려면 다음과 같이 하면 됩니다:</p><pre><code>const child = spawn('find', ['.', '-type', 'f']);</code></pre><p>명령어 실행 중 에러가 발생한다면, 예를 들어, 유효하지 않은 경로를 <code>find</code>에 입력하면 <code>child.stderr</code>, <code>data</code> 이벤트 핸들러가 동작이 되고 <code>exit</code> 이벤트 핸들러가 에러가 발생했다는 신호로 종료 code <code>1</code>을 알리게 될 것입니다. 이 에러 값들은 실제 호스트(host) OS와 에러 유형에 따라 달라집니다.</p><p>자식 프로세스 <code>stdin</code>은 쓰기가 가능한 스트림입니다. 명령어에 입력을 보내기 위해 <code>stdin</code>을 활용할 수 있습니다. 여느 쓰기가 가능한 스트림처럼 <code>stdin</code>을 사용하는 데 가장 쉬운 방법은 <code>pipe</code> 함수를 사용하는 것입니다. 간단하게 읽기가 가능한 스트림을 쓰기가 가능한 스트림에 연결하면 됩니다. 주 프로세스 <code>stdin</code>이 읽기가 가능한 스트림이므로 이를 자식 프로세스 <code>stdin</code> 스트림에 연결할 수 있습니다. 예를 들면 다음과 같습니다:</p><pre><code>const { spawn } = require('child_process');

const child = spawn('wc');

process.stdin.pipe(child.stdin)

child.stdout.on('data', (data) =&gt; {
  console.log(`child stdout:\n${data}`);
});</code></pre><p>위 예시에서 자식 프로세스는 리눅스에서 줄, 단어와 글자들을 세는 <code>wc</code> 명령어를 호출합니다. 그런 다음에 주 프로세스 <code>stdin</code>(읽기가 가능한 스트림)을 자식 프로세스 <code>stdin</code>(쓰기 가능한 스트림)과 연결시킵니다. 이 조합의 결과는 우리가 타자를 칠 수 있는 표준 입력 모드를 얻는 것이며 <code>Ctrl+D</code>를 누르면 우리가 입력한 것이 <code>wc</code> 명령어의 입력으로 사용될 것입니다.</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/9776f5fedbac7577130549ebb47952ddf480b4138bfe3e95b33347b7ee9b97b1/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f312a7339645159394764676b6b4966397a4331424c3642672e676966" class="kg-image" alt="Gif captured from my Pluralsight course — Advanced Node.js" width="600" height="400" loading="lazy"></figure><p>또한, 리눅스 명령어에서 하는 것처럼 여러 프로세스의 표준 입/출력을 서로에게 연결시킬 수 있습니다. 예를 들면, 현재 디렉토리에 있는 모든 파일의 수를 파악하기 위해 <code>find</code> 명령어의 <code>stdout</code>을 <code>wc</code> 명령어의 <code>stdin</code>과 연결시킬 수 있습니다.</p><pre><code>const { spawn } = require('child_process');

const find = spawn('find', ['.', '-type', 'f']);
const wc = spawn('wc', ['-l']);

find.stdout.pipe(wc.stdin);

wc.stdout.on('data', (data) =&gt; {
  console.log(`Number of files ${data}`);
});</code></pre><p>필자는 줄의 수만 세기 위해 <code>wc</code> 명령어에 <code>-l</code> 인자를 더했습니다. 위에 있는 코드가 실행이 되면 현재 디렉토리에 있는 모든 디렉토리 안의 파일들의 수를 출력하게 됩니다.</p><h2 id="shell-exec-">Shell 문법과 exec 함수</h2><p>기본적으로 <code>spawn</code> 함수는 전달되는 명령어를 실행하기 위해 <em>shell</em> 을 생성하지 않습니다. 이게 shell을 생성하는 <code>exec</code> 함수보다 효율적인 점입니다. <code>exec</code> 함수는 다른 주요 차이점이 있습니다. 이 함수는 명령어에 의해 생성된 출력을 <em>버퍼(buffer)에 저장</em> 하고 이 전체 출력값을 콜백 함수에 보냅니다(<code>spawn</code> 함수는 대신에 스트림을 사용합니다).</p><p>이전에 살펴본 <code>exec</code> 함수에 적용된 <code>find | wc</code> 예시입니다.</p><pre><code>const { exec } = require('child_process');

exec('find . -type f | wc -l', (err, stdout, stderr) =&gt; {
  if (err) {
    console.error(`exec error: ${err}`);
    return;
  }

  console.log(`Number of files ${stdout}`);
});</code></pre><p><code>exec</code> 함수는 명령어 실행을 위해 shell을 사용하기 때문에 shell <em>pipe</em> 기능을 이용하여 <em>shell 문법</em> 을 바로 사용할 수 있습니다.</p><p>여기서 외부에서 제공되는 동적 입력을 실행한다면 shell 문법 사용이 보안 위험이 수반된다는 것을 알아두셔야 합니다. 사용자가 ;과 $같은 shell 문법 글자들을 사용하여 명령어 주입 공격을 쉽게 할 수 있게 됩니다.(예를 들면, <code>command + ’; rm -rf ~’</code>)</p><p><code>exec</code> 함수 출력을 버퍼에 저장하고 그 출력을 <code>stdout</code> 인자로 콜백 함수(<code>exec</code>의 두 번째 인자)에 보냅니다. 이 <code>stdout</code> 인자가 우리가 출력하기를 원하는 명령어의 결과인 것입니다.</p><p><code>exec</code> 함수는 shell 문법을 사용해야 하고 명령어로부터 예상되는 데이터의 크기가 작을 때 좋은 선택입니다.(<code>exec</code> 함수는 반환(return)하기 전에 전체 데이터를 버퍼에 저장한다는 것을 기억하시면 됩니다)</p><p><code>spwan</code> 함수는 명령어로부터 예상되는 데이터의 크기가 클 때 더 좋은 선택이 됩니다. 이는 그 데이터가 표준 IO 객체와 함께 스트림이 될 것이기 때문입니다.</p><p>필요하다면 spwan으로 생성이 된 자식 프로세스가 그 부모 프로세스의 표준 IO 객체를 상속받게 할 수 있지만 가장 중요한 것은 <code>spawn</code> 함수가 shell 문법을 사용하도록 만들 수 있다는 것입니다. 다음은 <code>spawn</code> 함수에 적용된 같은 <code>find | wc</code> 명령어입니다.</p><pre><code>const child = spawn('find . -type f | wc -l', {
  stdio: 'inherit',
  shell: true
});</code></pre><p><code>stdio: 'inherit'</code> 옵션(option)이 있기 때문에 코드를 실행하면 자식 프로세스는 주 프로세스의 <code>stdin</code>, <code>stdout</code>과 <code>stderr</code>을 상속받습니다. 이것이 자식 프로세스의 데이터 이벤트 핸들러들이 주 프로세스의 <code>process.stdout</code> 스트림에서 작동하게 하고 이 스크립트(script)가 결과를 즉시 출력하게 만듭니다.</p><p><code>shell: true</code> 옵션이 있기 때문에 <code>exec</code>함수와 했던 것처럼 전달받은 명령어에서 shell 문법을 사용할 수 있었습니다. 하지만 이 코드로 <code>spawn</code> 함수가 주는 데이터 스트리밍의 이점 또한 살릴 수 있습니다. <em>일거양득이라고 할 수 있겠습니다.</em></p><p><code>shell</code>과 <code>stdio</code> 이외에도 <code>child_process</code> 함수에 마지막 인자로 사용할 수 있는 다른 좋은 옵션들이 있습니다. 예를 들면, 스크립트의 작업 디렉토리를 변경하기 위해 <code>cwd</code> 옵션을 사용할 수 있습니다. 예시로 myDownloads 폴더가 작업 디렉토리로 설정되고 shell을 사용하는 <code>spawn</code> 함수를 이용한 전체 파일의 수 세기 예제가 있습니다. <code>cwd</code> 옵션은 스크립트가 <code>~/Downloads</code>에 있는 모든 파일의 수를 셀 수 있게 해줍니다.</p><pre><code>const child = spawn('find . -type f | wc -l', {
  stdio: 'inherit',
  shell: true,
  cwd: '/Users/samer/Downloads'
});</code></pre><p>사용이 가능한 다른 옵션은 새 자식 프로세스에 보이는 환경 변수를 특정하는 <code>env</code> 옵션이 있습니다. 이 옵션에 대한 기본값은 현재 프로세스 환경에 모든 명령어가 접근하게 해주는 <code>process.env</code>입니다. 이 기본값을 덮어 씌우길 원한다면 간단하게 <code>env</code> 옵션으로 빈 객체를 전달하거나 유일한 환경 변수로 여기도록 새로운 값을 전달하면 됩니다.</p><pre><code>const child = spawn('echo $ANSWER', {
  stdio: 'inherit',
  shell: true,
  env: { ANSWER: 42 },
});</code></pre><p><code>echo</code> 명령어는 부모 프로세스 환경 변수에 접근할 수 없습니다. 예를 들면, 이 명령어는 <code>$HOME</code>에 접근할 수 없지만 <code>$ANSWER</code>에는 접근할 수 있는데 <code>env</code> 옵션을 통해 맞춤(custom) 환경 변수로 전달되었기 때문입니다.</p><p>이 단락에서 마지막으로 설명하려는 중요한 자식 프로세스 옵션은 자식 프로세스를 부모 프로세스와 독립적으로 실행시켜주는 <code>detached</code> 옵션입니다.</p><p>이벤트 루프가 끊임없이 분주한 <code>timer.js</code>이라는 파일이 있다고 가정해봅시다.</p><pre><code>setTimeout(() =&gt; {  
  // keep the event loop busy
}, 20000);</code></pre><p><code>detached</code> 옵션을 사용하여 백그라운드(background)에서 실행시킬 수 있습니다. (백그라운드 실행은 사용자의 개입없이 실행이 되는 것을 의미합니다. - 역주)</p><pre><code>const { spawn } = require('child_process');

const child = spawn('node', ['timer.js'], {
  detached: true,
  stdio: 'ignore'
});

child.unref();</code></pre><p>분리된(detached) 자식 프로세스의 정확한 동작은 OS에 따라 달라집니다. 윈도우에서는 분리된 자식 프로세스가 개별 console 창을 가지는 반면에 리눅스에서는 자식 프로세스가 새 프로세스 그룹과 세션(session)의 리더(leader)가 될 것입니다.</p><p><code>unref</code> 함수가 분리된 프로세스에서 호출이 된다면 부모 프로세스는 자식 프로세스와 별개로 종료될 수 있습니다. 이는 자식 프로세스가 소요 시간이 긴 프로세스를 실행시킬 때 유용하지만 이 자식 프로세스를 백그라운드에서 계속 실행시키기 위해서는 자식 프로세스의 <code>stdio</code> 설정 역시 부모 프로세스와 독립적이어야 합니다.</p><p>위 예시는 부모 프로세스가 자식 프로세스가 백그라운드에서 계속 실행하는 동안 종료될 수 있도록 분리시키고 부모 <code>stdio</code> 파일 설명서를 무시하여 백그라운드에서 node 스크립트(timer.js)를 실행할 것입니다.</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/e5e79744650f13e201e392d8ff077d4919f572d1625b47e78eef12d69493a45f/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f312a5768764d73387a762d57533676376e44586d44557a772e676966" class="kg-image" alt="Gif captured from my Pluralsight course — Advanced Node.js" width="600" height="400" loading="lazy"></figure><h2 id="execfile-">execFile 함수</h2><p>만약 shell을 사용하지 않고 파일을 실행시켜야 한다면, <code>execFile</code> 함수를 사용하면 됩니다. 이 함수는 <code>exec</code> 함수와 동일하게 동작하지만 <code>exec</code> 함수를 조금 더 효율적으로 만들어주는 shell을 사용하지 않습니다. 윈도우에서는 <code>.bat</code>이나 <code>.cmd</code> 같은 파일은 실행이 되지 않습니다. 이러한 파일들은 <code>execFile</code>으로 실행시킬 수 없으며 <code>exec</code> 혹은 <code>spawn</code> 함수에 shell 사용 옵션을 true로 두어야 실행시킬 수 있습니다.</p><h2 id="-sync-">*Sync 함수</h2><p><code>child_process</code>로부터 나온 <code>spawn</code>, <code>exec</code>과 <code>execFile</code> 함수들은 자식 프로세스가 종료될 때까지 기다리는 동기화 차단 버전들도 있습니다.</p><pre><code>const { 
  spawnSync, 
  execSync, 
  execFileSync,
} = require('child_process');</code></pre><p>이 동기화 버전들은 스크립팅(scripting) 작업이나 시작 프로세스 작업 시 유용할 수 있으나 이 경우들이 아니라면 피해야 합니다.</p><h2 id="fork-">fork() 함수</h2><p><code>fork</code> 함수는 node 프로세스를 생성하기 위한 <code>spawn</code> 함수의 변형입니다. <code>spawn</code>과 <code>fork</code> 함수의 가장 큰 차이는 <code>fork</code>를 사용할 때 소통 통로가 자식 프로세스에 만들어지기 때문에 부모 프로세스와 복사된(forked) 프로세스 간에 메시지를 주고 받기 위해 전역(global) <code>process</code> 객체와 함께 복사된 프로세스에서 <code>send</code> 함수를 사용할 수 있다는 것입니다. 이것은 <code>EventEmitter</code> 모듈을 통해 구현할 수 있습니다. 다음 예시를 들어보겠습니다.</p><p>부모 파일, <code>parent.js</code>:</p><pre><code>const { fork } = require('child_process');

const forked = fork('child.js');

forked.on('message', (msg) =&gt; {
  console.log('Message from child', msg);
});

forked.send({ hello: 'world' });</code></pre><p>자식 파일, <code>child.js</code>:</p><pre><code>process.on('message', (msg) =&gt; {
  console.log('Message from parent:', msg);
});

let counter = 0;

setInterval(() =&gt; {
  process.send({ counter: counter++ });
}, 1000);</code></pre><p>위의 부모 파일에서 (<code>node</code> 명령어와 함께 파일을 실행시키는)<code>child.js</code>를 복사(fork)하고 난 다음에 <code>message</code> 이벤트를 리슨합니다. <code>message</code> 이벤트는 자식 프로세스가 <code>process.send</code>를 사용할 때마다, 여기서는 매초마다 발생할 것입니다.</p><p>부모에서 자식으로 메시지를 전달하기 위해서 <code>send</code> 함수를 복사(forked)된 객체에서 실행시킬 있고 자식 스크립트에서 전역 <code>process</code> 객체에 있는 <code>message</code> 이벤트를 리슨할 수 있습니다.</p><p>위 <code>parent.js</code>를 실행시킬 때, 복사된 자식 프로세스에 의해 출력이 될 <code>{ hello: 'world' }</code> 객체를 우선 보내고 난 다음에 복사된 자식 프로세스가 매초마다 부모 프로세스에서 출력이 되는 카운터(counter) 증분값을 보낼 것입니다.</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/f4a84df45d480a7bbd181cf26531a6fb1c0b38e127e0944a25f639864ea9a26d/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f312a474f494f54415a54636e3430715a334a776773724e412e676966" class="kg-image" alt="Screenshot captured from my Pluralsight course — Advanced Node.js" width="600" height="400" loading="lazy"></figure><p><code>fork</code> 함수에 대해 실례를 더 살펴보겠습니다.</p><p>두 개의 엔드포인트(endpoint)를 처리하는 http 서버를 가지고 있다고 가정하겠습니다. 이 중 한 엔드포인트(아래의 <code>/compute</code>)는 과도한 계산이 이루어지며 완료까지 수 초가 걸립니다. 다음과 같이 for 루프를 통해서 모방해볼 수 있습니다.</p><pre><code>const http = require('http');

const longComputation = () =&gt; {
  let sum = 0;
  for (let i = 0; i &lt; 1e9; i++) {
    sum += i;
  };
  return sum;
};

const server = http.createServer();

server.on('request', (req, res) =&gt; {
  if (req.url === '/compute') {
    const sum = longComputation();
    return res.end(`Sum is ${sum}`);
  } else {
    res.end('Ok')
  }
});

server.listen(3000);</code></pre><p>이 프로그램은 큰 문제를 가지고 있습니다; <code>/compute</code> 엔드포인트에 요청이 오면, 긴 시간이 소요되는 for 루프 연산으로 이벤트 루프가 분주하기 때문에 서버가 다른 요청들을 처리할 수가 없게 됩니다.</p><p>장시간 연산의 성질에 영향을 받는 이 문제를 해결하는 몇가지 방법이 있지만 모든 연산에 통하는 해결책은 그 연산을 <code>fork</code>를 이용해서 다른 프로세스로 이동시키는 것입니다.</p><p>우선, <code>longComputation</code> 함수 자체를 한 파일에 이동시키고 주 프로세스로부터 받은 메시지로 명령을 받을 때 해당 함수를 불러오게 합니다.</p><p>새롭게 생성한 <code>compute.js</code>:</p><pre><code>const longComputation = () =&gt; {
  let sum = 0;
  for (let i = 0; i &lt; 1e9; i++) {
    sum += i;
  };
  return sum;
};

process.on('message', (msg) =&gt; {
  const sum = longComputation();
  process.send(sum);
});</code></pre><p>이제 주 프로세스 이벤트 루프에서 장시간 연산을 하는 대신에 <code>compute.js</code>를 복사(fork)하고 서버와 복사된 프로세스 사이에 메시지를 주고 받는 메시지 인터페이스(interface)를 사용할 수 있습니다.</p><pre><code>const http = require('http');
const { fork } = require('child_process');

const server = http.createServer();

server.on('request', (req, res) =&gt; {
  if (req.url === '/compute') {
    const compute = fork('compute.js');
    compute.send('start');
    compute.on('message', sum =&gt; {
      res.end(`Sum is ${sum}`);
    });
  } else {
    res.end('Ok')
  }
});

server.listen(3000);</code></pre><p>위의 코드로 <code>/compute</code> 요청이 일어날 때, 그 장시간 연산을 실행시키기 위해 메시지를 복사된(forked) 프로세스에 보냅니다. 주 프로세스의 이벤트 루프는 차단되지 않을 것입니다.</p><p>복사된 프로세스가 이 장시간 연산을 마무리하면, 해당 프로세스는 <code>process.send</code>를 이용해서 부모 프로세스에 결과를 보낼 수 있게 됩니다.</p><p>부모 프로세스에서 복사된 자식 프로세스에 있는 <code>message</code> 이벤트를 리슨합니다. 이 이벤트를 받을 때, http로 요청을 보낸 사용자에게 보낼 <code>sum</code> 값을 대기시킵니다.</p><p>위의 코드는 물론, 복사할 수 있는 프로세스의 개수에 따라 제한되지만 우리가 코드를 실행하고 http를 통해 이 장시간 연산 엔드포인트에 요청을 보낼 때, 주 서버는 차단되지 않고 더 많은 요청을 받을 수 있습니다.</p><p>다음 기사의 주제인 Node의 <code>cluster</code> 모듈은 자식 프로세스 복사와 여느 시스템 상에서 만들 수 있는 많은 복사물들 간 요청의 부하 조절에 대한 생각에 기반을 둡니다.</p><p>그럼 이만 여기서 마무리 짓겠습니다. 읽어 주셔서 감사합니다! 다음에 또 뵙겠습니다!</p><p>React나 Node에 대하여 배우고 있나요? 제 책을 확인해보세요.</p><ul><li>Learn React.js by Building Games</li><li>Node.js Beyond the Basics</li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React.useEffect 훅(Hook) - 일반적인 문제와 그 해결 방법 ]]>
                </title>
                <description>
                    <![CDATA[  감수 Boyeon Ihn [https://boyeonihn.vercel.app/] React 훅(hook)은 꽤 오랫동안 사용되어 왔습니다. 개발자 대부분이 이 훅의 동작 원리와 일반적인 사용 사례에 대해 제법 익숙합니다. 하지만 많은 이들이 빠지는 useEffect 함정이 있습니다. 사용 사례 간단한 시나리오로 이야기를 시작해 보겠습니다. React App을 만들고 한 컴포넌트에 현재 사용자의 이름을 보여주기를 원한다고 하겠습니다. 우선, API로부터 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/react-useeffect-hug-hook-ilbanjeogin-munjewa-geu-haegyeol-bangbeob/</link>
                <guid isPermaLink="false">64958fc4f825440663cbe6b7</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ SeunghyunKim ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jun 2023 12:57:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/06/react-main-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/most-common-react-useeffect-problems-and-how-to-fix-them/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React.useEffect Hook – Common Problems and How to Fix Them</a>
      </p><p></p><h4 id="-boyeon-ihn">감수 <strong><a href="https://boyeonihn.vercel.app/">Boyeon Ihn</a></strong></h4><p></p><p>React 훅(hook)은 꽤 오랫동안 사용되어 왔습니다. 개발자 대부분이 이 훅의 동작 원리와 일반적인 사용 사례에 대해 제법 익숙합니다. 하지만 많은 이들이 빠지는 <code>useEffect</code> 함정이 있습니다.</p><h2 id="-">사용 사례</h2><p>간단한 시나리오로 이야기를 시작해 보겠습니다. React App을 만들고 한 컴포넌트에 현재 사용자의 이름을 보여주기를 원한다고 하겠습니다. 우선, API로부터 사용자 이름을 불러와야 합니다.</p><p>또한, 애플리케이션 내 다른 곳에서도 사용자 데이터가 사용될 것이기 때문에 데이터 불러오는 로직(logic)을 커스텀(custom) React 훅에 담아보겠습니다.</p><p>기본적으로 이 React 컴포넌트는 다음과 같을 것입니다.</p><pre><code class="language-js">const Component = () =&gt; {
  // useUser custom hook

  return &lt;div&gt;{user.name}&lt;/div&gt;;
};
</code></pre><p>아주 간단해 보이지 않나요?</p><h2 id="useuser-react-">useUser React 훅</h2><p>두번째 단계는 <code>useUser</code> 커스텀 훅을 만드는 것입니다.</p><pre><code class="language-js">const useUser = (user) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    if (user) {
      fetch('users.json').then((response) =&gt;
        response.json().then((users) =&gt; {
          return setUserData(users.find((item) =&gt; item.id === user.id));
        })
      );
    }
  }, []);

  return userData;
};
</code></pre><p>하나씩 자세히 살펴보겠습니다. 우선 이 훅이 사용자 객체를 받는지 확인을 합니다. 그런 다음에 <code>user.json</code>이라는 파일로부터 애플리케이션 사용자의 목록을 불러오고 필요한 사용자를 찾기 위해 아이디(id)로 목록을 걸러냅니다.</p><p>그런 다음에 필요한 데이터를 갖게 되면, 그 데이터를 훅에 있는 <code>userData</code> state에 저장합니다. 마지막으로 <code>userData</code>를 반환합니다.</p><p>주의: 위 예시는 단지 설명을 위한 것입니다. 실제로 데이터 불러오기는 이보다 훨씬 복잡합니다. 이 주제에 관해 관심이 있다면 ReactQuery, Typescript and GraphQL으로 데이터 불러오기 설정을 어떻게 하는지에 관한 <a href="https://whereisthemouse.com/graphql-requests-made-easy-with-react-query-and-typescript">제 글을 확인하시길 바랍니다</a>.</p><p>이제 훅을 React 컴포넌트에 적용하면 어떻게 되는지 확인해보겠습니다.</p><pre><code class="language-js">const Component = () =&gt; {
  const user = useUser({ id: 1 });
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
</code></pre><p>모든 게 예상대로 작동하는 것처럼 보입니다. 잠깐만... 이게 뭐죠?</p><h2 id="eslint-exhaustive-deps-">ESLint exhaustive-deps 규칙</h2><p>훅(hook)에 ESLint 경고가 생겼습니다.</p><pre><code>React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
</code></pre><p>음, <code>useEffect</code>에 디펜던시(dependency)가 빠진 것으로 보입니다. 자, 그럼! 디펜던시를 추가해보겠습니다. 별 대수겠어요? 😂(여기서 디펜던시는 한 대상과 의존관계에 있는 다른 대상을 일컫습니다. 즉, <code>useEffect</code> 훅이 디펜던시에 영향을 받는다는 것으로 이해하시면 되겠습니다. - 역주)</p><pre><code class="language-js">const useUser = (user) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    if (user) {
      fetch('users.json').then((response) =&gt;
        response.json().then((users) =&gt; {
          return setUserData(users.find((item) =&gt; item.id === user.id));
        })
      );
    }
  }, [user]);

  return userData;
};
</code></pre><p>이런, 이제는 <code>Component</code>가 렌더링(rendering)을 멈추는 것 같지 않아 보입니다. 어떻게 된 일일까요? - (렌더링은 간단하게 화면에 보이는 요소들을 불러오는 것으로 이해하시면 되겠습니다. - 역주)<br>설명해보겠습니다.</p><h2 id="-re-render-">무한 렌더링(re-render) 문제</h2><p>컴포넌트가 무한 렌더링(re-rendering)을 하는 이유는 <code>useEffect</code>의 디펜던시가 끊임없이 변하기 때문입니다. 하지만 우리는 계속 같은 객체를 훅에 전달하고 있는데 왜 문제가 발생할까요?<br>같은 key와 value를 가진 객체를 전달하고 있는 것은 맞지만 엄밀히 말하면 같은 객체는 아닙니다. 사실 <code>Component</code>가 렌더링할 때마다 새로운 객체가 생성됩니다. 그런 다음 이 새로 생긴 객체를 <code>useUser</code> 훅의 인자(argument)로 전달됩니다.<br><code>useEffect</code>가 두 객체를 비교할 때 서로 다른 참조 값(reference) 가지고 있기 때문에 다시 사용자들을 불러오고 새로운 사용자 객체를 state에 저장합니다. 그리고 그 state의 업데이트에 의해 컴포넌트의 리렌더링을 일으키는 것입니다. 무한 반복이 되는 거죠. (Reference는 한 변수를 선언할 때 해당 변수에 생기는 참조 값입니다. 그 변수의 별명 같은 것으로 보면 됩니다. 같아 보이는 두 객체라도 각기 다른 변수에 저장이 되면 각각의 참조 값은 다른 값을 가집니다. - 역주)<br>그러면 어떻게 해결할 수 있을까요?</p><h2 id="--1">해결 방법</h2><p>이제 문제를 파악했으니 해결책을 찾을 수 있습니다.<br>첫번째, 그리고 아마 가장 확실한 방법은 <code>useEffect</code> 디펜던시 배열에서 해당 디펜던시를 제거하고 ESLint 규칙을 무시하고 그냥 넘어가는 것입니다.<br>하지만 이 방법은 잘못된 접근입니다. 이 방법은 앱에 에러와 예기치 못한 동작을 초래할 수 있습니다(그리고 분명 그럴 것입니다). <code>useEffect</code>가 어떻게 동작하는지 더 알고 싶은 분께 Dan Abramov`s <a href="https://overreacted.io/a-complete-guide-to-useeffect/">완벽 가이드</a>를 강력하게 추천합니다.</p><p>그러면 다음 해결책은 뭘까요?</p><p>이 예시 같은 경우에 가장 쉬운 해결책은 <code>{ id: 1 }</code> 객체를 컴포넌트에서 빼내는 것입니다. 이 방법은 그 객체에 동일한 참조 값을 주고 문제를 해결해 줄 것입니다.</p><pre><code class="language-js">const userObject = { id: 1 };
const Component = () =&gt; {
  const user = useUser(userObject);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
export default Component;
</code></pre><p>하지만 이 방법은 언제나 가능한 것은 아닙니다. 사용자 id가 컴포넌트 props 혹은 state에 의존한다고 가정해보겠습니다.</p><p>예를 들어, 사용자 id에 접근하기 위해 URL 파라미터를 사용하는 상황 같은 것일 수 있습니다. 만약 이러한 경우라면 그 객체를 기억하고 동일한 참조 값을 보장해 줄 <code>useMemo</code>를 이용하면 됩니다.</p><pre><code class="language-js">const Component = () =&gt; {
  const { userId } = useParams();
  const userObject = useMemo(() =&gt; {
    return { id: userId };
  }, [userId]); // Don't forget the dependencies here either!
  const user = useUser(userObject);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
export default Component;
</code></pre><p>마지막으로 <code>useUser</code> 훅에 객체 변수를 보내는 대신에 원시 값(primitive value)인 사용자 id만 보내는 게 가능합니다. 이 방법은 <code>useEffect</code> 훅에서 참조 값 동일 문제를 예방할 수 있습니다.</p><pre><code class="language-js">const useUser = (userId) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    fetch('users.json').then((response) =&gt;
      response.json().then((users) =&gt; {
        return setUserData(users.find((item) =&gt; item.id === userId));
      })
    );
  }, [userId]);
  return userData;
};
const Component = () =&gt; {
  const user = useUser(1);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
</code></pre><p>해결됐네요!</p><p>그리고 이 과정을 통해서 ESLint 규칙을 깨뜨릴 필요도 없었네요.</p><p>주의: 커스텀 훅에 보낸 인자가 객체가 아니라 함수라면 무한 리-렌더링을 피하기 위해 비슷한 기술을 사용했을 것입니다. 주요한 차이는 위의 예시에서 사용한 <code>useMemo</code> 대신 <code>useCallback</code>을 사용해야 합니다.</p><p>읽어 주셔서 감사합니다!</p><p>코딩에 대해 궁금하시나요? <a href="https://codesandbox.io/s/useeffect-gotcha-20jw9?file=/src/App.js">여기</a>서 맘껏 코딩해보세요.</p><p><a href="https://twitter.com/iva_kop">Twitter</a>와 개인 <a href="https://whereisthemouse.com/">블로그</a>에 더 많은 React 관련 컨텐츠가 있으니 팔로우하러 놀러오세요.</p><p>이미지 출처 <a href="https://www.freepik.com/vectors/technology">vectorjuice</a></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
