<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>j22_s00.log</title>
        <link>https://velog.io/</link>
        <description>컴공생의 밍기적</description>
        <lastBuildDate>Mon, 19 May 2025 06:04:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. j22_s00.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/j22_s00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[졸프] 전시 스타일을 반영한 나만의 티켓 굿즈 생성]]></title>
            <link>https://velog.io/@j22_s00/%EC%A1%B8%ED%94%84-%EC%A0%84%EC%8B%9C-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%84-%EB%B0%98%EC%98%81%ED%95%9C-%EB%82%98%EB%A7%8C%EC%9D%98-%ED%8B%B0%EC%BC%93-%EA%B5%BF%EC%A6%88-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@j22_s00/%EC%A1%B8%ED%94%84-%EC%A0%84%EC%8B%9C-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%84-%EB%B0%98%EC%98%81%ED%95%9C-%EB%82%98%EB%A7%8C%EC%9D%98-%ED%8B%B0%EC%BC%93-%EA%B5%BF%EC%A6%88-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Mon, 19 May 2025 06:04:29 GMT</pubDate>
            <description><![CDATA[<p>3-2학기, 졸업 프로젝트 스타트를 잘 마치고 여러 회의와 서베이를 거쳐 졸업 프로젝트의 주제를 변경하기로 했다.</p>
<p>이전 주제는 스키밍 장치 탐지를 위한 어플리케이션을 만드는 것으로 이상탐지 모델을 사용하여 구현하려 하였지만, 스키밍 장치에 대한 데이터셋 부족과 스키밍 장치마다 모델을 만들기는 어렵다는 판단이 들어 새로운 프로젝트를 진행하게 되었다.</p>
<p>우리의 새로운 프로젝트는 바로</p>
<h2 id="전시-관람-후-사진과-관람평을-기록하고-나만의-티켓-굿즈를-만들어주는-아카이빙-웹-서비스">전시 관람 후 사진과 관람평을 기록하고 나만의 티켓 굿즈를 만들어주는 아카이빙 웹 서비스</h2>
<p>이다!</p>
<p>이 중, 중요 기능 중 하나인 전시 스타일을 반영하여 나만의 티켓 굿즈 생성하는 기능에서 가장 주요한 부분인 티켓 이미지를 생성하는 부분에 대해 기술하려고 한다.</p>
<p>정확한 목표는 사용자가 찍은 전시회 사진을 입력받아, 해당 전시 스타일을 반영한 티켓(에 들어갈) 이미지를 생성하는 것!</p>
<h3 id="step1-ai-모델-선정하기">Step1. AI 모델 선정하기</h3>
<p>스타일을 반영한 이미지를 생성하기 위해선 style transfer AI 모델을 사용해야하기 때문에 최신 style transfer 모델을 서베이해보았다.
그러다 &#39;Styleshot&#39;이라는 모델을 알게 되었는데</p>
<p><a href="https://styleshot.github.io">styleshot 모델 github.io</a></p>
<p>해당 모델은 두 개의 input 이미지와 prompt를 통해 새로운 이미지를 생성한다.
input 이미지는 style reference와 content reference로 나뉘어져 있다.</p>
<ul>
<li>style reference -&gt; 스타일을 추출</li>
<li>content reference -&gt; 윤곽을 추출
이렇게 두 가지의 이미지에서 각기 다른 요소를 추출하여 합쳐주는 것이 이 styleshot이 작동하는 방식인 것이다.</li>
</ul>
<p>test 해본 결과 
전시회의 스타일을 잘 추출하여 자연스럽게 만들어주는 것이, 우리 프로젝트에서 사용하기에 적합하다고 생각이 들어 모델을 사용할 준비를 하였다.</p>
<h3 id="step2-local에서-ai-모델-사용하기구축하기">Step2. local에서 AI 모델 사용하기/구축하기</h3>
<p>먼저 모델을 다운받아 local에서 잘 돌아가는지 확인하는 작업을 진행하였다.</p>
<h4 id="git-clone">git clone</h4>
<pre><code>git clone https://github.com/Jeoyal/StyleShot.git
cd StyleShot</code></pre><h4 id="환경설정">환경설정</h4>
<pre><code>conda create -n styleshot python==3.8
conda activate styleshot
pip install -r requirements.txt</code></pre><h4 id="pre-trained-weight-다운로드">pre-trained weight 다운로드</h4>
<pre><code>git lfs install
git clone https://huggingface.co/Gaojunyao/StyleShot_lineart</code></pre><p>모델을 inference하기 위해선 pre-trained weight를 따로 다운받아야한다.
styleshot에는 lineart 와 contour 2가지의 모드가 있는데, 이중 우리는 lineart 모드를 사용하기 때문에 해당 모드의 weight만 다운 받았다.</p>
<p>-&gt; 오픈 소스 모델이므로 github에 잘 정리가 되어있어 클론과 환경 설정까지 진행을 한 후 inference하는 파일을 실행해보았다.
하지만,, 에러가 났고 해당 에러는 huggingface에서의 버전이 달라져 생기는 오류였다.</p>
<h4 id="에러가-생기는-파일-수정">에러가 생기는 파일 수정</h4>
<ul>
<li>./ip_adapter/ip_adapter.py</li>
</ul>
<p>해당 파일에서 오류가 되는 부분을 수정해준 뒤, 다시 inference하는 파일을 실행해본 결과 잘 실행이 되었다.</p>
<ul>
<li><p>style reference image
<img src="https://velog.velcdn.com/images/j22_s00/post/e2b573d8-8c35-479a-af91-91813842e992/image.jpeg" alt=""></p>
</li>
<li><p>content reference image
<img src="https://velog.velcdn.com/images/j22_s00/post/03239237-0ab3-4883-8a54-dad5f0a8ed23/image.jpeg" alt=""></p>
</li>
<li><p>output image
<img src="https://velog.velcdn.com/images/j22_s00/post/58757a04-6002-4ab4-8e1c-48459843d7ee/image.png" alt=""></p>
</li>
</ul>
<p>우리가 원하던 대로 전시 스타일을 잘 반영하면서 티켓 이미지에 사용할 사진이 잘 생성이 되었다.</p>
<h3 id="step3-ai-모델-배포하기">step3. AI 모델 배포하기</h3>
<p>local 환경에서 모델이 잘 작동하는 것을 확인했으니, 이제 우리의 웹 서비스에서 사용할 수 있도록 모델을 배포해야한다.
AI 모델을 배포하는 방법은 여러가지가 있는데, 우리 sw구조와 알맞은 방법으로 FastAPI를 통해 AI 서버로 모델을 배포하는 방식을 사용하였다.</p>
<p>memoir의 루트 폴더를 만들어준 뒤,
그 안에 방금 우리가 Styleshot 모델을 사용할 때 다운받아두었던 Styleshot 폴더와 FastAPI로 배포하는 main.py 파일을 넣어준다.</p>
<ul>
<li>main.py<pre><code>import sys
import os
import requests
sys.path.append(os.path.join(os.path.dirname(__file__), &quot;StyleShot&quot;))
import io
import torch
import cv2
import numpy as np
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import JSONResponse
from PIL import Image
from StyleShot.annotator.hed import SOFT_HEDdetector
from StyleShot.annotator.lineart import LineartDetector
from diffusers import UNet2DConditionModel, ControlNetModel
from transformers import CLIPVisionModelWithProjection
from huggingface_hub import snapshot_download
from StyleShot.ip_adapter import StyleShot, StyleContentStableDiffusionControlNetPipeline
</code></pre></li>
</ul>
<p>import boto3
from botocore.exceptions import NoCredentialsError
import uuid</p>
<p>from PIL import Image
from typing import Optional</p>
<p>from dotenv import load_dotenv
load_dotenv()</p>
<p>def upload_to_s3(local_file_path: str, bucket: str, region: str, s3_key_prefix: str = &quot;&quot;) -&gt; str:
    s3 = boto3.client(&#39;s3&#39;)
    filename = f&quot;{s3_key_prefix}{uuid.uuid4().hex}.png&quot;
    try:
        s3.upload_file(local_file_path, bucket, filename)
        return f&quot;https://{bucket}.s3.{region}.amazonaws.com/{filename}&quot;
    except NoCredentialsError:
        raise RuntimeError(&quot;S3 credentials not found.&quot;)</p>
<p>app = FastAPI()</p>
<h1 id="-모델-초기화-">=== 모델 초기화 ===</h1>
<p>base_model_path = &quot;models/stable-diffusion-v1-5&quot;
transformer_block_path = &quot;models/CLIP-ViT-H-14-laion2B-s32B-b79K&quot;</p>
<p>device = &quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;
styleshot_models = {}</p>
<p>def load_styleshot(preprocessor: str):
    if preprocessor in styleshot_models:
        return styleshot_models[preprocessor]</p>
<pre><code>if preprocessor == &quot;Lineart&quot;:
    detector = LineartDetector()
    styleshot_model_path = &quot;models/StyleShot_lineart&quot; 
elif preprocessor == &quot;Contour&quot;:
    detector = SOFT_HEDdetector()
    styleshot_model_path = &quot;Gaojunyao/StyleShot&quot;
else:
    raise ValueError(&quot;Invalid preprocessor&quot;)

# 모델 다운로드
if not os.path.isdir(styleshot_model_path):
    snapshot_download(styleshot_model_path, local_dir=styleshot_model_path)

if not os.path.isdir(base_model_path):
    snapshot_download(base_model_path, local_dir=base_model_path)

if not os.path.isdir(transformer_block_path):
    snapshot_download(transformer_block_path, local_dir=transformer_block_path)

ip_ckpt = os.path.join(styleshot_model_path, &quot;pretrained_weight/ip.bin&quot;)
style_aware_encoder_path = os.path.join(styleshot_model_path, &quot;pretrained_weight/style_aware_encoder.bin&quot;)

unet = UNet2DConditionModel.from_pretrained(base_model_path, subfolder=&quot;unet&quot;)
content_fusion_encoder = ControlNetModel.from_unet(unet)

pipe = StyleContentStableDiffusionControlNetPipeline.from_pretrained(
    base_model_path,
    controlnet=content_fusion_encoder
)

styleshot = StyleShot(device, pipe, ip_ckpt, style_aware_encoder_path, transformer_block_path)
styleshot_models[preprocessor] = (styleshot, detector)

return styleshot, detector</code></pre><h1 id="-api-엔드포인트-">=== API 엔드포인트 ===</h1>
<p>@app.post(&quot;/generate/&quot;)
async def generate_image(
    preprocessor: str = Form(...),
    style_url: str = Form(...),
    content_url: str = Form(...),
    prompt: Optional[str] = Form(None)
):
    if prompt is None:
        prompt = &quot;default prompt&quot;  # 또는 그냥 빈 문자열로 처리</p>
<pre><code>styleshot, detector = load_styleshot(preprocessor)

# 스타일 이미지 다운로드
style_response = requests.get(style_url)
style_response.raise_for_status()
style_image = Image.open(io.BytesIO(style_response.content)).convert(&quot;RGB&quot;)

# 콘텐츠 이미지 다운로드
content_response = requests.get(content_url)
content_response.raise_for_status()
content_array = np.frombuffer(content_response.content, np.uint8)
content_image = cv2.imdecode(content_array, cv2.IMREAD_COLOR)
content_image = cv2.cvtColor(content_image, cv2.COLOR_BGR2RGB)
processed_content = detector(content_image)
processed_content = Image.fromarray(processed_content)

# 추론
result = styleshot.generate(style_image=style_image, content_image=processed_content)
output_image = result[0][0]

# 결과 반환
img_bytes = io.BytesIO()
output_image.save(img_bytes, format=&#39;PNG&#39;)
img_bytes.seek(0)

output_path = f&quot;/tmp/{uuid.uuid4().hex}.png&quot;
output_image.save(output_path)  # 파일 저장

# S3 업로드
s3_url = upload_to_s3(
    local_file_path=output_path,
    bucket=&quot;hukmemoirbucket&quot;,
    region=&quot;ap-northeast-2&quot;,
    s3_key_prefix=&quot;results/&quot;
)
os.remove(output_path)  # 서버 공간 정리

return JSONResponse(content={&quot;s3_url&quot;: s3_url})</code></pre><p>@app.post(&quot;/test-upload/&quot;)
def test_s3_upload():
    # 1. 더미 이미지 생성
    dummy_image = Image.new(&quot;RGB&quot;, (256, 256), color=&quot;blue&quot;)
    temp_path = &quot;test_result.png&quot;
    dummy_image.save(temp_path)</p>
<pre><code># 2. S3 업로드
try:
    s3_url = upload_to_s3(
        local_file_path=temp_path,
        bucket=&quot;hukmemoirbucket&quot;,
        region=&quot;ap-northeast-2&quot;,
        s3_key_prefix=&quot;test/&quot;
    )
    os.remove(temp_path)  # 업로드 후 파일 정리
    return {&quot;s3_url&quot;: s3_url}
except Exception as e:
    return {&quot;error&quot;: str(e)}</code></pre><pre><code>
이 main.py 파일에서는 모델을 FastAPI로 배포해주고, 이미지를 받아 티켓 이미지를 생성하게 되면 이를 S3에 저장하고 해당 url를 반환해주는 코드까지 구현되어있다.

이때 local에서와 다른 점은 이 memoir를 local에서 돌렸던 상태로 git에 올리게 되면 너무나도 큰 용량 때문에 올라가지 않게 된다.
이를 해결하기 위해 아까 다운받은 pre-trained weight 파일들은 .gitignore 처리를 한 후
ec2 서버에서 위의 모델들을 다시 다운 받을 수 있게 하는 코드를 작성해둔다.

- download_weights.sh</code></pre><p>#!/bin/bash</p>
<p>set -e  # 에러 발생 시 중단</p>
<p>echo &quot;🔽 StyleShot 모델 및 종속 모델 다운로드 시작...&quot;</p>
<h1 id="기본-디렉토리-구조-설정">기본 디렉토리 구조 설정</h1>
<p>MODEL_DIR=&quot;models&quot;
mkdir -p $MODEL_DIR</p>
<h1 id="huggingface-cli-설치-확인">huggingface-cli 설치 확인</h1>
<p>if ! command -v huggingface-cli &amp;&gt; /dev/null; then
  echo &quot; huggingface-cli가 설치되어 있지 않습니다. 설치 중...&quot;
  pip install -q huggingface_hub
fi</p>
<h1 id="git-lfs-설치-확인">git-lfs 설치 확인</h1>
<p>if ! command -v git-lfs &amp;&gt; /dev/null; then
  echo &quot; git-lfs가 설치되어 있지 않습니다. 설치 중...&quot;
  sudo apt-get update &amp;&amp; sudo apt-get install -y git-lfs
  git lfs install
fi</p>
<h1 id="모델-리스트">모델 리스트</h1>
<p>declare -A models=(
  [&quot;StyleShot_lineart&quot;]=&quot;Gaojunyao/StyleShot_lineart&quot;
  [&quot;StyleShot_contour&quot;]=&quot;Gaojunyao/StyleShot&quot;
  [&quot;stable-diffusion-v1-5&quot;]=&quot;runwayml/stable-diffusion-v1-5&quot;
  [&quot;CLIP-ViT-H-14-laion2B-s32B-b79K&quot;]=&quot;laion/CLIP-ViT-H-14-laion2B-s32B-b79K&quot;
)</p>
<h1 id="모델-다운로드">모델 다운로드</h1>
<p>for dir in &quot;${!models[@]}&quot;; do
  path=&quot;$MODEL_DIR/$dir&quot;
  repo=&quot;${models[$dir]}&quot;</p>
<p>  if [ ! -d &quot;$path&quot; ]; then
    echo &quot; $repo → $path&quot;
    python3 -c &quot;from huggingface_hub import snapshot_download; snapshot_download(repo_id=&#39;$repo&#39;, local_dir=&#39;$path&#39;, local_dir_use_symlinks=False)&quot;
  else
    echo &quot; $repo 이미 존재: $path&quot;
  fi
done</p>
<p>echo &quot; 모든 모델 다운로드 완료!&quot;</p>
<p>```</p>
<p>이렇게 해두면 git에는 base 모델이 올라가있지 않지만, ec2 서버에서 git clone을 받은 수 download_weights.sh을 실행시키면 자동으로 models 폴더가 만들어지고 여기에 필요한 모델들이 다운로드 된다. 
이렇게 처음 모델을 다운시켜두고 한번 inference를 하게 되면 그 다음부터는 checkpoint가 생겨 빠르게 이미지를 생성할 수 있게 된다.</p>
<h3 id="ec2-서버에서-실행해보기">ec2 서버에서 실행해보기</h3>
<p>이제 github에 올린 memoir 폴더를 ec2 서버에서 클론 받은 뒤
FastAPI로 배포한 styleshot을 실제 ec2 서버에서 실행시켜보았을 때
잘 실행이 되었고 생성된 이미지가 S3에 저장이 되고 해당 url를 return 하는 것까지 잘 실행되는 것이 확인되었다</p>
<p>이후 이를 docker로 묶어 배포하는 것은 다른 팀원의 역할이였기에 이 상태로 넘겨주며 프로젝트를 마치게 되었다.</p>
<p>작성자 : (Team 28 HUK) 2171008 김지수</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[졸프] 해외 안심 거래 서비스를 만들어보자!]]></title>
            <link>https://velog.io/@j22_s00/%EC%A1%B8%ED%94%84-%ED%95%B4%EC%99%B8-%EC%95%88%EC%8B%AC-%EA%B1%B0%EB%9E%98-%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@j22_s00/%EC%A1%B8%ED%94%84-%ED%95%B4%EC%99%B8-%EC%95%88%EC%8B%AC-%EA%B1%B0%EB%9E%98-%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 26 Nov 2024 02:50:53 GMT</pubDate>
            <description><![CDATA[<p>24-2학기, 이화여자대학교에서 졸업프로젝트를 시작하게 되었다.
졸업프로젝트로 만들게 된 것은 해외 안심 거래 서비스!
간단하게 두 개의 서비스를 제공하는 어플을 만드는 것이다.</p>
<ol>
<li>ATM 기기의 스키밍 장치 탐지</li>
<li>실시간 거래 내역 중 이상거래 탐지</li>
</ol>
<p>이 중, 내가 맡은 부분은 ATM 기기의 스키밍 장치를 탐지하는 것.</p>
<p>ATM 기기의 사진을 학습시켜, 이상탐지 알고리즘을 통해 스키밍 장치를 탐지하는 것이다.</p>
<p>좀 더 심화된 모델링은 다음학기 때 구현하는 것이기에,
이번 학기에는 어느정도 스키밍 장치를 탐지할 수 있는지에 대한 여부를 확인해보았다.</p>
<h3 id="step1-데이터셋-구하기">step1, 데이터셋 구하기</h3>
<p>ATM 기기에 대한 데이터셋을 찾아보았지만 인터넷 상에서 구하기가 어려웠고, ATM기기마다 카드 투입구 부분이 달랐기에 먼저, 하나의 ATM기기만을 학습시켰을 때 스키밍 장치를 잘 탐지할 수 있는지에 대해 확인해보았다.
학교 근처 ATM기기를 정해 약 200개의 사진을 직접 찍었고, 각 이미지당 5종류의 Augmentation을 통해 약 1400장의 데이터셋을 확보하였다.</p>
<h3 id="step2-ai-모델-정하기">step2, AI 모델 정하기</h3>
<p>여러 이상탐지 알고리즘 중, 먼저 쉽게 구현할 수 있는 Autoencoder를 먼저 시도해보았다. 메모리 기반의 MemSeg 모델을 사용해볼까도 고민해보았지만, 먼저 Autoencoder로 탐지를 해본 후, 필요하다면 시도해보기로 하였다.
Autoencoder 모델은 정상 데이터만을 학습을 한 후, 복원 오차를 기준으로 이상 데이터를 탐지할 수 있기에 스키밍 장치의 사진을 구하기 어려운 우리에겐 너무 좋은 모델이였다.</p>
<h4 id="autoencoder">Autoencoder</h4>
<p>Autoencoder는 encoder 부분과 decoder부분으로 나눠진다. 
encoder 부분에서는 입력 이미지를 저차원으로 압축하여, 특정 feature들을 추출하는 것이고
decoder 부분에서는 추출한 feature를 통해 원래 이미지를 복원하는 것이다.</p>
<h3 id="step3-코드-구현">step3, 코드 구현</h3>
<p>colab에서 코드를 구현하였기에, 1400장의 데이터셋을 가져올 방법이 필요했다.
이를 위해 kaggle API를 사용하였다.
kaggle에 데이터셋을 올리고 API를 가져와 colab에서도 우리가 준비한 데이터셋을 사용할 수 있었다.</p>
<h4 id="전처리-과정">전처리 과정</h4>
<pre><code>import os
from torchvision import transforms
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import matplotlib.pyplot as plt

# 데이터셋 정의
class ATMDataset(Dataset):
    def __init__(self, folder_path, transform=None):
        self.folder_path = folder_path
        self.image_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.endswith((&#39;.png&#39;, &#39;.jpg&#39;, &#39;.jpeg&#39;))]
        self.transform = transform

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        img_path = self.image_paths[idx]
        image = Image.open(img_path).convert(&quot;L&quot;)  # 흑백 변환 (1채널)
        if self.transform:
            image = self.transform(image)
        return image

# 데이터 경로
data_dir = &quot;/root/.cache/kagglehub/datasets/j22s00/atm-cardslot/versions/1/ATM&quot;  # Kaggle 데이터셋이 저장된 폴더 경로

# 전처리 파이프라인
transform = transforms.Compose([
    transforms.Resize((128, 128)),  # 이미지 크기 조정
    transforms.ToTensor(),          # Tensor로 변환
    transforms.Normalize(mean=[0.5], std=[0.5])  # 정규화
])

# 데이터셋 및 데이터로더 생성
dataset = ATMDataset(data_dir, transform=transform)
data_loader = DataLoader(dataset, batch_size=32, shuffle=True)

# 데이터 시각화
def visualize_sample(dataset, num_samples=5):
    plt.figure(figsize=(12, 6))
    for i in range(num_samples):
        image = dataset[i]
        plt.subplot(1, num_samples, i + 1)
        plt.imshow(image.squeeze(0), cmap=&quot;gray&quot;)
        plt.axis(&quot;off&quot;)
    plt.show()

visualize_sample(dataset)
</code></pre><h4 id="모델-정의">모델 정의</h4>
<pre><code>import torch
import torch.nn as nn

# Autoencoder 모델 정의
class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()
        # Encoder
        self.encoder = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU()
        )
        # Decoder
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(32, 16, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(16, 1, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.Sigmoid()
        )

    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x
</code></pre><h4 id="모델-학습">모델 학습</h4>
<pre><code>import torch
import torch.nn as nn

# Autoencoder 모델 정의
class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()
        # Encoder
        self.encoder = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU()
        )
        # Decoder
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(32, 16, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(16, 1, kernel_size=3, stride=2, padding=1, output_padding=1),
            nn.Sigmoid()
        )

    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x
</code></pre><p>의 코드를 통해 모델을 학습시키고 포토샵으로 스키밍 이미지를 만들어 테스트를 진행해보았다.</p>
<h4 id="테스트-결과">테스트 결과</h4>
<p>하지만 결과는 처참하였다. 스키밍 사진은 탐지하지 못하였고, 오히려 각도가 반대되었던 사진만을 이상탐지한 것이다.</p>
<p>이러한 문제의 원인은
<strong>카트 투입구 부분을 attention하지 않았기에 주변 배경까지도 가중치를 두고 학습이 되었기 때문</strong>이라고 분석하였다.</p>
<h3 id="step4-이상탐지를-잘-하기-위한-수정">step4, 이상탐지를 잘 하기 위한 수정</h3>
<p>이를 해결하기 위해 카드 투입구 부분을 YOLO 모델을 사용하여 탐지하여 crop하려고 하였지만, YOLO 모델에 카드 투입구 부분을 또한 학습 시켜야했기 때문에 현재로썬 쉽지 않다고 생각이 되었고, 이는 다음 학기의 그로쓰 때 구현하기로 결정하였다...</p>
<h4 id="새로운-데이터셋-구축">새로운 데이터셋 구축</h4>
<p>그렇기에 &#39;다시&#39; 데이터셋을 구하기 위해 근처 ATM기기에서 이번엔 카드 투입구 부분만을 확대하여 200장 정도의 사진을 찍어서 준비하였다.</p>
<h4 id="새로운-모델">새로운 모델</h4>
<p>현재 사용하고 있는 autoencoder 모델이 이상탐지를 잘 못하는 것 같아, 데이터셋의 수도 적어진 것을 감안하여 모델과 학습 방법을 바꾸었다.</p>
<h4 id="데이터-전처리">데이터 전처리</h4>
<pre><code>import os
import numpy as np
import cv2
import tensorflow as tf
from tensorflow.keras.preprocessing import image
from sklearn.model_selection import train_test_split

# 데이터셋 경로 설정
dataset_path = &#39;/content/drive/MyDrive/atm&#39;

# 이미지 읽어오기
images = []
for filename in os.listdir(dataset_path):
    if filename.endswith(&#39;.jpg&#39;) or filename.endswith(&#39;.png&#39;):
        img = cv2.imread(os.path.join(dataset_path, filename))
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 색상 채널 변환
        img = cv2.resize(img, (128, 128))  # 이미지 크기 조정
        images.append(img)

# 이미지 데이터를 numpy 배열로 변환
images = np.array(images) / 255.0  # 0-1 범위로 정규화

# 훈련 및 검증 데이터셋 나누기
X_train, X_test = train_test_split(images, test_size=0.2, random_state=42)
</code></pre><h4 id="모델-정의-1">모델 정의</h4>
<pre><code>from tensorflow.keras import layers, models

# 모델 정의
input_img = layers.Input(shape=(128, 128, 3))

# 인코더 부분
x = layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(input_img)
x = layers.MaxPooling2D((2, 2), padding=&#39;same&#39;)(x)
x = layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(x)
encoded = layers.MaxPooling2D((2, 2), padding=&#39;same&#39;)(x)

# 디코더 부분
x = layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(encoded)
x = layers.UpSampling2D((2, 2))(x)
x = layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(x)
x = layers.UpSampling2D((2, 2))(x)
decoded = layers.Conv2D(3, (3, 3), activation=&#39;sigmoid&#39;, padding=&#39;same&#39;)(x)

# 모델 생성
autoencoder = models.Model(input_img, decoded)

# 모델 컴파일
autoencoder.compile(optimizer=&#39;adam&#39;, loss=&#39;mean_squared_error&#39;)

# 모델 구조 요약
autoencoder.summary()
</code></pre><h4 id="모델-학습-1">모델 학습</h4>
<pre><code># Autoencoder 학습
autoencoder.fit(X_train, X_train, epochs=50, batch_size=16, validation_data=(X_test, X_test))
</code></pre><h4 id="최종-test-결과">최종 test 결과</h4>
<p>이렇게 학습한 모델에 총 3개의 test 이미지를 넣어보았다.
이 이미지들은 스키밍 장치를 포토샵으로 가져온 이미지이다.</p>
<h4 id="테스트-이미지-전처리-및-테스트">테스트 이미지 전처리 및 테스트</h4>
<pre><code>import os
import numpy as np
import cv2
from tensorflow.keras.preprocessing import image
import matplotlib.pyplot as plt

# 테스트 데이터 경로
test_dataset_path = &#39;/content/test_dir&#39;

# 이미지 파일 리스트 가져오기
test_images = [f for f in os.listdir(test_dataset_path) if f.endswith((&#39;.jpg&#39;, &#39;.jpeg&#39;, &#39;.png&#39;, &#39;.bmp&#39;, &#39;.tiff&#39;))]
print(f&quot;Found {len(test_images)} images for testing.&quot;)

# 이미지 로드 및 전처리 함수
def load_and_preprocess_image(image_path):
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 색상 채널 변환
    img = cv2.resize(img, (128, 128))  # 모델에 맞는 크기로 조정
    img = np.array(img) / 255.0  # 0-1 범위로 정규화
    return img

# 테스트 이미지 처리
for test_image in test_images:
    image_path = os.path.join(test_dataset_path, test_image)

    # 이미지를 로드하고 전처리
    img = load_and_preprocess_image(image_path)
    img_batch = np.expand_dims(img, axis=0)  # 배치 차원 추가

    # 모델 예측 (복원된 이미지 계산)
    reconstructed_img = autoencoder.predict(img_batch)

    # 원본 이미지와 복원된 이미지 출력
    fig, axes = plt.subplots(1, 2, figsize=(10, 5))

    # 원본 이미지 출력
    axes[0].imshow(img)
    axes[0].set_title(f&quot;Original: {test_image}&quot;)
    axes[0].axis(&#39;off&#39;)

    # 복원된 이미지 출력
    axes[1].imshow(reconstructed_img[0])  # 배치 차원 제거
    axes[1].set_title(f&quot;Reconstructed: {test_image}&quot;)
    axes[1].axis(&#39;off&#39;)

    plt.show()
</code></pre><h4 id="임계값-설정">임계값 설정</h4>
<pre><code>import numpy as np

# 훈련 데이터의 평균 재구성 오차와 표준편차를 구해서 임계값 설정
train_reconstruction_errors = []  # 훈련 중 재구성 오차 리스트

for img in X_train:
    reconstructed_img = autoencoder.predict(np.expand_dims(img, axis=0))
    error = np.mean(np.square(img - reconstructed_img[0]))  # MSE 계산
    train_reconstruction_errors.append(error)

# 평균과 표준편차 구하기
mean_error = np.mean(train_reconstruction_errors)
std_error = np.std(train_reconstruction_errors)

# 임계값을 평균 + 2*표준편차로 설정 (재구성 오차가 크게 나올 확률이 낮은 값)
threshold = mean_error + 2 * std_error

# 이상 여부 판단
for test_image in test_images:
    image_path = os.path.join(test_dataset_path, test_image)

    # 이미지를 로드하고 전처리
    img = load_and_preprocess_image(image_path)
    img_batch = np.expand_dims(img, axis=0)  # 배치 차원 추가

    # 모델 예측 (복원된 이미지 계산)
    reconstructed_img = autoencoder.predict(img_batch)

    # 재구성 오차 계산
    reconstruction_error = calculate_reconstruction_error(img, reconstructed_img[0])

    # 재구성 오차 출력
    print(f&quot;Image: {test_image}, Reconstruction Error: {reconstruction_error:.4f}&quot;)

    # 이상 여부 판단
    if reconstruction_error &gt; threshold:
        print(f&quot;Image: {test_image} is Anomalous.&quot;)
    else:
        print(f&quot;Image: {test_image} is Normal.&quot;)

    # 원본 이미지와 복원된 이미지 출력
    fig, axes = plt.subplots(1, 2, figsize=(10, 5))

    # 원본 이미지 출력
    axes[0].imshow(img)
    axes[0].set_title(f&quot;Original: {test_image}&quot;)
    axes[0].axis(&#39;off&#39;)

    # 복원된 이미지 출력
    axes[1].imshow(reconstructed_img[0])  # 배치 차원 제거
    axes[1].set_title(f&quot;Reconstructed: {test_image}&quot;)
    axes[1].axis(&#39;off&#39;)

    plt.show()</code></pre><p><strong>이 결과로 3개의 테스트 이미지 중, 하나의 이미지를 이상데이터로 잘 탐지했다는 것을 알 수 있었다.</strong></p>
<p>아직은, 성능이 부족하지만 이후로 다양한 하이퍼파라미터와 코드 수정 등을 통해 스키밍 장치를 잘 탐지하는 모델을 만들어보려고 한다.</p>
<h3 id="step5-이상데이터-모두를-잘-이상데이터로-탐지하기-위한-수정">step5, 이상데이터 모두를 잘 이상데이터로 탐지하기 위한 수정</h3>
<p>추가 성능 향상을 위해</p>
<ol>
<li>모델 복잡도를 줄이고</li>
<li>학습 데이터에 노이즈를 포함한 이미지를 입력하였다.</li>
</ol>
<p>그 이유는 Autoencoder가 너무 복잡하다면 데이터의 세부적인 노이즈까지 복원할 수 있기에 이를 방지하고 노이즈를 포함한 이미지를 입력으로 주었을 때, 깨끗한 정상 데이터를 복원하도록 훈련한다면 이상 데이터가 학습되지 않도록 할 수 있기 때문이다.</p>
<p>따라서,</p>
<h4 id="모델-정의-2">모델 정의</h4>
<pre><code>from tensorflow.keras import layers, models

# 간단한 Autoencoder 모델 정의
input_img = layers.Input(shape=(128, 128, 3))

# 인코더
x = layers.Conv2D(16, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(input_img)
x = layers.MaxPooling2D((2, 2), padding=&#39;same&#39;)(x)
x = layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(x)
encoded = layers.MaxPooling2D((2, 2), padding=&#39;same&#39;)(x)

# 디코더
x = layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(encoded)
x = layers.UpSampling2D((2, 2))(x)
x = layers.Conv2D(16, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)(x)
x = layers.UpSampling2D((2, 2))(x)
decoded = layers.Conv2D(3, (3, 3), activation=&#39;sigmoid&#39;, padding=&#39;same&#39;)(x)

# 모델 생성
autoencoder = models.Model(input_img, decoded)
autoencoder.compile(optimizer=&#39;adam&#39;, loss=&#39;mean_squared_error&#39;)
</code></pre><h4 id="모델-학습-2">모델 학습</h4>
<pre><code># 훈련 데이터에 랜덤 노이즈 추가
noisy_X_train = X_train + np.random.normal(loc=0.0, scale=0.1, size=X_train.shape)
noisy_X_train = np.clip(noisy_X_train, 0., 1.)  # 픽셀 값을 [0, 1]로 클리핑

# Autoencoder 학습
autoencoder.fit(noisy_X_train, X_train, epochs=50, batch_size=16, validation_split=0.2)
</code></pre><p>이렇게 수정한 후, 테스트 부분에선 임계값을 수정하는 부분을 빼고 테스트를 한 결과</p>
<p><strong>3개의 이상데이터 모두 이상 데이터로 잘 탐지한 결과를 얻을 수 있었다!!</strong></p>
<p>이젠 정상데이터도 테스트를 하였을 때, 정상 데이터로 잘 탐지하는지를 추가로 테스트 시작하였다.
테스트셋엔 이젠, 이상데이터 3개와 정상데이터 6개를 넣었을 때, 테스트를 해보았지만
이상데이터 3개는 이상데이터로 하였지만
정상데이터에선 3개는 정상으로, 3개는 이상으로 탐지하였다.</p>
<p>이후 정상데이터 또한, 정상 데이터라고 잘 탐지할 수 있도록 성능을 향상시켜보려고 한다.</p>
<h3 id="step6-정상데이터-또한-정상데이터로-탐지하기-위한-수정">step6, 정상데이터 또한 정상데이터로 탐지하기 위한 수정</h3>
<p>단순히 복원 오차(MSE)만으로는 정상과 이상 데이터를 완벽히 구분하기에는 어려울 수 있기에 추가적인 분석 방법인 SSIM을 계산하였다. 이는 복원된 이미지와 입력 이미지 간의 구조적 유사성을 계산하는 것으로</p>
<p>먼저 모델을 학습시킨 후, 학습한 데이터를 기반으로 SSIM 임계값을 설정해준다.</p>
<h4 id="훈련-데이터-기반-ssim-임계값-설정">훈련 데이터 기반 SSIM 임계값 설정</h4>
<pre><code># 훈련 데이터에서 SSIM 계산
train_ssim_scores = []
for img in X_train:
    reconstructed_img = autoencoder.predict(np.expand_dims(img, axis=0))
    ssim_score = calculate_ssim(img, reconstructed_img[0])
    train_ssim_scores.append(ssim_score)

# 평균과 표준편차 계산
mean_ssim = np.mean(train_ssim_scores)
std_ssim = np.std(train_ssim_scores)

# SSIM 임계값 설정 (평균 - 2*표준편차)
ssim_threshold = mean_ssim - 2 * std_ssim
print(f&quot;SSIM Threshold: {ssim_threshold}&quot;)</code></pre><h4 id="이상탐지-테스트">이상탐지 테스트</h4>
<pre><code>for test_image in test_images:
    image_path = os.path.join(test_dataset_path, test_image)

    # 이미지 로드 및 전처리
    img = load_and_preprocess_image(image_path)
    img_batch = np.expand_dims(img, axis=0)  # 배치 차원 추가

    # 복원된 이미지 계산
    reconstructed_img = autoencoder.predict(img_batch)

    # 재구성 오차 (MSE) 계산
    reconstruction_error = calculate_reconstruction_error(img, reconstructed_img[0])

    # SSIM 계산
    ssim_score = calculate_ssim(img, reconstructed_img[0])

    # 이상 여부 판단
    if reconstruction_error &gt; threshold or ssim_score &lt; ssim_threshold:
        print(f&quot;Image {test_image} is Anomalous.&quot;)
    else:
        print(f&quot;Image {test_image} is Normal.&quot;)
</code></pre><p>이후 이를 고려하여 이상탐지를 진행하였고
그 결과</p>
<p><strong>3개의 이상데이터는 이상데이터로, 
6개의 정상데이터는 정상데이터로 잘 탐지하였다.</strong></p>
<p>이로써 ATM기기의 스키밍 장치를 잘 탐지할 수 있는 모델을 완성하였고
이후엔 더욱 많은 데이터셋을 훈련 및 테스트해보며 더욱 성능을 향상 시켜보려 한다.</p>
<p>작성자 : (Team 28 HUK) 2171008 김지수</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[9장. 애플리케이션 설계]]></title>
            <link>https://velog.io/@j22_s00/9%EC%9E%A5.-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@j22_s00/9%EC%9E%A5.-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 06 Apr 2024 08:31:24 GMT</pubDate>
            <description><![CDATA[<p>[몽고DB 완벽 가이드] 책을 보고 정리한 내용입니다.</p>
<h3 id="스키마-설계-고려-사항">스키마 설계 고려 사항</h3>
<ul>
<li>제약 사항</li>
<li>쿼리 및 쓰기의 접근 패턴</li>
<li>관계 유형</li>
<li>카디널리티</li>
</ul>
<h4 id="스키마-설계-패턴">스키마 설계 패턴</h4>
<ul>
<li>다형성 패턴 : 컬렉션 내 모든 도큐먼트가 유사하지만 동일하지 않는 구조를 가질 때 적합</li>
<li>속성 패턴 : 정렬하거나 쿼리하려는 도큐먼트에 필드의 서브셋이 있는 경우, 정렬하려는 필드가 도큐먼트의 서브셋에만 존재하는 경우에 적합</li>
<li>버킷 패턴 : 데이터가 일정 기간 동안 스트림으로 유입되는 시계열 데이터에 적합</li>
<li>이상치 패턴 : 드물게 도큐먼트의 쿼리가 애플리케이션의 정상적인 패턴을 벗어날 때 사용</li>
<li>계산된 패턴 : 데이터를 자주 계산해야 할 때나 데이터 접근 패턴이 읽기 집약적일 때 사용</li>
<li>서브셋 패턴 : 장비의 램 용량을 초과하는 작업 셋이 있을 때 사용</li>
<li>확장된 참조 패턴 : 각각 고유한 컬렉션이 있는 여러 논리 엔티티 또는 &#39;사물&#39;이 있고 특정 기능을 위해 엔티티들을 모을 때 사용</li>
<li>근사 패턴 : 리소스가 많이 드는 계산이 필요하지만 높은 정확도가 반드시 필요하지는 않은 상황에 유용</li>
<li>트리 패턴 : 쿼리가 많고 구조적으로 주로 계층적인 데이터가 았을 때 적용</li>
<li>사전 할당 패턴 : 주로 MMAP 스토리지 엔진과 함께 사용</li>
<li>도큐먼트 버전 관리 패턴 : 도큐먼트의 이전 버전을 유지하는 ㅐㅁ커니즘을 제공</li>
</ul>
<h3 id="정규화-vs-비정규화">정규화 vs 비정규화</h3>
<p>데이터를 얼마나 정규화 할지는 늘 중요한 문제이다.
정규화 : 컬렉션 간의 참조를 이용해 데이터를 여러 컬렉션으로 나누는 작업.
비정규화 : 모든 데잍를 하나의 도큐먼트에 내장하는 것.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[3장. 도큐먼트 생성, 갱신, 삭제]]></title>
            <link>https://velog.io/@j22_s00/3%EC%9E%A5.-%EB%8F%84%ED%81%90%EB%A8%BC%ED%8A%B8-%EC%83%9D%EC%84%B1-%EA%B0%B1%EC%8B%A0-%EC%82%AD%EC%A0%9C</link>
            <guid>https://velog.io/@j22_s00/3%EC%9E%A5.-%EB%8F%84%ED%81%90%EB%A8%BC%ED%8A%B8-%EC%83%9D%EC%84%B1-%EA%B0%B1%EC%8B%A0-%EC%82%AD%EC%A0%9C</guid>
            <pubDate>Sat, 06 Apr 2024 08:13:04 GMT</pubDate>
            <description><![CDATA[<p>[몽고DB 완벽 가이드] 책을 보고 정리한 내용입니다.</p>
<h3 id="도큐먼트-삽입">도큐먼트 삽입</h3>
<p>몽고DB에 데이터를 추가하는 기본 방법. insertOne 메서드 사용.</p>
<pre><code>&gt; db.movies.insertOne({&quot;title&quot; : &quot;Stand my Me&quot;})</code></pre><p>도큐먼트에 &quot;_id&quot; 키가 추가되고 도큐먼트가 몽고DB에 저장된다.</p>
<p>여러 도큐먼트를 컬렉션에 추가하려면 insertMany로 도큐먼트 배열을 전달한다. </p>
<pre><code>&gt; db.movies.insertMany([{&quot;title&quot; : &quot;Ghostbusters&quot;},
                        {&quot;title&quot; : &quot;E.T.&quot;},
                        {&quot;title&quot; : &quot;Blade Runner&quot;}]);</code></pre><p>** 48메가바이트보다 큰 메세지는 허용하지 않아, 시도할 시 일괄 삽입 여러 개로 분할된다.</p>
<p>이 때, 중간 도큐먼트에서 오류가 발생할 시,정렬 연산/비정렬 연산을 선택했는지에 따라 발생하는 상황이 달라진다.
insertMany에 대한 두 번째 매개변수로 옵션 도큐먼트를 지정할 수 있는데, &quot;ordered&quot;키에 true(기본값)를 지정하면 도큐먼트가 제공된 순서대로 삽입이 되고 flase를 지정하면 삽입을 재배열할 수 있다. 즉, 재배열이 가능하다면 오류가 난 것만 제외하고 삽입이 되지만, 재배열이 되지 않을 시 오류가 난 도큐먼트부터는 삽입이 되지 않는다.</p>
<h3 id="도큐먼트-삭제">도큐먼트 삭제</h3>
<p>deleteOne과 deleteMany 메서드 사용. 필터 도큐먼트를 첫 번째 매개변수로 사용.</p>
<pre><code>&gt; db.movies.deleteOne({&quot;_id&quot; : 4})</code></pre><p>필터 도큐먼트에 해당하는 도큐먼트가 여러개일 땐,
deleteOne은 첫 번째 도큐먼트만, deleteMany는 모두 제거된다.</p>
<p>전체 컬렉션을 삭제하기 위해선 drop을 사용하는게 편리하다.</p>
<pre><code>&gt; db.movies.drop()</code></pre><p>데이터는 한 번 제거하면 영원히 사라진다.</p>
<h3 id="도큐먼트-갱신">도큐먼트 갱신</h3>
<p>updateOne, updateMany, replaceOne과 같은 메서드들을 사용한다.
updateOne, updateMany는 필터 도큐먼트를 첫 번째 매개변수로, 변경 사항을 설명하는 수정자 도큐먼트를 두 번째 매개변수로 사용한다.
replaceOne도 첫 번째 매개변수로는 필터를 사용하지만 두 번째 매개변수는 필터와 일치하는 도큐먼트를 교체할 도큐먼트라는 점이 차이점이다.</p>
<h4 id="도큐먼트-치환">도큐먼트 치환</h4>
<pre><code>{
    &quot;_id&quot; : ObjectId(/*생략*/),
    &quot;name&quot; : &quot;joe&quot;,
    &quot;friends&quot; : 32,
    &quot;enemies&quot; 2
}</code></pre><pre><code>&gt; var joe = db.users.findOne({&quot;name&quot; : &quot;joe&quot;});
&gt; joe.relationships = {&quot;friends&quot; : joe.friends, &quot;enemies&quot;: joe.enemies);
&gt; joe.username = joe.name;
&gt; delete joe.friends;
&gt; delete joe.enemies;
&gt; delete joe.name;
db.user.replaceOne({&quot;name&quot; : &quot;joe&quot;}, joe);</code></pre><p>도큐먼트 치환할 때에는, 고유한 도큐먼트를 갱신 대상으로 지정하는 것이 좋다. ex. &quot;_id&quot; 키로 일치하는 고유한 도큐먼트를 지정</p>
<h4 id="갱신-연산자">갱신 연산자</h4>
<p>도큐먼트의 특정 부분만 갱신하는 경우가 많다. 갱신 연산자를 사용한다. </p>
<pre><code>&gt; db.analytics.updateOne({&quot;url&quot; : &quot;www.example.com&quot;},
... {&quot;$inc&quot; : {&quot;pageviews&quot; : 1}})</code></pre><p>&quot;pageviews&quot; 키의 값을 증가시키기 위해 &quot;$inc&quot; 제한자를 사용.
연산자를 사용하여 &quot;_id&quot;의 값은 변경할 수 없고, 변경하기 위해선 도큐먼트 전체를 치환해야한다.</p>
<ul>
<li>&quot;$set&quot; 제한자 : 필드 값을 설정한다. 필드가 존재하지 않을 시 새 필드를 생성한다.<pre><code>&gt; db.users.updateOne({&quot;_id&quot; : ObjectId(/*생략*/)},
...{&quot;$set&quot; : {&quot;favorite book&quot; : &quot;War and Peace&quot;}})</code></pre>이때, &quot;$set&quot;은 데이터 형을 변경 시킬 수도 잇으며 내장 도큐먼트 내부의 데이터 또한 변경할 수 있다</li>
</ul>
<p>&quot;$unset&quot;으로는 키와 값을 모두 제거한다.</p>
<ul>
<li><p>&quot;$inc&quot; 연산자 : 이미 존재하는 키의 값을 변경하거나 새 키를 생성하는데 사용. 자주 변하는 수치 값을 갱신하는데 매우 유용.</p>
<pre><code>&gt; db.games.updateOne({&quot;game&quot; : &quot;pinball&quot;, &quot;user&quot; : &quot;joe&quot;},
...{&quot;$inc&quot; : {&quot;score&quot; : 50}})</code></pre><p>&quot;$inc&quot;는 숫자를 증감하기 위해 설계되었기 때문에 int, log, double. decimal 타입 값에만 사용할 수 있다.</p>
</li>
<li><p>&quot;$push&quot; : 배열이 존재하면 배열 끝에 요소를 추가시키고 존재하지 않으면 새로운 배열을 생성한다.
또한 이 연산자에는 제한자를 제공한다.</p>
<pre><code>&gt; db.movies.updateOne({&quot;genre&quot; : &quot;horror&quot;},
...{&quot;$push&quot; : {&quot;top10&quot; : {&quot;$each&quot; : [{&quot;name&quot; : &quot;Nightmare on Elm Street&quot;, &quot;rating&quot; : 6.6},
                                  {&quot;name&quot; : &quot;Saw,&quot;rating&quot; : 4.3}],
                        &quot;$slice&quot; : -10,
                        &quot;$sort&quot; : {&quot;rating&quot; : -1}}}})</code></pre><p>&quot;rating&quot; 필드로 배열의 모든 요소를 정렬한 후 처음 10개 요소를 유지한다. 이때, &quot;$each&quot;를 반드시 포함해야한다.</p>
</li>
</ul>
<p>특정 값이 배열에 존재하지 않을 때, 해당 값을 추가하면서 배열을 집합처럼 처리하려면 쿼리 도큐먼트에서 &quot;$ne&quot;를 사용한다.</p>
<p>&quot;$addToSet&quot; 또한 비슷한데, 중복을 피할 수 있거나 고유한 값을 여러 개 추가하기 위해 필요하다.</p>
<ul>
<li><p>&quot;$pop&quot; : 배열 요소를 제거할 때 사용. 배열 양 끝의 요소를 제거한다.</p>
</li>
<li><p>&quot;$pull&quot; : 지정된 조건에 따라 요소를 제거할 때 사용.</p>
</li>
<li><p>&quot;$&quot;문자 : 위치 연산자. </p>
<pre><code>&gt; db.blog.updateOne({&quot;comments.author&quot; : &quot;John&quot;},
... {&quot;$set&quot; : {&quot;comments.$.author&quot; : &quot;Jim&quot;}})</code></pre><p>John이라는 사용자가 이름은 Jim으로 갱신하기 위해서 위치 연산자 가용. 위치 연산자는 첫 번째로 일치하는 요소만 갱신한다.</p>
</li>
</ul>
<pre><code>&gt; db.blog.updateOne(
    {&quot;post&quot; : post_id },
    {$set : { &quot;comments.$[elem].hidden&quot; : true } },
    {
        arrayFilters: [ { &quot;elem.votes&quot;: { $lte: -5} } ]
    }
  )</code></pre><p>명령은 &quot;comments&quot; 배열의 각 일치 요소에 대한 식별자로 elem을 정의한다. elem이 식별한 댓글의 투표값이 -5이하면 hiddem 필드를 추가하고 값을 true로 설정.</p>
<h4 id="갱신입력">갱신입력</h4>
<p>: 특수한 형태를 갖는 갱신으로, 갱신 조건에 맞는 도큐먼트가 존재하지 않을 때는 쿼리 도큐먼트와 갱신 도큐먼트를 합쳐서 새로운 도큐먼트를 생성. </p>
<pre><code>&gt; db.analytics.updateOne({&quot;url&quot; : &quot;.blog&quot;}, {&quot;$inc&quot; : {&quot;pageviews&quot; : 1}},
... {&quot;upsert&quot; : true})</code></pre><p>해당 url 페이지에 방문할 때마다 pageview를 증가시키고 만일 처음 방문했다면 새로운 도큐먼트까지 생성
updateOne과 updateMany의 세 번째 매개변수는 옵션 도큐먼트로, 갱신 입력을 지정한다.</p>
<h4 id="저장-셀-보조자">저장 셀 보조자</h4>
<p>: save는 도큐먼트가 존재하지 않으면 도큐먼트를 삽입하고 존재하면 도큐먼트를 갱신하게 하는 셸 함수이다. </p>
<pre><code>&gt; var x = db.testcol.findOne()
&gt; x.num = 42
&gt; db.testcol.save(x)</code></pre><p>:: save 사용</p>
<pre><code>&gt; var x = db.testcol.findOne()
&gt; x.num = 42
db.testcol.replaceOne({&quot;_id&quot; : x._id}, x)</code></pre><p>:: save 미사용</p>
<h4 id="갱신한-도큐먼트-반환">갱신한 도큐먼트 반환</h4>
<p>findOneAndUpdate 메서드는 기본적으로 도큐먼트의 상태를 수정하기 전에 반환한다. 옵션 도큐먼트의 &quot;returnNewDocumentment&quot; 필드를 true로 설정하면 갱신된 도큐먼트를 반환한다. (세 번째 매개변수)
findOneAndReplace는 동일한 매개변수를 사용하며 교체 전이나 후에 필터와 일치하는 도큐먼트를 반환하고
findOneAndDelete도 유사하지만 갱신 도큐먼트를 매개변수로 사용하지 않으며 다른 두 메서드의 옵션을 부분적으로 가진다. 또한 삭제된 도큐먼트를 반환한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2장. 몽고DB 기본]]></title>
            <link>https://velog.io/@j22_s00/2%EC%9E%A5.-%EB%AA%BD%EA%B3%A0DB-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@j22_s00/2%EC%9E%A5.-%EB%AA%BD%EA%B3%A0DB-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Fri, 05 Apr 2024 17:01:39 GMT</pubDate>
            <description><![CDATA[<p>[몽고DB 완벽 가이드] 책을 보고 정리한 내용입니다.</p>
<h3 id="도큐먼트">도큐먼트</h3>
<p>: 몽고DB 데이터의 기본 단위이며 관계형 데이터베이스의 행과 유사. 정렬된 키와 연결된 값의 집합으로 이뤄져있다. </p>
<pre><code>{&quot;greeting&quot; : &quot;Hello, world!&quot;}</code></pre><p>간단한 도큐먼트로 
키 : &quot;greeting&quot;, 값 : &quot;Hello, world!&quot; 를 가진다.</p>
<ul>
<li>키는 null 문자를 포함하지 않고 ₩n은 키의 끝을 나타내는 데 사용된다.</li>
<li>데이터형과 대소문자를 구별한다.</li>
<li>키는 중복될 수 없다.</li>
</ul>
<h3 id="컬렉션">컬렉션</h3>
<p>: 도큐먼트의 모음. 관계형 데이트베이스에서의 테이블에 대응된다.</p>
<h4 id="동적-스키마">동적 스키마</h4>
<p>: 하나의 컬렉션 내 도큐먼트들이 모두 다른 구조를 가질 수 있다.</p>
<h4 id="네이밍">네이밍</h4>
<p>: 컬렉션은 이름으로 식별된다.</p>
<ul>
<li>빈 문자열은 유효한 컬렉션명이 아니다.</li>
<li>₩n은 컬렉션의 끝을 나타내는 문자이므로 컬렉션에 포함되지 않는다.</li>
<li>system.으로 시작하는 컬렉션명은 시스템 컬렉션에서 사용하는 예약어이다. (ex. system.users - 데이터 사용자 정보 컬렉션)</li>
<li>사용자가 만든 컬렉션에서 $를 컬렉션명에 포함할 수 없다.</li>
</ul>
<h4 id="서브컬렉션">서브컬렉션</h4>
<p>서브컬렉션의 네임스페이스에 &#39;.&#39;를 사용하여 컬렉션을 체계화한다.
ex. 블로그 기능이 있는 애플리케이션 : blog.posts, blog.authors 으로 표현가능
서브컬렉션의 특별한 속성은 없지만 여러 몽고DB 툴에서 지원한다</p>
<h3 id="데이터베이스">데이터베이스</h3>
<p>: 컬렉션을 그룹지어 놓는 곳.
몽고DB의 단일 인스턴스는 여러 데이터베이스를 호스팅할 수 있으며 각 데이터베이스를 완전히 독립적으로도 취급할 수 있다.</p>
<p>데이터베이스 또한 이름으로 식별되는데</p>
<ul>
<li>빈 문자열은 유효한 데이터베이스 이름이 아니다.</li>
<li>/ ₩ , &#39; &lt; &gt; : ? $ 등의 문자를 포함할 수 없다.</li>
<li>대소문자를 구별하낟.</li>
<li>최대 64바이트이다.</li>
</ul>
<p>또한, 직접 접근할 순 있지만 특별한 의미론을 갖는 예약된 데이터베이스 이름도 있다.</p>
<ul>
<li>admin : 인증과 권한 부여 역할</li>
<li>local : 단일 서버에 대한 데이터 저장</li>
<li>config : 샤딩된 몽고DB 클러스터는 이를 사용해 각 샤드의 정보 저장</li>
</ul>
<p>** 컬렉션을 저장하는 DB의 이름을 컬렉션명 앞에 붙이면 올바른 컬렉션명인 네임스페이스를 얻는다.
ex. cms 데이터베이스의 blog.posts 컬렉션을 사용한다면 컬렉션의 네임스페이스는 cms.blog.posts가 된다.</p>
<h3 id="셸-기본-작업">셸 기본 작업</h3>
<h4 id="생성">생성</h4>
<pre><code>&gt; movie = {&quot;title&quot; : &quot;Star Wars: Episode IV - A New Hope&quot;,
... &quot;director&quot; : &quot;George Lucas&quot;,
... &quot;year&quot; : 1977}</code></pre><p>도큐먼트를 나타내는 자바스크립트 객체인 movie라는 지역 변수를 생성하고, 이 변수는 &quot;title&quot;, &quot;director&quot;, &quot;year&quot;와 같은 키를 가진다. 이 객체는 유효한 몽고DB 도큐먼트이며 insearOne 함수를 이용해 movies 컬렉션에 저장할 수 있다.</p>
<pre><code>&gt; db.movies.insertOne(movie)
{
    &quot;acknowledged&quot; : true,
    &quot;insertedId&quot; : ObjectId(/* 생략 */)
}</code></pre><p>컬렉션에 find를 호출하면 이렇게 출력된다.</p>
<pre><code>&gt; db.movies.find().pretty()
{
    &quot;_id&quot; : ObjectId(/* 생략 */),
    &quot;title&quot; : &quot;Star Wars: Episode IV - A New Hope&quot;,
    &quot;director&quot; : &quot;George Lucas&quot;,
    &quot;year&quot; : 1977
}</code></pre><h4 id="읽기">읽기</h4>
<pre><code>&gt; db.movies.findOne()
{
    &quot;_id&quot; : ObjectId(/* 생략 */),
    &quot;title&quot; : &quot;Star Wars: Episode IV - A New Hope&quot;,
    &quot;director&quot; : &quot;George Lucas&quot;,
    &quot;year&quot; : 1977
}</code></pre><p>find와 findOne은 컬렉션을 쿼리하는데 사용하고, 컬렉션에서 단일 도큐먼트를 읽으려면 findOne을 사용한다.
이 둘은 쿼리 도큐먼트의 형태로, 조건 전달도 가능하다.</p>
<h4 id="갱신">갱신</h4>
<pre><code>&gt; db.movies.updateOne({title : &quot;Star Wars: Episode IV - A New Hope&quot;},
... {$set : {reviews: []}})
WriteResult({&quot;nMatched&quot;: 1, &quot;nUpserted&quot;: 0, &quot;nModified&quot;: 1})</code></pre><p>updateOne은 게시물을 갱신하는데 사용한다. 매개변수는 최소 두 개이고 첫 번째는 수정할 도큐먼트를 찾는 기준, 두 번째는 갱신 작업을 설명하는 도큐먼트이다. 이 때, 갱신하기 위해선 갱신 연산자인 $set를 사용한다.</p>
<p>위의 작업으로 도큐먼트에는 &quot;reviews&quot; 키가 생겼다. find로 호출하면 이렇게 출력된다.</p>
<pre><code>&gt; db.movies.findOne()
{
    &quot;_id&quot; : ObjectId(/* 생략 */),
    &quot;title&quot; : &quot;Star Wars: Episode IV - A New Hope&quot;,
    &quot;director&quot; : &quot;George Lucas&quot;,
    &quot;year&quot; : 1977,
    &quot;reviews&quot; : [ ]
}</code></pre><h4 id="삭제">삭제</h4>
<pre><code>&gt; db.movies.deleteOne({title : &quot;Star Wars: Episode IV - A New Hope&quot;})</code></pre><p>deleteOne과 deleteMany는 도큐먼트를 데이터베이스에서 영구적으로 삭제한다. 이 둘은 필터 도큐먼트로 삭제 조건을 지정한다.</p>
<h3 id="데이터형">데이터형</h3>
<ul>
<li>null</li>
<li>불리언</li>
<li>숫자</li>
<li>문자열</li>
<li>날짜 : 새로운 객체를 생성할 땐 new Date()</li>
<li>정규 표현식</li>
<li>배열</li>
<li>내장 도큐먼트 : 키에 대한 값으로 도큐먼트가 되는 것</li>
<li>객체 ID : 몽고DB의 모든 도큐먼트는 고유의 &quot;_id&quot;값을 가진다. (명시하지 않으면 자동생성)</li>
<li>이진 데이터</li>
<li>코드</li>
</ul>
<h3 id="몽고db-셸-사용">몽고DB 셸 사용</h3>
<p>다른 장비나 포트에 mongod를 연결하려면 셸을 시작할 때 호스트명, 포트, 데이터베이스를 명시해야 함.</p>
<pre><code>$ mongo some-host:3000/myDB
MongoDB shell version: 4.2.0
connecting to: some-host:3000.nyDB
&gt;</code></pre><p>만일 셸을 시작할 때 --nodb를 추가하면 어디에도 연결되지 않는다.
이후 new Mongo(호스트명)을 실행함으로써 mongod에 연결한다.</p>
<h4 id="mongorcjs-만들기">.mongorc.js 만들기</h4>
<p>셸이 시작할 때마다 실행되는 스크립트를 .mongorc.js 파일에 넣을 수 있다. 
ex. 로그인할 때 사용자를 맞이하는 셸
홈 디렉터리에 .mongorc.js 파일을 만들고 다음을 추가한다.</p>
<pre><code>// .mongorc.js

var compliment = [&quot;attractive&quot;, &quot;intelligent&quot;, like Batman&quot;];
var index = Math.floor(Math.random()*3);

print(&quot;Hello, you&#39;re looking particularly &quot;+compliment[index]+&quot; today!&quot;);</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[1장. 몽고DB 소개]]></title>
            <link>https://velog.io/@j22_s00/1%EC%9E%A5.-%EB%AA%BD%EA%B3%A0DB-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@j22_s00/1%EC%9E%A5.-%EB%AA%BD%EA%B3%A0DB-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Fri, 05 Apr 2024 16:44:15 GMT</pubDate>
            <description><![CDATA[<p>[몽고DB 완벽 가이드] 책을 보고 정리한 내용입니다.</p>
<h3 id="몽고db">몽고DB</h3>
<p>몽고DB는 관계형 데이터베이스가 아니라 <strong>도큐먼트 지향</strong> 데이터베이스이다.</p>
<h4 id="특징">특징</h4>
<ul>
<li>분산 확장을 쉽게 할 수 있다.</li>
<li>행 개념 대신, 유연한 모델인 도큐먼트를 사용한다.</li>
<li>복잡한 계층 관계를 하나의 레코드로 표현할 수 있다.</li>
<li>최신 객체 지향 언어를 사용하는 개발자의 관점에서 매우 적합하다.</li>
<li>고정된 스키마가 없다. (쉽게 필드 추가/제거 가능)</li>
<li>개발 과정을 빠르게 반복할 수 있어 속도가 빠르다.</li>
</ul>
<h4 id="기능">기능</h4>
<ul>
<li>DBMS의 대부분의 기능 (ex.데이터 생성/읽기/변경/삭제</li>
<li>인덱싱 
: 일반적인 보조 인덱스와 계층 구조의 보조 인덱스를 지원, 고유/복합/공간 정보/인덱싱 기능 제공 </li>
<li>집계
: 데이터 처리 파이프라인 개념을 기반으로 한 집계 프레임워크를 제공. 복잡한 분석 엔진을 구축 가능</li>
<li>특수한 컬렉션 유형
: 세션이나 고정크기 컬렉션과 같이 특정 시간에 만료해야하는 데이터에 대해 유효시간 컬렉션을 지원, 기준 필터와 일치하는 도큐먼트에 한정된 부분 인덱스 지원</li>
<li>파일 스토리지
: 큰 파일과 파일 메타데이터를 편리하게 저장하는 프로토콜 지원</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2장. 타입]]></title>
            <link>https://velog.io/@j22_s00/2%EC%9E%A5.-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@j22_s00/2%EC%9E%A5.-%ED%83%80%EC%9E%85</guid>
            <pubDate>Sun, 03 Mar 2024 16:32:13 GMT</pubDate>
            <description><![CDATA[<p>[우아한 타입스크립트 with 리액트] 책을 공부하면서 정리한 내용입니다. </p>
<h3 id="타입">타입</h3>
<p>자바스크립트는 7가지 테이터 타입(자료형)을 정의한다.</p>
<ul>
<li>undefined</li>
<li>null</li>
<li>Boolean</li>
<li>String</li>
<li>Symbol</li>
<li>Numeric</li>
<li>Object</li>
</ul>
<p>프로그래밍에서의 타입은 수학의 집합과 유사하다.
타입은 값이 가질 수 있는 유효한 범위의 집합을 말한다.</p>
<pre><code>function double(n) {
    return n*2;
}

double(2); // 4
double(&quot;z&quot;); // NaN</code></pre><p>double()의 내부 동작을 보면 숫자를 인자로 받을 거라고 기대한다. 만약 인자로 숫자가 아닌 다른 타입 값을 전달하면 의도치 않은 작업을 수행해서 원하는 값을 얻지 못한다. </p>
<pre><code>function double(n:number) {
    return n*2;
}

double(2); // 4
double(&quot;z&quot;); // Error</code></pre><p>일단 타입을 제한하면 타입스크립트 컴파일러는 함수를 호출할 때 호환되는 인자로 호출했는지를 판단한다. 즉 string 타입인 &#39;z&#39;는 number에 할당할 수 없기 때문에 에러가 발생한다.</p>
<h4 id="정적-타입-동적-타입">정적 타입, 동적 타입</h4>
<p>타입을 결정하는 시점에 따라 정적 타입과 동적 타입으로 분류할 수 있다.</p>
<p>정적 타입 시스템에서는 모든 변수의 타입이 컴파일타임에 결정된다. 코드 수준에서 개발자가 타입을 명시해줘야하는 C, 자바, 타입스크립트 등이 정적 타입 언어레 속한다. 조금 번거로워보여도 컴파일타임에 타입 에러를 발견할 수 있기 때문에 안정성을 보장할 수 있다.</p>
<p>동적 타입 시스템에서는 변수 타입이 런타임에서 결정된다. 파이썬, 자바스크립트가 대표적인 동적 타입 언어로 개발자는 직접 타입을 정의해줄 필요가 없다. 프로그램을 실행할 때 타입 에러가 발견되기 때문에 개발 과정에서 에러 없이 마음껏 코드를 작성할 수 있지만, 언제 프로그램에 오류가 생길지 모르는 물안감에 휩싸이게 된다.</p>
<h3 id="값-vs-타입">값 vs 타입</h3>
<p>값은 프로그램이 처리하기 위해 메모리에 저장하는 모든 데이터이다. 즉, 프로그램에서 조작하고 다룰 수 있는 어떤 표현이며 다양한 형태의 데이터를 포함한다. 수학적 개념에서 값으로 여겨지는 1, 2, 3과 같은 데이터는 물론이고 1 + 2 같은 식이 반환하는 결과값 3도 값에 해당한다. 프로그래밍 관점에서는 문자열, 숫자, 변수, 매개변수 등이 값에 해당한다.
객체, 함수또한 값이다. </p>
<p>타입스크립트 코드에서 타입과 값을 구분하는 것은 어렵지 않다. 타입은 주로 타입 선언(:) 또는 단언 문 (as)으로 작성하고 값은 할당 연산자(=)로 작성한다. </p>
<pre><code>interface Developer {
    name: string,
    isWorking: boolean;
}

const developer:Developer = { name: &quot;Zig&quot;, isWorking: true };</code></pre><p>위 예시코드에서는 변수 developer의 타입은 Developer이며, developer에 할당된 값은 { name: &quot;Zip&quot;, isworking: true }이다.</p>
<p>값-타입 공간을 혼동하는 문제를 해결하기 위해서는 값과 타입을 구분해서 작성해야 한다.</p>
<h3 id="타입을-확인하는-방법">타입을 확인하는 방법</h3>
<p>타입스크립트에서 typeof, instanceof, 그리고 타입 단언을 사용해서 타입을 확인할 수 있다. typeof는 연산하기 전에 피연산자의 데이터 타입을 나타내는 문자열을 반환한다. typeof 연산자가 반환하는 값은 자바스크립트의 7가지 기본 데이터 타입과 Function(함수), 호스트 객체, object 객체가 될 수 이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[8장. 내비게이션]]></title>
            <link>https://velog.io/@j22_s00/8%EC%9E%A5-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@j22_s00/8%EC%9E%A5-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Thu, 22 Feb 2024 15:35:28 GMT</pubDate>
            <description><![CDATA[<p>[처음 배우는 리액트 네이티브] 책을 공부하면서 정리한 내용입니다.</p>
<p>모바일 애플리케이션은 하나의 화면으로 구성되기 보단, 다양한 화면이 상황에 맞게 전환되면서 나타남.
 : 내비게이션 기능이 가장 중요한 기능 중 하나인 이유</p>
<p> 리액트 내비게이션 라이브러리 사용.</p>
<h3 id="리액트-내비게이션">리액트 내비게이션</h3>
<p>스택 내비게이션, 탭 내비게이션, 드로어 내비게이션 지원.</p>
<p>설치방법 / 대부분 필요한 종속성 설치</p>
<pre><code>npm install --save @react-navigation/native

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view</code></pre><h4 id="구조">구조</h4>
<p>NavigationContainer 컴포넌트
 : 내비게이션의 계층 구조와 상태를 관리하는 컨테이너 역할. 최상위 컴포넌트
Navigator 컴포넌트
 : 화면을 관리하는 중간 관리자 역할. 여러 개의 Screen 컴포넌트를 자식 컴포넌트로 가지고 있음.
Screen 컴포넌트
 : 화면으로 사용되는 컴포넌트. name(화면이름)과 component(화면으로 사용될 컴포넌트) 속성을 지정.
   이때, 화면으로 사용되는 컴포넌트에는 항상 navigation과 route가 props로 전달.</p>
<h4 id="설정-우선-순위">설정 우선 순위</h4>
<p>리액트 내비게이션으로 설정할 수 있는 다양한 속성을 수정하는 방법
Navigator 컴포넌트의 속성으로 수정 (자식 컴포넌트로 존재하는 모든 컴포넌트에 적용)
Screen 컴포넌트의 속성으로 수정 (해당 화면에만 적용)
화면에서 사용되는 컴포넌트의 props로 전달되는 navigation을 이용해서 수정 (해당 화면에만 적용)</p>
<p>이때, 작은 범위의 설정일 수록 우선순위가 높음. ex. Screen 컴포넌트 &lt; 화면으로 사용되는 컴포넌트</p>
<h3 id="스택-내비게이션">스택 내비게이션</h3>
<p>설치</p>
<pre><code>npm install @react-navigation/stack</code></pre><p>일반적으로 가장 많이 사용되는 내비게이션, 현제 화면 위에 다른 화면을 쌓으면서 화면을 이용.
ex. 채팅 애플리케이션에서 채팅방에 입장, 여러 목록 중 특정 항목의 상세화면으로 이동</p>
<p>화면 위에 새로운 화면을 쌓으면서(push) 이동하기 때문에 이전 화면을 유지.
따라서 가장 위에 있는 화면을 들어나면(pop) 이전 화면으로 돌아감.</p>
<p>-src/navigations/Stack.js</p>
<pre><code>import React from &#39;react&#39;;
import { createStactNavigator } from &#39;@react-navigation/stack&#39;;
import Home from &#39;../screens/Home&#39;;
import List from &#39;../screens/List&#39;;
import Item from &#39;../screens/Item&#39;;

const Stack = createStackNavigator();

const StackNavigation = () =&gt; {
    return (
        &lt;Stack.Navigator&gt;
          &lt;Stack.Screen name=&quot;Home&quot; components={Home} /&gt;
          &lt;Stack.Screen name=&quot;List&quot; components={List} /&gt;
          &lt;Stack.Screen name=&quot;Item&quot; components={Item} /&gt;
        &lt;/Stack.Navigator&gt;
     );
};

export default StackNavigation;</code></pre><p>Screen 컴포넌트의 name은 반드시 서로 다른 값을 가져야 함.
스택 내비게이션에서 첫 번째 화면으로 나오는 화면은 Navigator 컴포넌트의 첫 번째 자식 Screen 컴포넌트.
이외에도 initialRouteName 속성을 이용할 수도 있다.</p>
<h4 id="화면-이동">화면 이동</h4>
<p>Screen 컴포넌트의 component로 지정된 컴포넌트는 화면으로 이용되고, navigation이 porps로 전달.
navigate 함수 : 원하는 화면으로 이동하는 데 사용</p>
<p>-src/screens/Home.js</p>
<pre><code>const Home = ({ navigation }) =&gt; {
    return (
        &lt;Container&gt;
            &lt;Text&gt;Home&lt;/Text&gt;
            &lt;Button
                title=&quot;go to the list screen&quot;
                onPress={() =&gt; navigation.navigate(&#39;List&#39;)} 
             /&gt;
         &lt;/Container&gt;
     );
};</code></pre><p>Home 화면에서 List 화면으로 이동.</p>
<p>navigate 함수를 이용하여 원하는 화면의 이름을 전달.</p>
<p>만일, 이동하는 화면이 이전 화면의 상세 화면이라면, 어떤 내용을 렌더링해야 하는지 전달받아야 함.
navigate 함수에서 두 번째 피라미터에 객체를 전달해서 이동하는 화면에 필요한 정보 전달.</p>
<p>-src/screens/List.js</p>
<pre><code>const List = ({ navigation }) =&gt; {
    const _onPress = item =&gt; {
        navigation.navigate(&#39;Item&#39;, { id:item._id, name: item.name });
     };
 ...
 };</code></pre><p>List 화면에서 목록을 클릭하면 해당 항목의 정보와 함께 Item 화면으로 이동.</p>
<p>전달된 내용은 컴포넌트의 props로 전달되는 route의 params를 통해 확인.</p>
<p>-src/screens/Item.js</p>
<pre><code>const Item = ({ route }) =&gt; {
    return (
        &lt;Container&gt;
            &lt;Text&gt;Item&lt;/Text&gt;
            &lt;Text&gt;ID: {route.params.id}&lt;/Text&gt;
            &lt;Text&gt;Name: {route.params.name}&lt;/Text&gt;
        &lt;/Container&gt;
    );
 };</code></pre><p>Item 화면에서 전달되는 params를 이용하여 화면에 항목의 id와 name 출력.</p>
<h4 id="화면-배경색-수정">화면 배경색 수정</h4>
<p>-src/screens/Home.js</p>
<pre><code>const Container = styled.View`
    background-color: #ffffff;
    align-items: center;
`;</code></pre><p>Home 화면의 배경색을 흰색으로 설정했을 시,
내비게이션은 화면 전체를 차지하고 있지만 화면으로 사용된 컴포넌트의 영역이 전체를 차지하고 있지 않아 전체가 흰색으로 바뀌지 않을 수 있다.</p>
<p>스타일에 flex: 1을 설정하면 문제를 해결할 수 있다.
하지만 상황에 따라 화면 전체를 차지하지 못하는 경우에는, 리액트 내비게이션의 설정을 수정하여 해결할 수 있다.</p>
<p>-src/navigation/Stack.js</p>
<pre><code>const StackNavigation = () =&gt; {
    return (
        &lt;Stack.Navigator
         initialRouteName=&quot;Home&quot;
         screenOptions={{ cardStyle: { backgroundColor: &#39;0ffffff&#39; } }}
        &gt;
        ...
        &lt;/Stack.Navigator&gt;
    );
};</code></pre><p>cardStyle을 사용하면 스택 내비게이션의 화면 배경색 수정 가능.</p>
<h4 id="헤더-수정하기">헤더 수정하기</h4>
<p>스택 내비게이션의 헤더는 뒤로 가기 버튼, 타이틀(현재 화면)의 역할.
타이틀 : 
-기본적으로 Screen 컴포넌트의 name 속성을 사용.
-headerTitle 사용.
ex. src/navigations/Stack.js</p>
<pre><code>&lt;Stack.Screen
    name=&quot;List&quot;
    component={List}
    options={{ headerTitle: &#39;List Screen&#39; }}
/&gt;</code></pre><p>모든 화면에서 같은 타이틀이 나타나도록 수정하기 위해선, Navigator 컴포넌트의 screenOptions 속성에 headertitle을 지정.</p>
<p>스타일 :
headerStyle(해더의 배경색 수정), headerTitleStyle(헤더의 타이틀 컴포넌트의 스타일 수정), headerTitleAlign(헤더의 타이틀 정렬)
-src/navigations/Stacks.js</p>
<pre><code>const StackNavigation = () =&gt; {
    return (
        &lt;Stack.Navigator
          initialRouteName=&quot;Home&quot;
          screenOptions={{
           cardStyle: { backgroundColor: &#39;#ffffff&#39; },
           headerStyle: {
            height: 110,
            backgroundColor: &#39;#95a5a6&#39;,
            borderBottomMidth: 5,
            borderBottomColor: &#39;#34495e&#39;,
           },
           headerTitleStyle: { color: &#39;#ffffff&#39;, fontsize: 24 },
           headerTitleAlign: &#39;center&#39;,
          }}
        &gt;
        ...
        &lt;/Stack.Navigator&gt;
    );
};</code></pre><p>타이틀에 문자가 아닌 다른 것을 렌더링하기 위해선, headerTitle 속성에 컴포넌트를 반환하는 함수를 지정. 이때, 해당 함수의 파라미터로 style(headerTitleStyle로 부터)과 tintColor(headerTintColor로 부터) 등이 포함된 객체가 전달됨.
-src/navigations/Stack.js</p>
<pre><code>import { MaterialCommunityIcons } from &#39;@expo/vector-icons&#39;;
...

headerTitle: ({ style }) =&gt; (
    &lt;MaterialCommunityIcons name=&quot;react&quot; style={style} /&gt;
);</code></pre><p>뒤로가기 버튼 :
ios에서는 이전 화면의 타이틀을 버튼의 타이틀로 보여주고 안드로이드에서는 버튼의 타이틀을 보여주지 않음.
-src/navigations/Stack.js</p>
<pre><code>&lt;Stack Screen
 name=&quot;List&quot;
 component={List}
 options={{
  headerTitle: &#39;List Screen&#39;
  headerBackTitleVisible: true,
  headerBackTitle: &#39;Prev&#39;,
 }}
/&gt;</code></pre><p>둘 다 보이도록 통일.
headerBackTitle이 없을 시, 이전 화면의 타이틀이 나오고 headerBackTitle에 입력을 하면 해당 값이 나온다. 이때, 빈 문자열을 입력하면 Back이라는 문자열이 나타남.</p>
<p>headerBackTitleStyle을 이용해 버튼의 타이틀의 스타일 지정 가능. (버튼의 타이틀에만 적용) 버튼의 타이틀과 이미지의 색을 동일하게 변경하기 위해선 headerTintColor 사용.</p>
<pre><code>&lt;Stack Screen
 name=&quot;List&quot;
 component={List}
 options={{
  headerTitle: &#39;List Screen&#39;
  headerBackTitleVisible: true,
  headerBackTitle: &#39;Prev&#39;,
  headerTitleStyle: { fontSize: 24 },
  headerTintColor: &#39;#e74c3c&#39;,
 }}
/&gt;</code></pre><p>뒤로가기 버튼의 이미지를 변경
-src/navigations/Stack.js</p>
<pre><code>import { Platform } from &#39;react-native&#39;;
...

&lt;Stack Screen
 name=&quot;List&quot;
 component={List}
 options={{
  ...
  headerBackImage: ({ tintColor }) =&gt; {
      const style = {
     marginRight: 5,
     marginLeft: Platform.OS === &#39;ios&#39; ? 11 : 0,
    },
    return (
        &lt;MaterialCommunityIcons
          name=&quot;keyboard-backspace&quot;
          size={30}
          color={tintColor}
          style={style}
        /&gt;
    );
  };
 }}
/&gt;</code></pre><p>만일 뒤로 가기 버튼의 이미지가 아닌, 헤더의 왼쪽/오른쪽 버튼 전체를 변경하고 싶다면 headerLeft/Right에 컴포넌트를 반환하는 함수를 지정.
-src/screens/Item.js</p>
<pre><code>import React, { useLayoutEffect } from &#39;react&#39;;
import { MaterialCommunityIcons } from &#39;@expo/vector-icons&#39;;
import styled from &#39;styled-components/native&#39;

...

const Item = ({ navigation, route )} =&gt; {
    useLayoutEffect(() =&gt; {
        navigation.setOptions({
            headerBackTitleVisible: false,
            headerTintColor: &#39;#ffffff&#39;,
            headerLeft: ({ onPress, tintColor }) =&gt; {
                return (
                    &lt;MaterialCommunityIcons
                      name=&quot;keyboard=backspace&quot;
                      size={30}
                      style={{ marginLeft: 11 }}
                      color={tintColor}
                      onPress={onPress}
                    /&gt;
                );
            },
            headerRight: ({ tintColor }) =&gt; (
                &lt;MaterialCommunityIcons
                  name=&quot;home-variant&quot;
                  size={30}
                  style={{ marginRight: 11 }}
                  color={tintColor}
                  conPress={() =&gt; navigation.popToTop()}
                /&gt;
            ),
    });
}, []);

return (...);
};</code></pre><p>useLayoutEffect Hook은 useEffect Hook과 사용법이 동일하며 거의 같은 방식으로 동작, 하지만 컴포넌트가 업데이트된 직후 화면이 렌더링되기 전에 실행된다는 차이점이 있음. 따라서 화면을 렌더링하기 전에 변경할 부분이 있거나 수치 등을 측정해야하는 상황에서 사용.</p>
<p>headerLeft 함수의 파라미터 중 onPress는 뒤로 가기 버튼 기능이 전달.
headerRight 함수의 파라미터에는 tintColor만 전달되므로 onPress에 원하는 행동을 정의해야 함. 위의 함수인 popToTop 함수는 현재 쌓여 있는 모든 화면을 내보내고 첫 화면으로 돌아가는 기능.</p>
<p>헤더 감추기 :
headerMode - Navigator 컴포넌트의 속성으로 헤더를 렌더링 하는 방법을 설정하는 속성. float(헤더가 상단에 유지, 하나의 헤더 사용)/screen(각 화면하다 헤더를 가지며 화면 변경과 함께 나타나거나 사라짐)/none(헤더가 렌더링 되지 않음)
headerShown - 화면 옵션, Navigator 컴포넌트의 screenOption에 설정하면 전체 화면의 헤더가 보이지 않도록 설정. ex.-src/navigations/Stack.js</p>
<pre><code>&lt;Stack.Screen
  name=&quot;Home&quot;
  component={Home}
  options={{ headerShown: false }}
/&gt;</code></pre><p>이때, ios는 노치 디자인 문제로 화면 일부가 가려지는 문제를 해결하기 위해 아래의 코드 추가
-src/screens/Home.js</p>
<pre><code>const Container = styled.SafeAreaView`
    background-color: #ffffff;
    align-items: centerl
`;</code></pre><h3 id="탭-내비게이션">탭 내비게이션</h3>
<p>보통 화면 위나 아래에 위치, 탭 버튼을 누르면 버튼과 연결된 화면으로 이동하는 방식</p>
<p>설치방법</p>
<pre><code>npm install @react-navigation/bottom-tabs</code></pre><p>-src/navigations/Tab.js</p>
<pre><code>import React from &#39;react&#39;;
import { createBottomTabNavigator } from &#39;@react-navigation/bottom-tabs&#39;;
import { Mail, Meet, Setting } from &#39;../screens/TabScreens&#39;;

const Tab = createBottomTabNavigator();

const TabNavigation = () =&gt; {
    return (
        &lt;Tab.Navigator&gt;
          &lt;Tab.Screen name=&quot;Mail&quot; component={Mail} /&gt;
          &lt;Tab.Screen name=&quot;Meet&quot; component={Meet} /&gt;
          &lt;Tab.Screen name=&quot;Settings&quot; component={Settings} /&gt;
        );
};

export default TabNavigation;</code></pre><p>-App.js</p>
<pre><code>import TabNavigation from &#39;./navigations/Tab&#39;;

const App = () =&gt; {
    return (
        &lt;NavigationContainer&gt;
          &lt;TabNavigation /&gt;
        &lt;/NavigationContainer&gt;
    );
};</code></pre><p>Stack과 같이 initialRouteName 사용 가능.</p>
<h4 id="탭-바-수정하기">탭 바 수정하기</h4>
<p>버튼 아이콘 수정 : 
tabBarIcon 사용
-src/navigations/Tab.js</p>
<pre><code>import { MaterialCommunityicons } from &#39;@expo/vector-icons&#39;;
...

const TabIcon = {{ name, size, color }) =&gt; {
    return &lt;MaterialCommunityIcons name={name} size={size} color={color} /&gt;;
};

const Tab = createBottomTabNavigator();
const TabNavigation = () =&gt; {
 return(
     &lt;Tab.Navigator initialRouteName=&quot;Settings&quot;&gt;
    &lt;Tab.Screen
      name=&quot;Mail&quot;
      component={Mail}
      options={{
        tabBarIcon: props =&gt; TabIcon({ ...props, name: &#39;emil&#39; }),
      }}
      ...
    /Tab.Navigator&gt;
 );
};</code></pre><p>Stack에서와 같이 tabBarIcon에 컴포넌트를 반환하는 함수를 지정하면 버튼의 아이콘이 들어갈 자리에 해당 컴포넌트를 렌더링. 이때, tabBarIcon에 설정된 함수에는 color, size, focused값을 포함한 객체가 파라미터로 전달.
또한, Screen 컴포넌트마다 탭 버튼 아이콘을 지정하지 않고 한곳에서 모든 버튼의 아이콘을 관리하고 싶을 경우 Navigator 컴포넌트의 screenOptions 속성을 사용하여 관리.
-src/navigations/Tab.js</p>
<pre><code>const TabIcon = ({ name, size, color }) =&gt; {
    return &lt;MaterialCommunityIcons name={name} size={size} color={color} /&gt;;
};

const Tab = createBottomTabNavigator();
const TabNavigation = () =&gt; {
 return (
     &lt;Tab.Navigator
      initialRouteName=&quot;Settings&quot;
      screenOptions={(({ route }) =&gt; ({
        tabBarIcon: props =&gt; {
          let name= &#39;&#39;;
          if (route.name === &#39;Mail&#39;) name = &#39;email&#39;;
          else if (route.name === &#39;Meet) name = &#39;video&#39;;
          else name = &#39;settings&#39;;
          return TabIcon({ ...props, name });
        },
      })}
    &gt;
    ...</code></pre><p>screenOptions에 객체를 반환하는 함수를 설정하고 함수로 전달되는 route를 이용. </p>
<p>라벨 수정하기 :
버튼 아이콘 아래에 렌더링되는 라벨은 Screen 컴포넌트의 name값을 기본값을 사용. tabBarLabel을 이용하여 변경. 또한, 라벨의 위치를 바꾸고 싶으면 labelPosition의 값을 조정. (below-icon/beside-icon)
-src/navigations/Tab.js</p>
<pre><code>&lt;Tab.Navigator
  initialRouteName=&quot;Settings&quot;
  tabBarOptions={{ labelPosition: &#39;beside-icon&#39;, showLabel: false }}
&gt;
&lt;Tab.Screen
  name=&quot;Mail&quot;
  component={Mail}
  coptions={{
    tabBarLabel: &#39;Inbox&#39;,
    tabBarIcon: props =&gt; TabIcon({ ...props, name: &#39;email&#39; }),
  }}
/&gt;</code></pre><p>showLabel을 이용하면 라벨이 렌더링 되지 않음.</p>
<h4 id="스타일-수정">스타일 수정</h4>
<p>탭 바 배경색의 기본값은 흰색.
화면의 배경색 수정
-src/screens/TabScreens.js</p>
<pre><code>const Container = styled.View`
 flex: 1;
 justify-content: center;
 align-items: centerl
 background-color: #54b7f9;
`;
const StyledText = styled.Text`
 font-size: 30px;
 color: #ffffff;
`;</code></pre><p>탭 바 스타일 수정
이때, 탭 버튼의 아이콘은 선택되어 활성화된 상태의 색과 선택되지 않아 비활성화된 상태의 색을 각각 activeTintColor와 inactiveTintColor를 이용해 설정 가능.(라벨과 함께)
-src/navigations/Tab.js</p>
<pre><code>const TabNavigation = () =&gt; {
    return (
        &lt;Tab.Navigator
          initialRouteName=&quot;Settings&quot;
          tabBarOptions={{
            labelPosition: &#39;besided-icon&#39;,
            showLabel: false,
            style: {
              backgroundColor: &#39;#54b7f9&#39;,
              borderTopColor: &#39;#ffffff&#39;,
              borderTopMidth: 2,
            },
            activeTintColor: &#39;#ffffff&#39;,
            inactiveTintColor: &#39;#0B92E9;,
          }}
        &gt;</code></pre><p>버튼의 아이콘을 설정하기 위해 barTabIcon에 설정한 함수에는 파라미터로 size, color, focused를 가진 객체가 전달. 이 중 focused는 버튼의 선택된 상태를 나타내는 값인데 버튼의 활성화 상태에 따라 다른 버튼을 렌더링하거나 스타일 변경 가능.
-src/navigations/Tab.js</p>
<pre><code>&lt;Tab.Screen
  name=&quot;Mail&quot;
  component={Mail}
  options={{
    tabBarLabel: &#39;Inbox&#39;,
    tabBarIcon: props =&gt;
      TabIcon({
       ...porps,
       name: props.focused ? &#39;email&#39; : &#39;email-outline&#39;,
      }),
  }}
/&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[7장. Context API]]></title>
            <link>https://velog.io/@j22_s00/7%EC%9E%A5.-Context-API</link>
            <guid>https://velog.io/@j22_s00/7%EC%9E%A5.-Context-API</guid>
            <pubDate>Mon, 12 Feb 2024 14:34:10 GMT</pubDate>
            <description><![CDATA[<p><em>[처음 배우는 리액트 네이티브] 책을 공부하면서 정리한 내용입니다.</em></p>
<h3 id="context-api">Context API</h3>
<p>: 데이터를 전연적으로 관리하고 사용할 수 있도록 하는 기능
부모-자식 컴포넌트로 연결되어 있는 흐름상, 하위 컴포넌트에서 데이터를 요청하고 수정할 때, 건너가야하는 컴포넌트들이 많아 불편하다. 이때, Context API를 이용하면 Context를 생성해 필요한 컴포넌트에서 데이터를 바로 받아올 수 있다.</p>
<p>ex.</p>
<pre><code>const Context = createContext(defaultValve);</code></pre><p>Context 오브젝트는 입력된 기본값 외에도 Consumer, Provider 컴포넌트를 가진다.</p>
<h4 id="consumer-컴포넌트">Consumer 컴포넌트</h4>
<p>: 상위 컴포넌트 중 가장 가까운 곳에 있는 Provider 컴포넌트가 전달하는 데이터 이용.
  만약, 상위 컴포넌트 중 Provider 컴포넌트가 없다면, 기본값 사용.</p>
<ul>
<li>src/contexts/User.js<pre><code>import { createContext } from &#39;react&#39;;
</code></pre></li>
</ul>
<p>const UserContext = createContext({ name: &#39;Jeesoo Kim&#39; });</p>
<p>export default UserContext;</p>
<pre><code>
- src/conponents/User.js</code></pre><p>import React from &#39;react&#39;;
import UserContext from &#39;../contexts/User&#39;;</p>
<p>const User = () =&gt; {
    return (
        &lt;UserContext.Consumer&gt;
            {value =&gt; <Text>Name: {value.name}</Text>}
        &lt;/UserContext.Consumer&gt;
    );
};</p>
<p>export default User;</p>
<pre><code>
-src/App.js</code></pre><p>import User from &#39;./components/User&#39;;
...</p>
<p>const App = () =&gt; {
    return (
        <Container>
            <User />
        </Container>
    );
};</p>
<pre><code>
#### Provider 컴포넌트
: 하위 컴포넌트에 Context의 변화를 알리는 역할.
value를 받아서 모든 하위 컴포넌트에 전달하고, 하위 컴포넌트들은 이때마다 렌더링 됨.

-src/App.js</code></pre><p>import User from &#39;./components/User&#39;;
...</p>
<p>const App = () =&gt; {
    return (
        &lt;UserContext.Provider&gt;
            <Container>
                <User />
            </Container>
        &lt;/UserContext.Provider&gt;
    );
};</p>
<pre><code>이렇게 수정한 경우, 오류 메세지가 나온다.
이유 : App 컴포넌트를 Provider 컴포넌트로 감쌌기 때문에 User 컴포넌트에서 사용된 Consumer 컴포넌트는 더이상 Context의 기본값이 아닌 상위 컴포넌트인 Provider 컴포넌트가 전달하는 데이터를 사용하도록 변경. 하지만 아무것도 전달되지 않았기 때문.
</code></pre><p>import User from &#39;./components/User&#39;;
...</p>
<p>const App = () =&gt; {
    return (
        &lt;UserContext.Provider value={{ name: &#39;Jeesoo&#39; }}&gt;
            <Container>
                <User />
            </Container>
        &lt;/UserContext.Provider&gt;
    );
};</p>
<pre><code>이렇게 값을 전달해주면 잘 작동한다.

또한, Consumer 컴포넌트는 가장 가까운 Provider 컴포넌트가 전달하는 값을 이용한다.
(Provider 컴포넌트로부터 전달받는 하위 컴포넌트 수에는 제한이 없음.)

#### context 수정
-src/context/User.js</code></pre><p>import React, { createContext, useState } from &#39;react&#39;;</p>
<p>const UserContext = createContext({
    user: {name: &#39;&#39;},
    dispatch: () =&gt; {},
});</p>
<p>const UserProvider = ({ children }) =&gt; {
    const [name,setName] = useState(&#39;Jeesoo Kim&#39;);</p>
<pre><code>const value = { user: { name }, dispatch: setName };
return &lt;UserContext.Provider value={value}&gt;{children}&lt;/UserContext.Provider&gt;; </code></pre><p>};</p>
<p>const UserConsumer = UserContext.Consumer;</p>
<p>export { UserProvider, UserConsumer };
export default UserContext;</p>
<pre><code>UserProvider 컴포넌트 : Provider 컴포넌트의 value에 전역적으로 관리할 useState를 함께 전달.
하위에 있는 Consumer 컴포넌트의 자식 함수의 파라미터로 데이터와 데이터를 변경할 수 있는 함수까지 전달.
createContext의 기본값 형태도 UserProvider 컴포넌트의 value로 전달하는 형태와 동일.

-src/components/Input.js</code></pre><p>import React, { useState } from &#39;react&#39;;
import {UserConsumer } from &#39;../contexts/User&#39;;</p>
<p>const Input = () =&gt; {
    const [name, setName] = useState(&#39;&#39;);
    return (
        <UserConsumer>
            {({ dispatch }) =&gt; {
                return (
                    &lt;TextInput
                      value={name}
                      onChangeText={text =&gt; setName(text)}
                      onSubmitEditing={() -&gt; {
                          dispatch(name);
                        setName(&#39;&#39;);
                      }}
                      placeholder=&quot;Enter a name...&quot;
                      autoCapitalize=&quot;none&quot;
                      autoCorrect={false}
                      returnKeyType=&quot;done&quot;
                     /&gt;
                 );
             }}
         </UserConsumer>
     );
};</p>
<p>export default Input;</p>
<pre><code>useState 함수를 통해 name 상태변수를 생성.
TextInput 컴포넌트에 값이 변경될 때마다 name에 반영.
UserConsumer 컴포넌트의 자식 함수에 전달되는 value에는 Context의 값을 변경할 수 있는 dispatch 함수가 함께 전달되고, dispatch 함수를 통해 키보드의 확인 버튼을 누르면 입력된 값으로 Context의 값 변경.


### useContext
: Consumer 컴포넌트의 자식 함수로 전달되던 값과 동일한 데이터 반환.
즉, Consumer 컴포넌트를 사용하지 않고 Context의 내용을 사용 가능하게 함.

위의 코드를 useContext를 사용하는 코드로 수정.

-src/components/User.js</code></pre><p>import React, { useContext } from &#39;react&#39;;
import UserContext from &#39;../contexts/User&#39;;</p>
<p>const User = () =&gt; {
    const { user } = useContext(UserContext);
    return <Text> Name: {user.name} </Text>
};</p>
<p>export default User;</p>
<pre><code>
-src/components/Input.js</code></pre><p>import React, { useState, useContext } from &#39;react&#39;;
import {UserConsumer } from &#39;../contexts/User&#39;;
import UserContext from &#39;../contexts/User&#39;;</p>
<p>const Input = () =&gt; {
    const [name, setName] = useState(&#39;&#39;);
    const { dispatch } = useContext(UserContext);</p>
<pre><code>return (
    &lt;TextInput
     value={name}
     onChangeText={text =&gt; setName(text)}
     onSubmitEditing={() -&gt; {
         dispatch(name);
        setName(&#39;&#39;);
     }}
     placeholder=&quot;Enter a name...&quot;
     autoCapitalize=&quot;none&quot;
     autoCorrect={false}
     returnKeyType=&quot;done&quot;
    /&gt;
);</code></pre><p>};</p>
<p>export default Input;</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[6장. Hooks]]></title>
            <link>https://velog.io/@j22_s00/6%EC%9E%A5.-Hooks</link>
            <guid>https://velog.io/@j22_s00/6%EC%9E%A5.-Hooks</guid>
            <pubDate>Sat, 10 Feb 2024 15:00:02 GMT</pubDate>
            <description><![CDATA[<p><em>[처음 배우는 리액트 네이티브] 책을 공부하면서 정리한 내용입니다.</em></p>
<h3 id="usestate">useState</h3>
<p>ex.</p>
<pre><code>const [state, setState] = useState(initialState);</code></pre><p>호출하면 변수와 수정할 수 있는 세터 함수를 배열로 변환.
관리해야하는 상태의 수만큼 여러 번 사용 가능.
상태가 변경되면 컴포넌트가 변경된 내용을 반영하여 다시 렌더링.</p>
<h4 id="세터함수를-사용하는-방법">세터함수를 사용하는 방법</h4>
<ol>
<li>세터 함수에 변경될 상태의 값을 전달하는 방법</li>
<li>세터 함수의 피라미터에 함수를 전달하는 방법
ex.<pre><code>setState(prevState =&gt; {});</code></pre></li>
</ol>
<p>전달된 함수에는 변경되기 전의 상태의 값이 피라미터로 전달되고 이 값이 어떻게 수정할지 정의하면 됨.
세터 함수는 비동기로 동작하기 때문에 상태 변경이 여러 번 일어날 경우
상태가 변경되기 전에 또 다시 상태에 대한 업데이트가 실행.</p>
<pre><code>onPress={() =&gt; {
    setCount(count + 1);
    setCount(count + 1);
    console.log (`count: ${count}`);
    }}</code></pre><p>이때, 버튼을 클릭하면 1씩 증가하고 로그를 확인하면 증가 전의 현재 상태값이 나타난다.
이유 : 세터 함수가 비동기로 동작하기 때문에 세터 함수를 호출해도 상태가 바로 변경되지 않는다.</p>
<pre><code>...
onPress={() =&gt; {
    setCount(prevCount =&gt; prevCount + 1);
    setCount(prevCount =&gt; prevCount + 1);
    }}</code></pre><p>버튼을 클릭하면 2씩 증가.
상태에 대해 여러 업데이트가 함께 발생할 경우, 세터 함수에 함수를 인자로 전달하여 이전 상태값을 이용.</p>
<h3 id="useeffect">useEffect</h3>
<p>: 컴포넌트가 렌더링될 때마다 원하는 작업이 실행되도록 설정 가능
ex.</p>
<pre><code>useEffect(() =&gt; {}, []);</code></pre><p>첫 번째 파라미터 함수 : 조건을 만족할 때마다 호출
두 번째 파라미터 배열 : 함수가 호출되는 조건 설정</p>
<ul>
<li>배열이 없을 때 : 렌더링 될 때마다 함수 호출</li>
<li>배열이 있을 때 : (함수) 상태의 값을 변경하는 세터 함수가 비동기로 동작하므로 상태의 값이 변경되면 실행할 함수를 정의</li>
<li>배열이 비어있을 때 : 처음 렌더링이 될 때만 함수 호출 (컴포넌트가 마운트 될 때)</li>
<li>전달하는 함수에서 반환하는 함수를 정리함수라고 하는데, 실행 조건이 빈 배열인 경우 컴포넌트가 언마운트 될 때 정리함수 실행.
ex.<pre><code>useEffect(() =&gt; {
  console.log(&#39;\n===== Form Component Mount =====\n&#39;);
  return () =&gt; console.log(&#39;\n===== Form Component Unmount =====\n&#39;);
}, []);</code></pre></li>
</ul>
<h3 id="useref">useRef</h3>
<p>ex.</p>
<pre><code>const ref = useRef(initialValue);</code></pre><p>주의할 점</p>
<ol>
<li>컴포넌트의 ref로 지정하면 생성된 변수에 값이 저장되는 것이 아니라 변수의 .current에 해당 값을 담는 것.</li>
<li>usestate와 달리 useRef의 내용이 변경되어도 컴포넌트는 다시 렌더링 되지 않는다.<pre><code>const Form = () =&gt; {
...
const refName = useRef(null);
const refEmail = useRef(null);
</code></pre></li>
</ol>
<p>useEffect(() =&gt; {
    refName.current.focus();
    }, []);
...
return (</p>
<h3 id="">...</h3>
<pre><code>&lt;styledTextInput
 ...
 ref={refName}
 returnKeyType=&quot;next&quot;
 onSubmitEditing={() =&gt; refEmail.current.focus()} /&gt;
&lt;styledTextInput
 ...
 ref={refEmail}
 returnKeyType=&quot;done&quot; /&gt;
 ...
 );</code></pre><p>...</p>
<pre><code>refName과 refEmail을 생성해 각각 이름과 이메일을 입력받는 TextInput 컴포넌트의 ref로 설정했고, 키보드의 완료 버튼을 각각 next, done으로 변경.
컴포넌트가 마운트될 때, 포커스가 이름을 입력받는 컴포넌트가 있도록 함.
이름을 입력받은 컴포넌트의 확인 버튼(next)을 클릭하면 이메일을 입력받는 컴포넌트로 포커스로 이동.

### useMemo
: 동일한 연산의 반복 수행을 제거해서 선능 최적화.
ex.</code></pre><p>useMemo(() =&gt; {}, []);</p>
<pre><code>첫 번째 피라미터 함수
두 번째 피라미터 배열 : 함수 실행 조건, 지정된 값이 변화가 있는 경우에만 함수 호출
</code></pre><p>const getLength = text =&gt; {
    return text.length; };
...
const Length = () =&gt; {
    const [text, setText] = useState(list[0]);
    const [length, setLength] = useState(&#39;&#39;);</p>
<pre><code>const _onPress = () =&gt; {
    setLength(getLength(text));
    ++idx;
    if (idx &lt; list.length) setText(list[idx]);
};</code></pre><p>...
};</p>
<pre><code>배열에 있는 단어의 문자열의 길이를 구하는 코드.
마지막 문자열 이후에는 더 이상 문자열의 변화가 없기 때문에 같은 문자열의 길이를 반복해서 구한다.
</code></pre><p>const Length = () =&gt; {
    const [text, setText] = useState(list[0]);</p>
<pre><code>const _onPress = () =&gt; {
    ++idx;
    if (idx &lt; list.length) setText(list[idx]);
};
const length = useMemo (() =&gt; getLength(text), [text]);</code></pre><p> ...
 };</p>
<pre><code>useMemo를 사용하여 계산하는 값에 변화가 있는 경우에만 함수가 호출되도록 수정.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[4장. 스타일링]]></title>
            <link>https://velog.io/@j22_s00/4.-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</link>
            <guid>https://velog.io/@j22_s00/4.-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</guid>
            <pubDate>Fri, 09 Feb 2024 08:39:06 GMT</pubDate>
            <description><![CDATA[<p><em>[처음 배우는 리액트 네이티브] 책을 공부하면서 정리한 내용입니다.</em></p>
<h3 id="인라인-스타일링">인라인 스타일링</h3>
<p>: 컴포넌트에 직접 스타일을 입력하는 방식. 객체 형태로 전달
ex.</p>
<pre><code>&lt;View
    style={{
        flex: 1,
        backgroundColar: &#39;#fff&#39;,
        alignItems: &#39;center&#39;,
        justifyContent: &#39;center&#39;,
        }}
   &gt;</code></pre><p>클래스 스타일링
: 스타일시트에 정의된 스타일을 사용
ex.</p>
<pre><code>&lt;View style={styles.container}&gt;
 ...
&lt;/View&gt;
...
const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColar: &#39;#fff&#39;,
        alignItems: &#39;center&#39;,
        justifyContent: &#39;center&#39;,
    },
  });</code></pre><p>여러개의 스타일을 적용하고 싶을 땐, 배열을 사용.</p>
<h3 id="스타일-요소">스타일 요소</h3>
<h4 id="flex">flex</h4>
<p>: width나 height와 달리 항상 비율로 크기 결정. 값으로 숫자를 받으며 값이 0일 때는 설정된 width와 height 값에 따라, 양수인 경우 flex 값에 비례하여 크기 조정.</p>
<h4 id="flexdirection">flexDirection</h4>
<p>: 자식 컴포넌트가 쌓이는 방향 설정. 
coumn - 세로방향, row - 가로 방향, (-reverse은 역순)</p>
<h4 id="justifycontent">justifyContent</h4>
<p>: 컴포넌트를 정렬할 방식 설정. flexDirection과 동일한 방향.
flex-start - 시작점에서부터 정령
flex-end - 끝에섯부터 정렬
center - 중앙 정렬
space-between - 컴포넌트 사이의 공간을 동일하게 만들어서 정렬
space-around - 컴포넌트 각각의 주변 공간을 동일하게 만들어서 정렬
space-evenly - 컴포넌트 사이와 양 끝에 동일한 공간을 만들어서 정렬</p>
<h4 id="alignitems">alignItems</h4>
<p>: 컴포넌트를 정렬할 방식 설정. flexDirection과 수직인 방향.
flex-start - 시작점에서부터 정렬
flex-end - 끝에서부터 정렬
center - 중앙 정렬
stretch - alignItems의 방향으로 컴포넌트 확장
baseline - 컴포넌트 내부의 텍스트 베이스라인을 기준으로 정렬</p>
<h3 id="그림자">그림자</h3>
<p>shadowColor : 그림자 색 설정
shadowOffset : width와 height값을 지정하여 그림자 거리 설정
shadowOpacity : 그림자의 불투명도 설정
shadowRadius : 그림자의 흐림 반경 설정
-&gt; 하지만 ios에서만 적용 가능.</p>
<p>플랫폼마다 적용 여부가 다른 속성들을 위해 Platform 모듈 이용.</p>
<p>css와 유사하기 때문에 더욱 혼란스러울 수 있다. 이를 방지하기 위해 스타일드 컴포넌트 활용 추천. (설치)</p>
<h3 id="스타일드-컴포넌트">스타일드 컴포넌트</h3>
<p>: 자바스크립트 파일 안에 스타일을 작성하는 CSS-in-JS 라이브러리. 스타일이 적용된 컴포넌트</p>
<p>설치방법</p>
<pre><code>npm install styled-components</code></pre><p>ex.</p>
<pre><code>import styled from &#39;styled-components/native&#39;;

const MyTextComponent = styled.Text`
    color: #fff;
`;</code></pre><p>재사용시 css 사용.
ex.</p>
<pre><code>import styled, { css } from &#39;styled-components/native&#39;;

const whiteText = css`
    color : #fff;
    font-size: 14px;
`;
const MyBoldTextComponent = styled.Text`
    ${whiteText}
    font-weight: 600;
`;

const ErrorText = styled(whiteText)`
    font-weight: 600;
    color: red;
`;</code></pre><p>이미 작성된 스타일을 상속받아 새로운 스타일드 컴포넌트를 만들 때는 styled(컴포넌트이름)으로 표기.</p>
<p>porps, attrs를 사용하여 스타일 관리 가능</p>
<p>ThemeProvider</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[3장. 컴포넌트]]></title>
            <link>https://velog.io/@j22_s00/3%EC%9E%A5.-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@j22_s00/3%EC%9E%A5.-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Sun, 04 Feb 2024 09:28:13 GMT</pubDate>
            <description><![CDATA[<p><em>[처음 배우는 리액트 네이티브] 책을 공부하면서 정리한 내용입니다.</em></p>
<h3 id="jsx-문법">JSX 문법</h3>
<ol>
<li>여러개의 요소를 변환할 때에도 하나의 부모로 감싸야한다.
ex. View(=div), Fragment(import 필요, 단축 가능)<pre><code>&lt;View&gt; &lt;/View&gt;
</code></pre></li>
</ol>
<p><Fragment> </Fragment>
&lt;&gt; &lt;/&gt;</p>
<pre><code>
2. 변수 : 자바스크립트의 변수처럼 전달</code></pre><p>const 변수명 = &quot; &quot;;</p>
<pre><code>
3. 조건문 : if문, 삼항 연산자, AND/OR 연산자
 ; 컴포넌트 반환이 null -&gt; 허용, undefined -&gt; 오류


4. 주석</code></pre><p>{/* */}</p>
<p>태그 안 : //, /* */</p>
<pre><code>
5. 스타일링 : 객체 형태, 카멜표기법


### 컴포넌트
: 재사용이 가능한 조립 블록. 화면에 나타나는 UI요소.
ex. 리액트 네이티브 컴포넌트, Button 컴포넌트

### props
 : properties. 부모 컴포넌트로부터 전달된, 상속받은  속성값. 자식 컴포넌트에서는 변경 불가능

#### 속성으로 전달하는 방법
App.js</code></pre><MyButton title="Button" />
```
 : MyButton 컴포넌트를 호출 할 때 title 속성에 문자열 전달.

<p>MyButton.js</p>
<pre><code>const MyButton = props =&gt; {
    console.log(props);
    return (...);
 };</code></pre><p> : 전달받은 porps를 함수의 피라미터로 받아서 사용가능.</p>
<p>출력값 : &quot;title&quot; = &quot;Button&quot;</p>
<h4 id="태그를-사용하여-전달하는-방법">태그를 사용하여 전달하는 방법</h4>
<p>App.js</p>
<pre><code>&lt;MyButton&gt; Children Props &lt;/MyButton&gt;</code></pre><p> : 태그 사이에 있는 값은 자식 컴포넌트의 Porps에 children으로 전달.</p>
<p>MyButton.js</p>
<pre><code>const MyButton = props =&gt; {
    return (
        ...
        &lt;Text&gt; {props.children} &lt;/Text&gt;
    ...</code></pre><p> : props.children을 통해 전달받은 값 사용.</p>
<p>만약, 아무 값도 전달하지 않으면 아무것도 나타나지 않기 때문에 필수적으로 필요한 값이 있을 경우, 기본값을 defaultProps로 지정한다.</p>
<pre><code>MyButton.defaultProps = {
    title: &#39;Button&#39; };</code></pre><p>PropTypes
:props의 타입과 필수여부를 지정할 수 있는 방법
(prop-types 라이브러리 설치 필요)</p>
<pre><code>MyButton.propTypes = {
    title: PropsTypes.string.isRequired };</code></pre><p>: title의 값이 string이 아니거나 전달되지 않으면 경고 메세지가 출력된다.</p>
<h3 id="state">State</h3>
<p> : 컴포넌트 내부에서 생성되고 변경 가능. 상태가 변하면 컴포넌트는 리렌더링된다. Hooks를 사용해 함수형 컴포넌트로 관리.</p>
<h4 id="usestate-사용하기">useState 사용하기</h4>
<pre><code>const [state, setState] = useState(initialState);</code></pre><p> : state = 상태를 관리할 변수, 
 setState = 변수의 상태를 변경할 수 있는 세터 함수</p>
<h3 id="이벤트">이벤트</h3>
<ol>
<li><p>press 이벤트 : 웹프로그래밍에서의 onClick과 비슷. (4가지)
onPressIn - 터치가 시작될 때 항상 호출,
onPressOut - 터치가 헤제될 때 항상 호출,
onPress - 터치가 해제될 때 OnPressOut 이후 호출,
onLongPress - 터치가 일정시간 이상 지속될 시 호출</p>
</li>
<li><p>change 이벤트 : 변화를 감지. TexpInput 컴포넌트에서 자주 사용.</p>
</li>
<li><p>Pressable 컴포넌트 : 사용자의 터치에 상호작용하는 넘포넌트
HitRect - 버튼보다 약간 넓은 범위를 클릭해도 이벤트 발생 가능.
PressRect - 버튼 선택 상태에서 손가락이 이동 시 같은 처음 위치에서 이벤트 발생 가능.</p>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>